/*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"
#include "ScianScales.h"
#include "ScianDialogs.h"
#include "ScianTemplates.h"
#include "ScianTemplateHelper.h"
#include "ScianDepend.h"
#include "ScianSnap.h"
#include "ScianColorControls.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*/
	    DoUniqueTask(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)
    {
	real sliderBottom, sliderTop;
	real portionShown;
	real val;

	var = GetValue(hScroll);
	val = GetReal(var);

	sliderTop = maxx - (bounds[1] - bounds[0]) - fieldDepth * 2;
	if (sliderTop < val) sliderTop = val;

	sliderBottom = minx;
	if (sliderBottom > val) sliderBottom = val;

	SetSliderRange(hScroll, sliderTop, sliderBottom, 20.0);

	portionShown = bounds[1] - bounds[0] - fieldDepth * 2;
/*
	if (portionShown > sliderTop - sliderBottom) portionShown = sliderTop - sliderBottom;
*/
	SetPortionShown(hScroll, portionShown);
    }

    /*Adjust vscroll*/
    vScroll = GetVar(corral, VSCROLL);
    if (vScroll)
    {
	real sliderTop, sliderBottom;
	real val;
	real portionShown;

	var = GetValue(vScroll);
	val = GetReal(var);

	if (GetPredicate(corral, TOPDOWN))
	{
	    sliderTop = maxy;
	    sliderBottom = miny + (bounds[3] - bounds[2]) - fieldDepth * 2;
	}
	else
	{
	    sliderTop = maxy - (bounds[3] - bounds[2]) - fieldDepth * 2;
	    sliderBottom = miny;
	}
	if (sliderTop < val) sliderTop = val;
	if (sliderBottom > val) sliderBottom = val;
	SetSliderRange(vScroll, sliderTop, sliderBottom, 20.0);

	portionShown = bounds[3] - bounds[2] - fieldDepth * 2;
/*
	if (portionShown > sliderTop - sliderBottom) portionShown = sliderTop - sliderBottom;
*/
	SetPortionShown(vScroll, portionShown);
    }

    /*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;
    real exp;

    diff = max - min;
    exp = rfloor(rlog10(diff));
    return rpow(10.0, exp);
}

#ifdef PROTO
void ChooseGoodRange(real *min, real *max)
#else
void ChooseGoodRange(min, max)
real *min, *max;
#endif
/*Chooses a good range for data in min and max, returns in min and max*/
{
    /*If out of order, make in order*/
    if (*max <= *min)
    {
	*max = *min + 1.0;
    }

    if (*max > 0.0 && *min > 0.0)
    {
	/*Both positive*/
	real tryMin, tryMax;
	real expMin, expMax;

	expMin = rfloor(rlog10(*min));
	expMax = rceil(rlog10(*max));

	if (expMin < expMax - 1.0)
	{
	    /*The min might as well be zero*/
	    *min = 0.0;
	}
	else
	{
	    *min = rpow(10.0, expMin);
	}

	tryMax = rpow(10.0, expMax);

	/*Try dividing by 5*/
	if (tryMax / 2.0 >= *max)
	{
	    tryMax /= 2.0;
	}
	*max = tryMax;
    }
    else if (*max < 0.0 && *min < 0.0)
    {
	/*Both negative*/
	real tryMin, tryMax;
	real expMin, expMax;

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

	expMin = rfloor(rlog10(*min));
	expMax = rceil(rlog10(*max));

	if (expMin < expMax - 1.0)
	{
	    /*The min might as well be zero*/
	    *min = 0.0;
	}
	else
	{
	    *min = rpow(10.0, expMin);
	}

	tryMax = rpow(10.0, expMax);

	/*Try dividing by 5*/
	if (tryMax / 2.0 >= *max)
	{
	    tryMax /= 2.0;
	}
	*max = tryMax;

	*min = -*min;
	*max = -*max;
    }
    else
    {
	/*Span 0*/
	real tryMin, tryMax;
	real expMin, expMax;

	expMin = rfloor(rlog10(-*min));
	expMax = rceil(rlog10(*max));

	tryMax = rpow(10.0, expMax);

	/*Try dividing by 5*/
	if (tryMax / 2.0 >= *max)
	{
	    tryMax /= 2.0;
	}
	*max = tryMax;

	tryMin = -rpow(10.0, expMin);

	/*Try dividing by 5*/
	if (tryMin / 2.0 <= *min)
	{
	    tryMin /= 2.0;
	}
	*min = tryMin;
    }
}

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))
	    {
		return false;
	    }
	    ++t;
	}

	intValue = v;

	if (intValue != v)
	{
	    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))
	    {
		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
    {
	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;
}

static ObjPtr ChangeDirectControlValue(control)
ObjPtr control;
/*Changes a Direct control's value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;

    repObj = GetVarSurely("ChangeDirectControlValue", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("ChangeDirectControlValue", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    SetVar(repObj, whichVar, GetValue(control));
    ImInvalid(repObj);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeDirectControlAppearance(control)
ObjPtr control;
/*Makes a Direct control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    FuncTyp method;

    repObj = GetVarSurely("MakeDirectControlAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("MakeDirectControlAppearance", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    SetValue(control, GetVar(repObj, whichVar));
    SetMethod(control, CHANGEDVALUE, method);
    method = GetMethod(control, EXTRAAPPEARANCE);
    if (method)
    {
	(*method)(control);
    }
    ImInvalid(control);
    InhibitLogging(false);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

#ifdef PROTO
void AssocDirectControlWithVar(ObjPtr control, ObjPtr object, NameTyp var)
#else
void AssocDirectControlWithVar(control, object, var)
ObjPtr control;
ObjPtr object;
NameTyp var;
#endif
/*Associates a control directly with a variable of an object.  The mapping
  from control to variable must be 1:1, and an ImInvalid must be an appropriate
  response to changing the variable
*/
{
    SetVar(control, REPOBJ, object);
    SetVar(control, WHICHVAR, NewSymbol(var));
    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, var);
    SetMethod(control, CHANGEDVALUE, ChangeDirectControlValue);
    SetMethod(control, APPEARANCE, MakeDirectControlAppearance);
}

static ObjPtr ChangeFlagControlValue(control)
ObjPtr control;
/*Changes a flag control's value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;
    int whichFlag, oldFlags;
    Bool flagOn;

    repObj = GetVarSurely("ChangeFlagControlValue", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("ChangeFlagControlValue", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetIntVar("ChangeFlagControlValue", control, WHICHFLAG);
    if (!var)
    {
	return ObjFalse;
    }
    whichFlag = GetInt(var);

    var = GetVar(repObj, whichVar);
    if (var)
    {
	oldFlags = GetInt(var);
    }
    else
    {
	oldFlags = 0;
    }

    var = GetValue(control);
    if (!var)
    {
	return ObjFalse;
    }
    flagOn = IsTrue(var);

    if (flagOn)
    {
	oldFlags |= whichFlag;
    }
    else
    {
	oldFlags &= ~whichFlag;
    }

    SetVar(repObj, whichVar, NewInt(oldFlags));
    ImInvalid(repObj);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeFlagControlAppearance(control)
ObjPtr control;
/*Makes a Flag control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    FuncTyp method;
    int whichFlag, oldFlags;

    repObj = GetVarSurely("MakeFlagControlAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("MakeFlagControlAppearance", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetIntVar("MakeFlagControlAppearance", control, WHICHFLAG);
    if (!var)
    {
	return ObjFalse;
    }
    whichFlag = GetInt(var);

    var = GetVar(repObj, whichVar);
    if (var)
    {
	oldFlags = GetInt(var);
    }
    else
    {
	oldFlags = 0;
    }

    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    SetValue(control, oldFlags & whichFlag ? NewInt(1) : NewInt(0));
    SetMethod(control, CHANGEDVALUE, method);
    method = GetMethod(control, EXTRAAPPEARANCE);
    if (method)
    {
	(*method)(control);
    }
    ImInvalid(control);
    InhibitLogging(false);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

#ifdef PROTO
void AssocFlagControlWithVar(ObjPtr control, ObjPtr object, NameTyp var, int whichFlag)
#else
void AssocFlagControlWithVar(control, object, var, whichFlag)
ObjPtr control;
ObjPtr object;
NameTyp var;
int whichFlag;
#endif
/*Associates a control directly with a flag of an object's variable.  The mapping
  from control to variable must be n:1, and an ImInvalid must be an appropriate
  response to changing the variable
  whichFlag is the flag, which should be a power of 2.  The control must have
  an integer value.
*/
{
    SetVar(control, REPOBJ, object);
    SetVar(control, WHICHVAR, NewSymbol(var));
    SetVar(control, WHICHFLAG, NewInt(whichFlag));
    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, var);
    SetMethod(control, CHANGEDVALUE, ChangeFlagControlValue);
    SetMethod(control, APPEARANCE, MakeFlagControlAppearance);
}

ObjPtr ShowTextRealMinMaxError(control)
ObjPtr control;
/*Shows a text real box's min max error*/
{
    WinInfoPtr errWindow;
    ObjPtr var;
    int controlFlags;

    var = GetVar(control, CONTROLFLAGS);
    if (var)
    {
	controlFlags = GetInt(var);
    }
    else
    {
	controlFlags = 0;
    }

    var = GetVar(control, MINMAX);
    if (var)
    {
	real *elements;
	char minString[40], maxString[40];
	elements = ELEMENTS(var);
	PrintNumber(minString, elements[0]);
	PrintNumber(maxString, elements[1]);

	sprintf(tempStr, "You have entered a number outside the allowed range of \
%c%s, %s%c.  Please try again.", (controlFlags & TR_NE_BOTTOM) ? '(' : '[',
		minString, maxString,
		(controlFlags & TR_NE_TOP) ? ')' : ']');
    }
    else
    {
	strcpy(tempStr, "You have entered a number outside the allowed range.  \
Please try again.");
    }
    if (runningScript)
    {
	fprintf(stderr, "The following error occured while reading the script\n(It is probably safe to ignore):\n");
	fprintf(stderr, "%s\n", tempStr);
    }
    else
    {
	errWindow = AlertUser(UIERRORALERT, (WinInfoPtr) 0, tempStr, 0, 0, "");
	SetVar((ObjPtr) errWindow, INHIBITLOGGING, ObjTrue);
    }
    return ObjTrue;
}

static ObjPtr ChangeTextRealControlValue(control)
ObjPtr control;
/*Changes a text real control's value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;
    int controlFlags;
    real minMax[2];
    real value;
    char *s;

    repObj = GetVarSurely("ChangeTextRealControlValue", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("ChangeTextRealControlValue", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetVar(control, MINMAX);
    if (var)
    {
	Array2CArray(minMax, var);
    }
    else
    {
	minMax[0] = minusInf;
	minMax[1] = plusInf;
    }

    var = GetVar(control, CONTROLFLAGS);
    if (var)
    {
	controlFlags = GetInt(var);
    }
    else
    {
	controlFlags = 0;
    }

    var = GetValue(control);
    if ((!var) || (!IsString(var)))
    {
	return ObjFalse;
    }

    s = GetString(var);

    if (ParseReal(&value, s))
    {
	if (value == missingData)
	{
	    if (0 == (controlFlags & TR_MISSING_OK))
	    {
		WarnUser(CW_MISSINGERROR);
		return ObjFalse;
	    }
	}
	else
	{
	    if (controlFlags & TR_NE_BOTTOM)
	    {
		if (value <= minMax[0])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }
	    else
	    {
		if (value < minMax[0])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }

	    if (controlFlags & TR_NE_TOP)
	    {
		if (value >= minMax[1])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }
	    else
	    {
		if (value > minMax[1])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }
	    if (controlFlags & TR_INT_ONLY)
	    {
		if (value != floor(value))
		{
		    WarnUser(CW_NONINTERROR);
		    return ObjFalse;
		}
	    }
	}

	/*Dropped through, it must be OK*/
	SetVar(repObj, whichVar, NewReal(value));
	ImInvalid(repObj);
    }
    else
    {
	WarnUser(CW_NUMBERERROR);
	return ObjFalse;
    }

    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeTextRealControlAppearance(control)
ObjPtr control;
/*Makes a text real control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    FuncTyp method;

    repObj = GetVarSurely("MakeTextRealControlAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("MakeTextRealControlAppearance", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetRealVar("MakeTextRealControlAppearance", repObj, whichVar);
    if (var)
    {
	PrintNumber(tempStr, GetReal(var));
    }
    else
    {
	strcpy(tempStr, "");
    }

    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    SetValue(control, NewString(tempStr));
    SetMethod(control, CHANGEDVALUE, method);
    method = GetMethod(control, EXTRAAPPEARANCE);
    if (method)
    {
	(*method)(control);
    }
    ImInvalid(control);
    InhibitLogging(false);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

#ifdef PROTO
void AssocTextRealControlWithVar(ObjPtr control, ObjPtr object, NameTyp var, real min, real max, int flags)
#else
void AssocTextRealControlWithVar(control, object, var, min, max, flags)
ObjPtr control;
ObjPtr object;
real min, max;
NameTyp var;
int flags;
#endif
/*Associates a control with a text real value with a variable of an object.
  An ImInvalid must be an appropriate response to changing the variable
*/
{
    ObjPtr minMax;
    real *elements;

    SetVar(control, REPOBJ, object);
    SetVar(control, WHICHVAR, NewSymbol(var));
    minMax = NewRealArray(1, 2L);
    elements = ELEMENTS(minMax);
    elements[0] = min;
    elements[1] = max;
    SetVar(control, MINMAX, minMax);
    SetVar(control, CONTROLFLAGS, NewInt(flags));
    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, var);
    SetMethod(control, RANGEALERT, ShowTextRealMinMaxError);
    SetMethod(control, CHANGEDVALUE, ChangeTextRealControlValue);
    SetMethod(control, APPEARANCE, MakeTextRealControlAppearance);
}

static ObjPtr ChangeTextIntControlValue(control)
ObjPtr control;
/*Changes a text int control's value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;
    int controlFlags;
    real minMax[2];
    int value;
    char *s;

    repObj = GetVarSurely("ChangeTextIntControlValue", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("ChangeTextIntControlValue", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetVar(control, MINMAX);
    if (var)
    {
	Array2CArray(minMax, var);
    }
    else
    {
	minMax[0] = minusInf;
	minMax[1] = plusInf;
    }

    var = GetVar(control, CONTROLFLAGS);
    if (var)
    {
	controlFlags = GetInt(var);
    }
    else
    {
	controlFlags = 0;
    }

    var = GetValue(control);
    if ((!var) || (!IsString(var)))
    {
	return ObjFalse;
    }

    s = GetString(var);

    if (ParseInteger(&value, s))
    {
	if (value < minMax[0])
	{
	    DeferMessage(control, RANGEALERT);
	    return ObjFalse;
	}
	if (value > minMax[1])
	{
	    DeferMessage(control, RANGEALERT);
	    return ObjFalse;
	}

	/*Dropped through, it must be OK*/
	SetVar(repObj, whichVar, NewInt(value));
	ImInvalid(repObj);
    }
    else
    {
	WarnUser(CW_NUMBERERROR);
	return ObjFalse;
    }

    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeTextIntControlAppearance(control)
ObjPtr control;
/*Makes a text int control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    FuncTyp method;

    repObj = GetVarSurely("MakeTextIntControlAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("MakeTextIntControlAppearance", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetIntVar("MakeTextIntControlAppearance", repObj, whichVar);
    if (var)
    {
	PrintNumber(tempStr, GetReal(var));
    }
    else
    {
	strcpy(tempStr, "");
    }

    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    SetValue(control, NewString(tempStr));
    SetMethod(control, CHANGEDVALUE, method);
    method = GetMethod(control, EXTRAAPPEARANCE);
    if (method)
    {
	(*method)(control);
    }
    ImInvalid(control);
    InhibitLogging(false);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

#ifdef PROTO
void AssocTextIntControlWithVar(ObjPtr control, ObjPtr object, NameTyp var, real min, real max, int flags)
#else
void AssocTextIntControlWithVar(control, object, var, min, max, flags)
ObjPtr control;
ObjPtr object;
real min, max;
NameTyp var;
int flags;
#endif
/*Associates a control with a text int value with a variable of an object.
  An ImInvalid must be an appropriate response to changing the variable
*/
{
    ObjPtr minMax;
    real *elements;

    SetVar(control, REPOBJ, object);
    SetVar(control, WHICHVAR, NewSymbol(var));
    minMax = NewRealArray(1, 2L);
    elements = ELEMENTS(minMax);
    elements[0] = min;
    elements[1] = max;
    SetVar(control, MINMAX, minMax);
    SetVar(control, CONTROLFLAGS, NewInt(flags));
    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, var);
    SetMethod(control, RANGEALERT, ShowTextRealMinMaxError);
    SetMethod(control, CHANGEDVALUE, ChangeTextIntControlValue);
    SetMethod(control, APPEARANCE, MakeTextIntControlAppearance);
}

static ObjPtr ChangeIndexedTextRealControlValue(control)
ObjPtr control;
/*Changes an indexed text real control's value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;
    long whichIndex;
    long nIndices;
    long k;
    real *el1, *el2;
    int controlFlags;
    real minMax[2];
    real value;
    char *s;

    repObj = GetVarSurely("ChangeIndexedTextRealControlValue", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("ChangeIndexedTextRealControlValue", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetIntVar("ChangeIndexedTextRealControlValue", control, WHICHINDEX);
    if (!var)
    {
	return ObjFalse;
    }
    whichIndex = GetInt(var);

    var = GetVar(control, MINMAX);
    if (var)
    {
	Array2CArray(minMax, var);
    }
    else
    {
	minMax[0] = minusInf;
	minMax[1] = plusInf;
    }

    var = GetVar(control, CONTROLFLAGS);
    if (var)
    {
	controlFlags = GetInt(var);
    }
    else
    {
	controlFlags = 0;
    }

    var = GetValue(control);
    if ((!var) || (!IsString(var)))
    {
	return ObjFalse;
    }

    s = GetString(var);

    if (ParseReal(&value, s))
    {
	if (value == missingData)
	{
	    if (0 == (controlFlags & TR_MISSING_OK))
	    {
		WarnUser(CW_MISSINGERROR);
		return ObjFalse;
	    }
	}
	else
	{
	    if (controlFlags & TR_NE_BOTTOM)
	    {
		if (value <= minMax[0])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }
	    else
	    {
		if (value < minMax[0])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }

	    if (controlFlags & TR_NE_TOP)
	    {
		if (value >= minMax[1])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }
	    else
	    {
		if (value > minMax[1])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }

	}

	/*Dropped through, it must be OK*/
	var = GetArrayVar("ChangeIndexedTextRealControlValue", repObj, whichVar);
	if (!var)
	{
	    return ObjFalse;
	}
	el1 = ELEMENTS(var);
	nIndices = DIMS(var)[0];
	var = NewRealArray(1, nIndices);
	el2 = ELEMENTS(var);
	for (k = 0; k < nIndices; ++k)
	{
	    el2[k] = el1[k];
	}
	el2[whichIndex] = value;
	SetVar(repObj, whichVar, var);

	ImInvalid(repObj);
    }
    else
    {
	WarnUser(CW_NUMBERERROR);
	return ObjFalse;
    }

    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeIndexedTextRealControlAppearance(control)
ObjPtr control;
/*Makes a text real control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    long whichIndex;
    FuncTyp method;

    repObj = GetVarSurely("MakeIndexedTextRealControlAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("MakeIndexedTextRealControlAppearance", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetIntVar("MakeIndexedTextRealControlAppearance", control, WHICHINDEX);
    if (!var)
    {
	return ObjFalse;
    }
    whichIndex = GetInt(var);

    var = GetArrayVar("MakeIndexedTextRealControlAppearance", repObj, whichVar);
    if (var)
    {
	real *elements;
	elements = ELEMENTS(var);

	PrintNumber(tempStr, elements[whichIndex]);
    }
    else
    {
	strcpy(tempStr, "");
    }

    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    SetValue(control, NewString(tempStr));
    SetMethod(control, CHANGEDVALUE, method);
    method = GetMethod(control, EXTRAAPPEARANCE);
    if (method)
    {
	(*method)(control);
    }
    ImInvalid(control);
    InhibitLogging(false);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

#ifdef PROTO
void AssocIndexedTextRealControlWithVar(ObjPtr control, ObjPtr object, NameTyp var, long whichIndex, real min, real max, int flags)
#else
void AssocIndexedTextRealControlWithVar(control, object, var, whichIndex, min, max, flags)
ObjPtr control;
ObjPtr object;
NameTyp var;
long whichIndex;
real min, max;
int flags;
#endif
/*Associates a control with a text real value with an indexed variable of an object.
  An ImInvalid must be an appropriate response to changing the variable
*/
{
    ObjPtr minMax;
    real *elements;

    SetVar(control, REPOBJ, object);
    SetVar(control, WHICHVAR, NewSymbol(var));
    minMax = NewRealArray(1, 2L);
    elements = ELEMENTS(minMax);
    elements[0] = min;
    elements[1] = max;
    SetVar(control, MINMAX, minMax);
    SetVar(control, CONTROLFLAGS, NewInt(flags));
    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, var);
    SetVar(control, WHICHINDEX, NewInt(whichIndex));
    SetMethod(control, RANGEALERT, ShowTextRealMinMaxError);
    SetMethod(control, CHANGEDVALUE, ChangeIndexedTextRealControlValue);
    SetMethod(control, APPEARANCE, MakeIndexedTextRealControlAppearance);
}

static ObjPtr ChangeColorControlValue(control)
ObjPtr control;
/*Changes a color control's value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;
    real hsv[3], rgb[3];
    real hs[2];

    repObj = GetVarSurely("ChangeColorControlValue", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("ChangeColorControlValue", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetVar(repObj, whichVar);
    if (!var)
    {
	var = GetVar(control, OLDVAR);
    }
    if (!var)
    {
	rgb[0] = rgb[1] = rgb[2] = 0.0;
    }
    else if (IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 3)
    {
	Array2CArray(rgb, var);
    }
    else if (IsInt(var))
    {
	rgb[0] = uiColors[GetInt(var)][0] / 255.0;
	rgb[1] = uiColors[GetInt(var)][1] / 255.0;
	rgb[2] = uiColors[GetInt(var)][2] / 255.0;
    }
    else
    {
	ReportError("ChangeColorControlValue", "Bad color value");
    }
    RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), rgb[0], rgb[1], rgb[2]);

    var = GetValue(control);
    if (!var)
    {
	return ObjFalse;
    }
    Array2CArray(hs, var);
    hsv[0] = hs[0];
    hsv[1] = hs[1];

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), hsv[0], hsv[1], hsv[2]);

    var = NewRealArray(1, 3L);
    CArray2Array(var, rgb);

    SetVar(repObj, whichVar, var);
    ImInvalid(repObj);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr PasteColorControl(control, color)
ObjPtr control, color;
/*Pastes color into a color control*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;

    repObj = GetVarSurely("PasteColorControl", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("PasteColorControl", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    if (IsRealArray(color) && RANK(color) == 1 && DIMS(color)[0] == 3)
    {
	/*Set the object's var to this*/
	SetVar(repObj, whichVar, color);
	MakeObjectName(tempStr, repObj);

	Log("begin snapshot ");
	Log(tempStr);
	Log("\n");
	LogVariable(repObj, whichVar);
	Log("end snapshot\n");

	ImInvalid(repObj);
    }
    else if (IsRealArray(color) && RANK(color) == 1 && DIMS(color)[0] == 2)
    {
    real hsv[3], rgb[3];
    real hs[2];

    var = GetVar(repObj, whichVar);
    if (!var)
    {
	var = GetVar(control, OLDVAR);
    }
    if (!var)
    {
	rgb[0] = rgb[1] = rgb[2] = 0.0;
    }
    else if (IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 3)
    {
	Array2CArray(rgb, var);
    }
    else if (IsInt(var))
    {
	rgb[0] = uiColors[GetInt(var)][0] / 255.0;
	rgb[1] = uiColors[GetInt(var)][1] / 255.0;
	rgb[2] = uiColors[GetInt(var)][2] / 255.0;
    }
    else
    {
	PasteError(control, color);
    }
    RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), rgb[0], rgb[1], rgb[2]);

    Array2CArray(hs, color);
    hsv[0] = hs[0];
    hsv[1] = hs[1];

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), hsv[0], hsv[1], hsv[2]);

    var = NewRealArray(1, 3L);
    CArray2Array(var, rgb);

    SetVar(repObj, whichVar, var);
    ImInvalid(repObj);
    SetVar(control, APPEARANCE, ObjTrue);
    }
    return ObjTrue;
}

static ObjPtr MakeColorControlAppearance(control)
ObjPtr control;
/*Makes a color control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    FuncTyp method;
    real hsv[3], rgb[3];

    repObj = GetVarSurely("MakeColorControlAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("MakeColorControlAppearance", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetVar(repObj, whichVar);
    if (!var)
    {
	return ObjFalse;
    }
    if (IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 3)
    {
	Array2CArray(rgb, var);
    }
    else if (IsInt(var))
    {
	rgb[0] = uiColors[GetInt(var)][0] / 255.0;
	rgb[1] = uiColors[GetInt(var)][1] / 255.0;
	rgb[2] = uiColors[GetInt(var)][2] / 255.0;
    }
    else
    {
	ReportError("MakeColorControlAppearance", "Bad color value");
    }
    SetVar(control, OLDVAR, var);
    RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), rgb[0], rgb[1], rgb[2]);

    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    var = NewRealArray(1, 2L);
    CArray2Array(var, hsv);
    SetValue(control, var);
    SetMethod(control, CHANGEDVALUE, method);
    method = GetMethod(control, EXTRAAPPEARANCE);
    if (method)
    {
	(*method)(control);
    }
    ImInvalid(control);
    InhibitLogging(false);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

#ifdef PROTO
void AssocColorControlWithVar(ObjPtr control, ObjPtr object, NameTyp var)
#else
void AssocColorControlWithVar(control, object, var)
ObjPtr control;
ObjPtr object;
NameTyp var;
#endif
/*Associates a color directly with a variable of an object.  The mapping
  from control to variable must be 1:1, and an ImInvalid must be an appropriate
  response to changing the variable
*/
{
    SetVar(control, REPOBJ, object);
    SetVar(control, WHICHVAR, NewSymbol(var));
    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, var);
    SetMethod(control, CHANGEDVALUE, ChangeColorControlValue);
    SetMethod(control, APPEARANCE, MakeColorControlAppearance);
    SetMethod(control, PASTE, PasteColorControl);
}

static ObjPtr ChangeBrightnessControlValue(control)
ObjPtr control;
/*Changes a brightness control's value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;
    real hsv[3], rgb[3];

    repObj = GetVarSurely("ChangeBrightnessControlValue", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("ChangeBrightnessControlValue", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetVar(repObj, whichVar);
    if (!var)
    {
	var = GetVar(control, OLDVAR);
    }
    if (!var)
    {
	rgb[0] = rgb[1] = rgb[2] = 0.0;
    }
    else if (IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 3)
    {
	Array2CArray(rgb, var);
    }
    else if (IsInt(var))
    {
	rgb[0] = uiColors[GetInt(var)][0] / 255.0;
	rgb[1] = uiColors[GetInt(var)][1] / 255.0;
	rgb[2] = uiColors[GetInt(var)][2] / 255.0;
    }
    else
    {
	ReportError("ChangeBrightnessControlValue", "Bad color value");
    }
    RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), rgb[0], rgb[1], rgb[2]);

    var = GetValue(control);
    if (!var)
    {
	return ObjFalse;
    }
    hsv[2] = GetReal(var);

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), hsv[0], hsv[1], hsv[2]);

    var = NewRealArray(1, 3L);
    CArray2Array(var, rgb);

    SetVar(repObj, whichVar, var);
    ImInvalid(repObj);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeBrightnessControlAppearance(control)
ObjPtr control;
/*Makes a color control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    FuncTyp method;
    real hsv[3], rgb[3];

    repObj = GetVarSurely("MakeBrightnessControlAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("MakeBrightnessControlAppearance", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetVar(repObj, whichVar);
    if (!var)
    {
	return ObjFalse;
    }
    if (IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 3)
    {
	Array2CArray(rgb, var);
    }
    else if (IsInt(var))
    {
	rgb[0] = uiColors[GetInt(var)][0] / 255.0;
	rgb[1] = uiColors[GetInt(var)][1] / 255.0;
	rgb[2] = uiColors[GetInt(var)][2] / 255.0;
    }
    else
    {
	ReportError("MakeBrightnessControlAppearance", "Bad color value");
    }
    SetVar(control, OLDVAR, var);
    RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), rgb[0], rgb[1], rgb[2]);

    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    SetValue(control, NewReal(hsv[2]));
    SetMethod(control, CHANGEDVALUE, method);
    method = GetMethod(control, EXTRAAPPEARANCE);
    if (method)
    {
	(*method)(control);
    }
    ImInvalid(control);
    InhibitLogging(false);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

#ifdef PROTO
void AssocBrightnessControlWithVar(ObjPtr control, ObjPtr object, NameTyp var)
#else
void AssocBrightnessControlWithVar(control, object, var)
ObjPtr control;
ObjPtr object;
NameTyp var;
#endif
/*Associates a brightness control with a variable of an object.  The mapping
  from control to variable must be 1:1, and an ImInvalid must be an appropriate
  response to changing the variable
*/
{
    SetVar(control, REPOBJ, object);
    SetVar(control, WHICHVAR, NewSymbol(var));
    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, var);
    SetMethod(control, CHANGEDVALUE, ChangeBrightnessControlValue);
    SetMethod(control, APPEARANCE, MakeBrightnessControlAppearance);
}

static ObjPtr ChangeInhibitControlValue(control)
ObjPtr control;
/*Changes an inhibit control's value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;

    repObj = GetVarSurely("ChangeInhibitControlValue", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("ChangeDirectControlValue", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    var = GetValue(control);
    if (IsTrue(var))
    {
	SetVar(repObj, whichVar, NULLOBJ);
    }
    else
    {
	SetVar(repObj, whichVar, GetVar(control, OLDVAR));
    }
    ImInvalid(repObj);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeInhibitControlAppearance(control)
ObjPtr control;
/*Makes an inhibit control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    FuncTyp method;

    repObj = GetVarSurely("MakeInhibitControlAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetSymbolVar("MakeInhibitControlAppearance", control, WHICHVAR);
    if (!var)
    {
	return ObjFalse;
    }
    whichVar = GetSymbolID(var);

    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    var = GetVar(repObj, whichVar);
    if (var)
    {
	SetVar(control, OLDVAR, var);
	SetValue(control, NewInt(0));
    }
    else
    {
	SetValue(control, NewInt(1));
    }

    SetMethod(control, CHANGEDVALUE, method);
    InhibitLogging(false);
    ImInvalid(repObj);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

#ifdef PROTO
void AssocInhibitControlWithVar(ObjPtr control, ObjPtr object, NameTyp var, ObjPtr oldVal)
#else
void AssocInhibitControlWithVar(control, object, var, oldVal)
ObjPtr control;
ObjPtr object;
NameTyp var;
ObjPtr oldVal;
#endif
/*Associates an inhibit with a variable of an object.  The mapping
  from control to variable must be 1:1, and an ImInvalid must be an appropriate
  response to changing the variable

  An inhibit control sets the variable it controls to NULLOBJ when it is on
  and puts it back the way it was when turned off.  Other controls which control
  the same thing must use a similar method.  Right now, this is intended to
  work with brightness and color controls.  oldVal is an initial old value
*/
{
    SetVar(control, REPOBJ, object);
    SetVar(control, WHICHVAR, NewSymbol(var));
    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, var);
    SetVar(control, OLDVAR, oldVal);
    SetMethod(control, CHANGEDVALUE, ChangeInhibitControlValue);
    SetMethod(control, APPEARANCE, MakeInhibitControlAppearance);
}

ObjPtr MakeSaveButtonAppearance(object)
ObjPtr object;
/*Makes a save button's APPEARANCE*/
{
    ActivateButton(object, true);
    SetVar(object, APPEARANCE, ObjTrue);
    return ObjTrue;
}

ObjPtr SaveButtonChanged(object)
ObjPtr object;
/*Changes a save button*/
{
    ObjPtr repObj;

    ActivateButton(object, false);
    SetVar(object, APPEARANCE, ObjTrue);
    repObj = GetVar((ObjPtr) object, REPOBJ);
    if (repObj)
    {
	Log("save controls");
	DeferMessage(repObj, SAVECPANEL);
    }
    return ObjTrue;
}

ObjPtr ChangeControlPanelButton(object)
ObjPtr object;
/*Changedvalue for a control button*/
{
    ObjPtr panel, panelContents, value, curButton, parent;

    value = GetValue(object);

    parent = GetVar(object, PARENT);
    curButton = GetVar(parent, BUTTON);

    if (value && IsInt(value) && 0 == GetInt(value) && curButton != object)
    {
	panel = GetObjectVar("ChangeControlPanelButton", object, PANEL);
	panelContents = GetListVar("ChangeControlPanelButton", object, PANELCONTENTS);
	if (panel && panelContents)
	{
	    ThingListPtr runner;

	    if (!GetPredicate(object, CONTROLSADDED))
	    {
		/*Have to add controls*/
		FuncTyp method;
		ObjPtr repObj;

		SetVar(object, CONTROLSADDED, ObjTrue);
		repObj = GetObjectVar("ChangeControlPanelButton", object, REPOBJ);
		if (repObj)
		{
		    method = GetMethod(object, ADDCONTROLS);
		    if (method)
		    {
			(*method)(repObj, panelContents);
		    }

		    /*Make a save settings button*/
		    if (GetPredicate(object, CANSAVESETTINGS))
		    {
			ObjPtr button, list;

			button = TemplateButton(SettingsTemplate, "Save All Settings");
			PrefixList(panelContents, button);
			SetVar(button, PARENT, panel);
			SetVar(button, REPOBJ, repObj);
			SetVar(button, HELPSTRING, NewString("Press this button to save all \
the settings for this object to a disk file.  Every setting for this object, \
including settings set through other control panels, will be saved.\n\n\
Settings are saved in a subdirectory named .scianSettings in your home directory.  \
If that directory does not exist, it will automatically be created.  Settings are \
saved in a file with a name of the form <name>.<type> where <name> is the name of \
the object (with spaces converted to underscores) and <type> is a string indicating the type \
of the object.\n\n\
For example, the controls for the LVR-5000 recorder driver will be stored in a \
file named LVR-5000.recdvr in the directory ~/.scianSettings.  The files are actually \
fragments of scripts.  Do not edit them unless you are really adventurous."));
			ActivateButton(button, false);
			list = AssembleSnapVars(repObj);
			if (list)
			{
			    ThingListPtr runner;

			    runner = LISTOF(list);
			    while (runner)
			    {
				NameTyp symbolID;

				symbolID = GetSymbolID(runner -> thing);
				DeclareIndirectDependency(button, APPEARANCE, REPOBJ, symbolID);
			
				runner = runner -> next;
			    }
			}
			SetVar(button, APPEARANCE, ObjTrue);
			SetMethod(button, APPEARANCE, MakeSaveButtonAppearance);
			SetMethod(button, CHANGEDVALUE, SaveButtonChanged);
		    }
		}
	    }

	    runner = LISTOF(panelContents);
	    while (runner)
	    {
		SetVar(runner -> thing, PARENT, panel);
		runner = runner -> next;
	    }
	    SetVar(panel, CONTENTS, panelContents);
	    if (curButton)
	    {
		SetVar(curButton, VALUE, NewInt(0));
	    }
	    SetVar(object, VALUE, NewInt(1));
	    SetVar(parent, BUTTON, object);
	    ImInvalid(panel);
	    return ObjTrue;
	}
	else
	{
	    return ObjFalse;
	}
    }
    else
    {
	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();
    InitScales();
    InitPerspecControls();
    InitTitleBoxes();
    InitTrackControls();
    InitColorControls();
}
 
void KillControls()
/*Kills the controls*/
{
    KillColorControls();
    KillTrackControls();
    KillTitleBoxes();
    KillPerspecControls();
    KillScales();
    KillTextBoxes();
    KillSliders();
    KillButtons();
    RemoveFromReferenceList(flowLineClass);
    RemoveFromReferenceList(controlFieldClass);
    RemoveFromReferenceList(switchClass);
    RemoveFromReferenceList(corralClass);
    RemoveFromReferenceList(fieldClass);
    RemoveFromReferenceList(greyPanelClass);
    RemoveFromReferenceList(panelClass);
    RemoveFromReferenceList(controlClass);
    RemoveFromReferenceList(allSelected);
    RemoveFromReferenceList(screenClass);
}

