/*ScianObjWindows.c
  Object window stuff
  Eric Pepke
  March 28, 1990
*/
#include "Scian.h"
#include "ScianTypes.h"
#include "ScianLists.h"
#include "ScianWindows.h"
#include "ScianColors.h"
#include "ScianIDs.h"
#include "ScianObjWindows.h"
#include "ScianErrors.h"
#include "ScianDraw.h"
#include "ScianControls.h"
#include "ScianArrays.h"
#include "ScianScripts.h"
#include "ScianVisWindows.h"
#include "ScianDialogs.h"
#include "ScianIcons.h"
#include "ScianEvents.h"
#include "ScianStyle.h"
#include "ScianObjFunctions.h"
#include "ScianPictures.h"
#include "ScianButtons.h"
#include "ScianTitleBoxes.h"
#include "ScianTextBoxes.h"
#include "ScianSliders.h"

#ifdef PROTO
void ReshapeList(ThingListPtr, int, int, int, int, int, int, int, int);
#else
void ReshapeList();
#endif

int pauseDrawing = 0;
char tempStr[TEMPSTRSIZE + 1];
ObjPtr objWindowClass;			/*Object window class*/
ObjPtr dragBuffer = NULLOBJ;		/*Objects held*/
Bool showWindows = true;		/*Show new opened windows*/

ObjPtr NewControlWindow(object)
ObjPtr object;
/*Opens a new control window for object*/
{
    FuncTyp newCtlWindow;
    ObjPtr retVal;
    ObjPtr repObj, name, repName = NULLOBJ;
    char windowName[255];

    if (IsIcon(object))
    {
	/*It's an icon, use its REPOBJ.  Do nothing if it has none.*/
	object = GetVar(object, REPOBJ);
	if (!object)
	{
	    return ObjFalse;
	}
    }

    strcpy(windowName, "");

    MakeVar(object, NAME);
    name = GetVar(object, NAME);
    if (name)
    {
	if (repName)
	{
	    strcat(windowName, " ");
	}
	strcat(windowName, GetString(name));
    }

    newCtlWindow = GetMethod(object, NEWCTLWINDOW);
    if (newCtlWindow)
    {
	PauseDrawing(true);
	showWindows = showControlPanels;
	retVal = (*newCtlWindow)(object, windowName);
	showWindows = true;
	PauseDrawing(false);
    }
    else
    {
	retVal = NULLOBJ;
    }
    return retVal;
}

void SetEndpoints(object, x1, y1, x2, y2)
ObjPtr object;
int x1, y1, x2, y2;
/*Sets the endpoints of an object*/
{
    int l, r, b, t;
    ObjPtr var;
    real loc[2];

    l = MIN(x1, x2) - HANDLESIZE / 2;
    r = MAX(x1, x2) + HANDLESIZE / 2;
    b = MIN(y1, y2) - HANDLESIZE / 2;
    t = MAX(y1, y2) + HANDLESIZE / 2;
    Set2DIntBounds(object, l, r, b, t);

    var = NewRealArray(1, 2L);
    loc[0] = x1;
    loc[1] = y1;
    CArray2Array(var, loc);
    SetVar(object, STARTPOINT, var);

    var = NewRealArray(1, 2L);
    loc[0] = x2;
    loc[1] = y2;
    CArray2Array(var, loc);
    SetVar(object, ENDPOINT, var);
}

Bool Get2DIntBounds(object, l, r, b, t)
ObjPtr object;
int *l, *r, *b, *t;
/*Gets the 2D integer bounds of a screen object*/
{
    ObjPtr boundsArray;
    boundsArray = GetFixedArrayVar("Get2DIntBounds", object, BOUNDS, 1, 4L);
    if (boundsArray)
    {
	real temp[4];
	Array2CArray(temp, boundsArray);
	*l = temp[0];
	*r = temp[1];
	*b = temp[2];
	*t = temp[3];
	return true;
    }
    else
    {
	return false;
    }
}

Bool Set2DIntBounds(object, l, r, b, t)
ObjPtr object;
int l, r, b, t;
/*Sets the 2D integer bounds of a screen object to l, r, b, t*/
{
    ObjPtr boundsArray;
    boundsArray = NewRealArray(1, 4L);
    if (boundsArray)
    {
	real temp[4];
	temp[0] = l;
	temp[1] = r;
	temp[2] = b;
	temp[3] = t;
	CArray2Array(boundsArray, temp);
	SetVar(object, BOUNDS, boundsArray);
	return true;
    }
    else
    {
	return false;
    }
}

void ForAllObjects(obj, routine)
ObjPtr obj;
FuncTyp routine;
/*Performs routine on all selected objects in win*/
{
    ObjPtr contents;
    FuncTyp method;

    method = GetMethod(obj, FORALLOBJECTS);
    if (method)
    {
	(*method)(obj, routine);
    }
}

static ObjPtr CloseObjWindow(theInfo)
WinInfoPtr theInfo;
/*Closes obj window theInfo*/
{  
    if (theInfo -> mainMenu)
    {
	DeleteMenus(theInfo);
    }
    theInfo -> mainMenu = 0;
    return ObjTrue;
}

#ifdef GODLIKE
static ObjPtr DrawBounds(stuff)
ObjPtr stuff;
/*Draws the bounds of stuff*/
{
    ThingListPtr runner;
    ObjPtr bounds, contents;
    if (IsList(stuff))
    {
	runner = LISTOF(stuff);
	while (runner)
	{
	    DrawBounds(runner -> thing);
	    runner = runner -> next;
	}
    }
    else
    {
	contents = GetVar(stuff, CONTENTS);
	if (contents)
	{
	    DrawBounds(contents);
	}
	bounds = GetVar(stuff, BOUNDS);
	if (bounds)
	{
	    int l, r, b, t;
	    Get2DIntBounds(stuff, &l, &r, &b, &t);
	    FrameUIRect(l, r, b, t, UIWHITE);
	}
    }
}

static ObjPtr DrawSoftBounds(stuff)
ObjPtr stuff;
/*Draws the soft bounds of stuff*/
{
    ThingListPtr runner;
    ObjPtr bounds, contents;
    if (IsList(stuff))
    {
	runner = LISTOF(stuff);
	while (runner)
	{
	    DrawSoftBounds(runner -> thing);
	    runner = runner -> next;
	}
    }
    else
    {
	contents = GetVar(stuff, CONTENTS);
	if (contents)
	{
	    DrawSoftBounds(contents);
	}
	bounds = GetVar(stuff, BOUNDS);
	if (bounds)
	{
	    int l, r, b, t;
	    ObjPtr softBounds;
	    Get2DIntBounds(stuff, &l, &r, &b, &t);
	    softBounds = GetVar(stuff, INNERSOFTMARGIN);
	    if (!softBounds)
	    {
	        FrameUIRect(l + MINORBORDER, r - MINORBORDER, b + MINORBORDER, t - MINORBORDER, UIRED);
	    }
	    softBounds = GetVar(stuff, OUTERSOFTMARGIN);
	    if (!softBounds)
	    {
	        FrameUIRect(l - MINORBORDER, r + MINORBORDER, b - MINORBORDER, t + MINORBORDER, UIRED);
	    }
	}
    }
}

static void SaveSubTemplate(stuff, file)
ObjPtr stuff;
FILE *file;
/*Saves a subtemplate for stuff and its contents into file*/
{
    ThingListPtr runner;
    ObjPtr bounds, contents, name;
    if (IsList(stuff))
    {
	runner = LISTOF(stuff);
	while (runner)
	{
	    SaveSubTemplate(runner -> thing, file);
	    runner = runner -> next;
	}
    }
    else
    {
	contents = GetVar(stuff, CONTENTS);
	if (contents)
	{
	    SaveSubTemplate(contents, file);
	}
	bounds = GetVar(stuff, BOUNDS);
	if (bounds)
	{
	    int l, r, b, t;
	    Get2DIntBounds(stuff, &l, &r, &b, &t);
	    name = GetVar(stuff, NAME);
	    if (name)
	    {
		fprintf(file, "\t{\"%s\", %d, %d, %d, %d},\n",
			GetString(name), l, r, b, t);
	    }
	}
    }
}

static ObjPtr SaveTemplateReally(owner, reply)
ObjPtr owner;
char *reply;
/*Really saves a template named reply*/
{
	FILE *file;
	char fileName[200];
	ObjPtr contents;
	contents = GetVar(GetVar(owner, OWNERWINDOW), CONTENTS);

	sprintf(fileName, "%s.tpl", reply);
	file = fopen(fileName, "w");
	if (file)
	{
	    fprintf(file, "Template %sTemplate[] =\n    {\n", reply);
	    SaveSubTemplate(contents, file);
	    fprintf(file, "\t{\"\", 0, 0, 0, 0}\n");
	    fprintf(file, "    };\n\n");
	    fclose(file);
	}
}

static void DoSaveTemplate()
/*Saves a template for the current window*/
{
    if (selWinInfo)
    {
	char *s;
	WinInfoPtr curWindow, alertWindow;

	/*Figure out the name for the template*/
	strcpy(tempStr, selWinInfo -> winTitle);
	s = tempStr;

	while (*s)
	{
	    if (*s == ' ') *s = '_';
	    ++s;
	}
	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Enter template name:", (FuncTyp) SaveTemplateReally, tempStr);
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static ObjPtr AddObjToWindow(object, dialog)
ObjPtr object;
ObjPtr dialog;
{
    ObjPtr contents, panel;
    contents = GetVar(GetVar(dialog, OWNERWINDOW), CONTENTS);

    panel = LISTOF(contents) -> thing;
    if (panel)
    {
	contents = GetVar(panel, CONTENTS);
	PrefixList(contents, object);
	SetVar(object, PARENT, panel);
	ImInvalid(object);
    }
}

static ObjPtr NewScreenSlider(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new slider*/
{
    ObjPtr object;

    object = NewSlider(10, 10 + SLIDERWIDTH, 10, 100, PLAIN, name);
    AddObjToWindow(object, dialog);
}

static ObjPtr NewScreenScaleSlider(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new slider*/
{
    ObjPtr object;

    object = NewSlider(10, 10 + SLIDERWIDTH, 10, 100, SCALE, name);
    AddObjToWindow(object, dialog);
}

static ObjPtr NewScreenTitleBox(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new title box*/
{
    ObjPtr object;

    object = NewTitleBox(10, 100, 10, 70, name);
    AddObjToWindow(object, dialog);
}

static ObjPtr NewScreenRadioButton(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new radio button*/
{
    ObjPtr object;

    object = NewRadioButton(10, 100, 10, 10 + CHECKBOXHEIGHT, name);
    AddObjToWindow(object, dialog);
}

static ObjPtr NewScreenPlainButton(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new plain button*/
{
    ObjPtr object;

    object = NewButton(10, 100, 10, 10 + BUTTONHEIGHT, name);
    AddObjToWindow(object, dialog);
}

static ObjPtr NewScreenShortButton(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new plain button*/
{
    ObjPtr object;

    object = NewButton(10, 100, 10, 10 + SHORTBUTTONHEIGHT, name);
    AddObjToWindow(object, dialog);
}

static ObjPtr NewScreenCheckBox(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new check box*/
{
    ObjPtr object;

    object = NewCheckBox(10, 100, 10, 10 + CHECKBOXHEIGHT, name, false);
    AddObjToWindow(object, dialog);
}

static ObjPtr NewScreenTextBox(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new text box*/
{
    ObjPtr object;

    object = NewTextBox(10, 100, 10, 10 + TEXTBOXHEIGHT, 0, name, name);
    AddObjToWindow(object, dialog);
}

static ObjPtr NewScreenEditableTextBox(dialog, name)
ObjPtr dialog;
char *name;
/*Makes a new text box*/
{
    ObjPtr object;

    object = NewTextBox(10, 100, 10, 10 + EDITBOXHEIGHT, EDITABLE + WITH_PIT + ONE_LINE, name, name);
    AddObjToWindow(object, dialog);
}

static void DoNewScreenSlider()
/*Makes a new slider in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenSlider, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static void DoNewScreenScaleSlider()
/*Makes a new scale slider in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenScaleSlider, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static void DoNewScreenTitleBox()
/*Makes a new title box in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenTitleBox, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static void DoNewScreenRadioButton()
/*Makes a new radio button in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenRadioButton, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static void DoNewScreenPlainButton()
/*Makes a new plain button in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenPlainButton, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static void DoNewScreenShortButton()
/*Makes a new plain button in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenShortButton, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static void DoNewScreenCheckBox()
/*Makes a new check box in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenCheckBox, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static void DoNewScreenTextBox()
/*Makes a new text box in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenTextBox, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

static void DoNewScreenEditableTextBox()
/*Makes a new text box in the current window*/
{
    if (selWinInfo)
    {
	WinInfoPtr curWindow, alertWindow;

	curWindow = selWinInfo;
	alertWindow = AskUser((WinInfoPtr) selWinInfo,
		"Please name the new object:", (FuncTyp) NewScreenEditableTextBox, "");
	SetVar((ObjPtr) alertWindow, OWNERWINDOW, (ObjPtr) curWindow);
    }
}

#endif

static ObjPtr DrawObjWindow(theInfo)
WinInfoPtr theInfo;
/*Draws the obj window theInfo*/
{
#ifdef GRAPHICS
    ObjPtr contents;

    /*Draw the objects within the window*/
    contents = GetVar((ObjPtr) theInfo, CONTENTS);
    if (contents)
    {
	if (IsList(contents))
	{
	    DrawList(contents);
	}
    }
#ifdef GODLIKE
    if (GetPredicate((ObjPtr) theInfo, SHOWBOUNDS))
    {
	DrawBounds(contents);
    }
#endif
#endif
    return ObjTrue;
}

#ifdef PROTO
ObjPtr BoundsInvalid(ObjPtr object, unsigned long changeCount)
#else
ObjPtr BoundsInvalid(object, changeCount)
ObjPtr object;
unsigned long changeCount;
#endif
/*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

  In order for an object to be declared invalid, its changed bounds
  must have been changed more recently than changeCount
*/
{
    ObjPtr contents;
    ObjPtr myBounds;
    FuncTyp method;

    method = GetMethod(object, BOUNDSINVALID);
    if (method)
    {
	return (*method)(object, changeCount);
    }

    MakeVar(object, APPEARANCE);

    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)
	{
	    return myBounds;
	}
	else
	{
	    return ObjTrue;
	}
    }

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

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

	firstTime = true;
	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);
		    }
		    else
		    {
			Array2CArray(testElements, test);
			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)
	{
	    if (doubleNoBounds)
	    {
		return ObjTrue;
	    }
	    else
	    {
		ObjPtr retVal;
		retVal = NewRealArray(1, 4L);
		CArray2Array(retVal, boundsElements);
		return retVal;
	    }
	}
    }

    return NULLOBJ;
}

ObjPtr MakeObjWindowDrawn(curWindow)
WinInfoPtr curWindow;
/*Method to make an obj window drawn.  Returns true iff drawing done*/
{
    ObjPtr boundsInvalid;
    int l, r, b, t;
    int whichBuffer;

    if (IsDoubleBuf(curWindow))
    {
	/*Double buffer, determine which buffer we're drawing*/
	ObjPtr var;

	var = GetVar((ObjPtr) curWindow, WHICHBUFFER);
	if (var)
	{
	    whichBuffer = GetInt(var);
	}
	else
	{
	    whichBuffer = 0;
	}

	/*Set to other buffer*/
	SetVar((ObjPtr) curWindow, WHICHBUFFER, NewInt(whichBuffer ? 0 : 1));
    }
    else
    {
	/*Single buffer, just one buffer*/
	whichBuffer = 0;
    }

    boundsInvalid = BoundsInvalid((ObjPtr) curWindow,
			GetVarChangeCount((ObjPtr) curWindow,
				whichBuffer ? BUFFER1 : BUFFER0));
    if (boundsInvalid)
    {
	SetVar((ObjPtr) curWindow, whichBuffer ? BUFFER1 : BUFFER0, ObjTrue);

	SetDrawingWindow(curWindow);

#ifndef DONTCLIP
	if ((IsArray(boundsInvalid) && !GetPredicate((ObjPtr) curWindow, SHOWBOUNDS)))
	{
	    real elements[4];
	    Array2CArray(elements, boundsInvalid);
	    SetClipRect((int) elements[0], (int) elements[1], (int) elements[2], (int) elements[3]);
	    DrawWindowInfo(curWindow);
	    RestoreClipRect();
	}
	else
#endif
	{
	    DrawWindowInfo(curWindow);
	}

	RestoreDrawingWindow();
	return ObjTrue;
    }
    return ObjFalse;
}

ObjPtr PressObjWindow(theInfo, x, y, flags)
WinInfoPtr theInfo;
int x, y;
long flags;
/*Does a press in object window theInfo*/
{
    ObjPtr contents, retVal = ObjFalse;
#ifdef INTERACTIVE

    SetDrawingWindow(theInfo);

    /*Press in the objects within the window*/
    contents = GetVar((ObjPtr) theInfo, CONTENTS);
    if (contents)
    {
	if (IsList(contents))
	{
	    retVal = PressObject(contents, x, y, flags);
	}
    }
    RestoreDrawingWindow();
#endif
    return retVal;
}

static ObjPtr DropObjWindow(window, object, x, y)
ObjPtr window, object;
int x, y;
/*Does a drop in object window theInfo*/
{
    ObjPtr contents, retVal = ObjFalse;

    /*Drop in the objects within the window*/
    contents = GetVar(window, CONTENTS);
    if (contents)
    {
	if (IsList(contents))
	{
	    retVal = DropList(contents, object, x, y);
	}
    }
    return retVal;
}

static ObjPtr KeyDownObjWindow(theInfo, theKey, flags)
WinInfoPtr theInfo;
int theKey;
long flags;
/*Does a key down in object window theInfo*/
{
    ObjPtr contents;

    /*Press in the objects within the window*/
    contents = GetVar((ObjPtr) theInfo, CONTENTS);
    if (contents)
    {
	if (IsList(contents))
	{
	    KeyDownList(contents, theKey, flags);
	}
    }
    return ObjTrue;
}

void ReshapeObject(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.*/
{
    FuncTyp method;
    method = GetMethod(object, RESHAPE);
    if (method)
    {
	(*method)(object, ol, or, ob, ot, left, right, bottom, top);
    }
    else
    {
	ObjPtr boundsArray, locArray;
	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*/
	real wr, hr;			/*Width and height ratios*/
	Bool isIcon = false;		/*Is an icon?*/

	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)
	{
	    /*It might be an icon*/
	    locArray = GetVar(object, ICONLOC);
	    if (!locArray && !IsRealArray(locArray) || RANK(locArray) != 1 ||
		DIMS(locArray)[0] != 2)
	    {
		return;
	    }
	    isIcon = true;
	    Array2CArray(bounds, locArray);
	    bounds[3] = bounds[2] = bounds[1];
	    bounds[1] = bounds[0];
	}
	else
	{
	    Array2CArray(bounds, boundsArray);
	}
	oldBounds[0] = bounds[0];
	oldBounds[1] = bounds[1];
	oldBounds[2] = bounds[2];
	oldBounds[3] = bounds[3];

	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*/
	if (isIcon)
	{
	    locArray = NewRealArray(1, 2L);
	    ((real *) ELEMENTS(locArray))[0] = bounds[0];
	    ((real *) ELEMENTS(locArray))[1] = bounds[2];
	    SetVar(object, ICONLOC, locArray);
	}
	else
	{
	    boundsArray = NewRealArray(1, 4L);
	    CArray2Array(boundsArray, bounds);
	    SetVar(object, BOUNDS, boundsArray);
	}

	/*If there are some contents to this, do the reshape recursively*/
	contents = GetVar(object, CONTENTS);
	if (contents && IsList(contents))
	{
	    ReshapeList(LISTOF(contents),
		    0, (int) (oldBounds[1] - oldBounds[0]),
		    0, (int) (oldBounds[3] - oldBounds[2]),		0, (int) (bounds[1] - bounds[0]),
		    0, (int) (bounds[3] - bounds[2]));
	}
    }
}

void ReshapeList(contents, ol, or, ob, ot, left, right, bottom, top)
ThingListPtr contents;
int ol, or, ob, ot;
int left, right, bottom, top;
/*Reshapes a list of objects using ReshapeObject*/
{
    while (contents)
    {
	ObjPtr object;

	object = contents -> thing;
	ReshapeObject(object, ol, or, ob, ot, left, right, bottom, top);
	    
	contents = contents -> next;
    }
}

ObjPtr ReshapeObjWindow(window, ol, or, ob, ot, nl, nr, nb, nt)
ObjPtr window;
int ol, or, ob, ot;
int nl, nr, nb, nt;
/*Reshapes object window given that ol, or, ob, and ot was the old
  viewport.  Does not redraw anything.*/
{
    ObjPtr contents;

    contents = GetVar(window, CONTENTS);
    if (contents && IsList(contents))
    {
	ReshapeList(LISTOF(contents),
		0, or - ol, 0, ot - ob,
		0, nr - nl, 0, nt - nb);
    }   
    return ObjTrue;
}

void ImInvalid(object)
ObjPtr object;
/*Alerts the window system that an object is no longer valid*/
{
    FuncTyp method;

    method = GetMethod(object, IMINVALID);
    if (method)
    {
	(*method)(object);
    }
    else
    {
	ObjPtr bounds;

	bounds = GetVar(object, BOUNDS);
	if (bounds && IsArray(bounds) && RANK(bounds) == 1 &&
	    DIMS(bounds)[0] == 4)
	{
	    real elements[4];
	    Array2CArray(elements, bounds);
	    /*Kludge because most objects draw one bigger*/
	    elements[1] += 1.0;
	    elements[3] += 1.0;
	    bounds = NewRealArray(1, 4L);
	    CArray2Array(bounds, elements);
	    SetVar(object, CHANGEDBOUNDS, bounds);
	}
	else
	{
	    SetVar(object, CHANGEDBOUNDS, ObjTrue);
	}
    }
}

void ExpandInvalid(object)
ObjPtr object;
/*Expands the invalid bounds*/
{
    ObjPtr bounds, changedBounds;
    real biggerBounds[4];
    real *elements;

    changedBounds = GetVar(object, CHANGEDBOUNDS);
    if (changedBounds && IsArray(changedBounds) && RANK(changedBounds) == 1 &&
	DIMS(changedBounds)[0] == 4)
    {
	/*Have to expand these bounds*/
	Array2CArray(biggerBounds, changedBounds);

	bounds = GetVar(object, BOUNDS);
	if (bounds && IsArray(bounds) && RANK(bounds) == 1 &&
	    DIMS(bounds)[0] == 4)
	{
	    elements = ELEMENTS(bounds);

	    biggerBounds[0] = MIN(elements[0], biggerBounds[0]);
	    biggerBounds[1] = MAX(elements[1] + 1.0, biggerBounds[1]);
	    biggerBounds[2] = MIN(elements[2], biggerBounds[2]);
	    biggerBounds[3] = MAX(elements[3] + 1.0, biggerBounds[3]);

	    changedBounds = NewRealArray(1, 4L);
	    CArray2Array(changedBounds, biggerBounds);
	    SetVar(object, CHANGEDBOUNDS, changedBounds);
	}
	else
	{
	    SetVar(object, CHANGEDBOUNDS, ObjTrue);
	}
	return;
    }

    /*Fall through, just do ImInvalid*/
    ImInvalid(object);
}

#ifdef PROTO
void ImInvalidBounds(ObjPtr object, int l, int r, int b, int t)
#else
void ImInvalidBounds(object, l, r, b, t)
ObjPtr object;
int l, r, b, t;
#endif
/*Alerts the window system that an object is no longer valid
  within bounds*/
{
    ObjPtr bounds;
    real *elements;

    bounds = NewRealArray(1, 4L);
    elements = ELEMENTS(bounds);

    elements[0] = l;
    elements[1] = r;
    elements[2] = b;
    elements[3] = t;

    SetVar(object, CHANGEDBOUNDS, bounds);
}

#ifdef PROTO
void PauseDrawing(Bool whether)
#else
void PauseDrawing(whether)
Bool whether;
#endif
/*Pauses drawing for a while*/
{
    if (whether)
    {
	++pauseDrawing;
    }
    else
    {
	--pauseDrawing;
    }
}

#ifdef PROTO
void DrawSkeleton(Bool whether)
#else
void DrawSkeleton(whether)
Bool whether;
#endif
/*Does subsequent DrawMe's in skeleton mode or not*/
{
    if (!whether)
    {
	EraseAll();
    }
    OverDraw(whether);
}

void DrawMe(object)
ObjPtr object;
/*Redraws object in the current window undergoing interaction*/
{
#ifdef GRAPHICS
	ExpandInvalid(object);
	if (overDraw)
	{
	    color(0);
	    clear();
	    DrawObject(object);
	}
	else
	{
	    if (!pauseDrawing)
	    {
		drawMouse = true;
	    }
	}
#endif
}

#ifdef PROTO
void DrawMeInBounds(ObjPtr object, int l, int r, int b, int t)
#else
void DrawMeInBounds(object, l, r, b, t)
int l, r, b, t;
ObjPtr object;
#endif
/*Redraws object in the current window undergoing interaction*/
{
#ifdef GRAPHICS
	ImInvalidBounds(object, l, r, b, t);
	if (overDraw)
	{
	    color(0);
	    clear();
	    DrawObject(object);
	}
	else
	{
	    if (!pauseDrawing)
	    {
		drawMouse = true;
	    }
	}
#endif
}


void InitPickUp()
/*Initializes picking up objects*/
{
    if (dragBuffer)
    {
	DeleteThing(dragBuffer);
	dragBuffer = NULLOBJ;
    }
}

ObjPtr PickUpObject(object)
ObjPtr object;
/*Picks up an object*/
{
    object = ObjectWhichRepresents(selWinInfo, object);
    if (object && IsIcon(object))
    {
	char s[256];
	if (!dragBuffer)
	{
	    iconXOff = iconYOff = 0;
	    dragBuffer = NewList();
	    AddToReferenceList(dragBuffer);
	    if (!runningScript)
	    {
		MySetCursor(ICONCURSOR);
	    }
	}
	s[0] = ' ';
	MakeObjectName(s + 1, object);
	Log(s);
	PostfixList(dragBuffer, object);
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

void DoPickUpObjects()
/*Picks up the objects in the icon in the selected window*/
{
    DoObjFunction(OF_PICK_UP);
}

void ToggleShowBounds()
/*Toggles the show bounds in the current window*/
{
    if (selWinInfo)
    {
	Bool lastState;
	lastState = GetPredicate((ObjPtr) selWinInfo, SHOWBOUNDS);
	SetVar((ObjPtr) selWinInfo, SHOWBOUNDS,
			lastState ? ObjFalse : ObjTrue);
#ifdef GRAPHICS
#ifdef WINDOWS4D
	if (lastState)
	{
	    int l, r, b, t;

	    GetWindowBounds(&l, &r, &b, &t);
	    minsize(r - l + 1, t - b + 1);
	    maxsize(r - l + 1, t - b + 1);
	    winconstraints();
	}
	else
	{
	    minsize(40, 30);
	    maxsize(1280, 1024);
	    winconstraints();
	}
#endif
#endif
	ImInvalid((ObjPtr) selWinInfo);
    }
}

WinInfoPtr NewObjWindow(superClass, title, flags, minWidth, minHeight, maxWidth, maxHeight)
ObjPtr superClass;
char *title;
long flags;
int minWidth, minHeight, maxWidth, maxHeight;
/*Creates a new object window with title title and flags flags.  superClass is
  the class of the window, if NULLOBJ objWindowClass will be used*/
{
    WinInfoPtr retVal;
    WindowID theWindow;

    /*Edit flags*/
    if (!hasRGB && (flags & WINRGB))
    {
	flags &= ~WINRGB;
    }
    if (!hasCmap) flags |= WINRGB;

    if (showWindows)
    {
	theWindow = NewOpenedWindow(title, minWidth, minHeight, maxWidth, maxHeight,flags);
    }
    else
    {
	theWindow = 0;
    }

    retVal = NewWinInfo(superClass ? superClass : objWindowClass, theWindow, 
		flags, title,  minWidth, minHeight, maxWidth, maxHeight);
    SetVar((ObjPtr) retVal, CONTENTS, NewList());	/*Contents*/

#ifdef SELNEWWINDOWS
    inputWindow = retVal;
#endif
#ifdef GODLIKE
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "Toggle edit template", ToggleShowBounds);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "Save Template", DoSaveTemplate);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Slider", DoNewScreenSlider);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Scale Slider", DoNewScreenScaleSlider);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Title Box", DoNewScreenTitleBox);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Radio Button", DoNewScreenRadioButton);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Plain Button", DoNewScreenPlainButton);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Short Button", DoNewScreenShortButton);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Check Box", DoNewScreenCheckBox);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Text Box", DoNewScreenTextBox);
	DefineMenuItem((ObjPtr) retVal, DEUSMENU, "New Editable Text Box", DoNewScreenEditableTextBox);
#endif

    return retVal;
}

static ObjPtr FindObjectObjClass(object, name)
ObjPtr object;
char *name;
/*Searches an object and its contents for an object with name*/
{
    ObjPtr retVal = NULLOBJ;
    ObjPtr objName, contents;

    /*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);
	}
    }

    return retVal;
}

static ObjPtr FindObjectObjWindow(object, name)
ObjPtr object;
char *name;
/*Searches an object window and its subwindows for object name*/
{
    ObjPtr subWindows;
    ObjPtr retVal;
    ObjPtr foundObjects;

    retVal = NULLOBJ;

    foundObjects = FindObjectObjClass(object, name);
    if (foundObjects)
    {
	if (!retVal)
	{
	    retVal = NewList();
	}
	AppendList(retVal, foundObjects);
    }

    subWindows = GetVar(object, SUBWINDOWS);
    if (subWindows)
    {
	foundObjects = FindNamedObject(subWindows, name);
	if (foundObjects)
	{
	    if (!retVal)
	    {
		retVal = NewList();
	    }
	    AppendList(retVal, foundObjects);
	}
    }
    return retVal;
}

static ObjPtr ForAllObjectsObjClass(object, routine)
ObjPtr object;
FuncTyp routine;
/*Does routine on all objects within object*/
{
    ObjPtr contents;

    (*routine)(object);

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

    return ObjTrue;
}

extern ObjPtr objClass;

ObjPtr ShowObjWindowFrame(curWindow)
WinInfoPtr curWindow;
/*Shows the window frame*/
{
#ifdef GRAPHICS
#ifdef WINDOWS4D
#ifndef NOHIDEFRAME
    if (!(selWinInfo -> id))
    {
	return ObjFalse;
    }

    SelWindow(curWindow);
    if (logging)
    {
	Log("show frame\n");
    }
    wintitle(selWinInfo -> winTitle);
    minsize(selWinInfo -> minWidth, selWinInfo -> minHeight);
    maxsize(selWinInfo -> maxWidth, selWinInfo -> maxHeight);
    winconstraints();
    selWinInfo -> flags &= ~WINNOFRAME;
    DeleteMenus(selWinInfo);
#endif
#endif
#endif
    return ObjTrue;
}

ObjPtr HideObjWindowFrame(curWindow)
WinInfoPtr curWindow;
/*Hides the window frame*/
{
#ifdef WINDOWS4D
#ifdef GRAPHICS
#ifndef NOHIDEFRAME
    if (logging)
    {
	Log("hide frame\n");
    }

    SelWindow(curWindow);
    if (!(selWinInfo -> id))
    {
	return ObjFalse;
    }
    noborder();
    minsize(selWinInfo -> minWidth, selWinInfo -> minHeight);
    maxsize(selWinInfo -> maxWidth, selWinInfo -> maxHeight);
    winconstraints();
    selWinInfo -> flags |= WINNOFRAME;
    DeleteMenus(selWinInfo);
#endif
#endif
#endif
    return ObjTrue;
}

void InitObjWindows()
/*Initializes the obj window system*/
{
    objWindowClass = NewObject(windowClass, 0);
    AddToReferenceList(objWindowClass);
    SetMethod(objWindowClass, CLOSE, CloseObjWindow);
    SetMethod(objWindowClass, DRAW, DrawObjWindow);
    SetMethod(objWindowClass, PRESS, PressObjWindow);
    SetMethod(objWindowClass, DROPOBJECTS, DropObjWindow);
    SetMethod(objWindowClass, KEYDOWN, KeyDownObjWindow);
    SetMethod(objWindowClass, RESHAPE, ReshapeObjWindow);
    SetMethod(objWindowClass, MAKEDRAWN, MakeObjWindowDrawn);
    SetMethod(objWindowClass, SHOWFRAME, ShowObjWindowFrame);
    SetMethod(objWindowClass, HIDEFRAME, HideObjWindowFrame);
    SetMethod(objWindowClass, FINDOBJECT, FindObjectObjWindow);

    /*KLUDGE*/
    SetMethod(objClass, FINDOBJECT, FindObjectObjClass);
    SetMethod(objClass, FORALLOBJECTS, ForAllObjectsObjClass);
}

static Bool repped;
static ObjPtr testObject;
static ObjPtr objectWhichRepresents;

static ObjPtr CheckRepObj(object)
ObjPtr object;
/*Checks if an object matches testObject*/
{
    if (IsIcon(object) && (GetVar(object, REPOBJ) == testObject))
    {
	objectWhichRepresents = object;
	repped = true;
	return ObjTrue;
    }
    else if ((object == testObject) && !repped)
    {
	objectWhichRepresents = object;
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

ObjPtr ObjectWhichRepresents(window, object)
WinInfoPtr window;
ObjPtr object;
/*Returns the object in window that represents object, or NULLOBJ*/
{
    if (object == (ObjPtr) window) return (ObjPtr) window;

    repped = false;
    objectWhichRepresents = NULLOBJ;
    testObject = object;

    if (object)
    {
	ForAllObjects((ObjPtr) window, CheckRepObj);
    }
    else
    {
	return NULLOBJ;
    }
    if (!objectWhichRepresents)
    {
	ObjPtr var;
	var = GetVar(object, NAME);
	if (var)
	{
	    ObjPtr list;
	    list = FindNamedObject((ObjPtr) window, GetString(var));
	    if (list && LISTOF(list))
	    {
		objectWhichRepresents = LISTOF(list) -> thing;
	    }
	}
    }
    return objectWhichRepresents;
}

ObjPtr DeleteObject(object)
ObjPtr object;
/*Deletes an object from the current window.*/
{
    ObjPtr deleteObject;

    if (!selWinInfo || !object) return ObjFalse;
    deleteObject = ObjectWhichRepresents(selWinInfo, object);
    Select(object, false);
    if (deleteObject)
    {
	FuncTyp method;
	object = deleteObject;
	method = GetMethod(object, DELETEICON);

	if (method)
	{
	    if (IsTrue((*method)(object)))
	    {
		ObjPtr contentsList;
		contentsList = GetVar(object, PARENT);
	 	if (!contentsList || !IsList(contentsList))
		{
		    contentsList = GetVar(contentsList, CONTENTS);
		}
		if (contentsList && IsList(contentsList))
		{
		    ObjPtr parent;
		    parent = GetVar(contentsList, PARENT);
		    if (parent)
		    {
			ImInvalid(parent);
		    }
		    DeleteFromList(contentsList, object);
		    return ObjTrue;
		}
		else
		{
		    ReportError("DeleteObject","Internal error: cannot find contents list");
		}
	    }
	}
    }
    return ObjFalse;
}

void DoDelete()
/*Deletes objects*/
{
    ObjPtr corral;
    Bool didit = false;

    if (logging)
    {
	InhibitLogging(true);
    }
    if (selWinInfo)
    {
	ForAllSelectedObjects(DeleteObject);
    }
    if (logging)
    {
	InhibitLogging(false);
	if (didit)
	{
	    Log("delete\n");
	}
    }
}

void KillObjWindows()
/*Kills the obj window system*/
{
    DeleteThing(objWindowClass);
}

ObjPtr FindNamedObject(object, name)
ObjPtr object;
char *name;
/*Finds object with name within object.  Returns a list of matching objects*/
{
    FuncTyp method;
    ObjPtr retVal;

    method = GetMethod(object, FINDOBJECT);
    if (method)
    {
	retVal = (*method)(object, name);
	{
	    ThingListPtr runner;
	    if (retVal)
	    {
		ObjPtr test, temp;
		ObjPtr repObj = NULLOBJ;
		Bool hasExact, hasInexact;
		ObjPtr exactList;

		/*See if there are both exact and inexact matches*/
		hasExact = false;
		hasInexact = false;
		runner = LISTOF(retVal);
		exactList = NULLOBJ;
		while (runner)
		{
		    test = runner -> thing;
		    temp = GetVar(test, NAME);
		    if (0 == strcmp2(GetString(temp), name))
		    {
			if (!hasExact)
			{
			    exactList = NewList();
			}
			PrefixList(exactList, test);
			hasExact = true;
		    }
		    else
		    {
			hasInexact = true;
		    }
		    runner = runner -> next;
		}
		if (hasExact && hasInexact)
		{
		    /*Only use the exact matches*/
		    retVal = exactList;
		}

		/*See if multiple objects are really the same object*/
		runner = LISTOF(retVal);
		while (runner)
		{
		    test = runner -> thing;
		    if (IsIcon(test) && (temp = GetVar(test, REPOBJ)))
		    {
			test = temp;
		    }
		    
		    if (repObj)
		    {
			/*There's already a repobj*/
			if (test != repObj) break;
		    }
		    else
		    {
			/*There isn't a repobj yet*/
			repObj = test;
		    }
		    runner = runner -> next;
		}
		if (!runner)
		{
		    /*They are all the same object*/
		    retVal = NewList();
		    PrefixList(retVal, repObj);
		}
	    }
	    return retVal;
	}
    }
    else
    {
	return NULLOBJ;
    }
}

void MakeMeCurrent(object)
ObjPtr object;
/*Makes object the current object in the local window*/
{
    if (runningScript && !scriptSelectP)
    {
	return;
    }

    if (selWinInfo)
    {
	ObjPtr oldCurrent;

	oldCurrent = GetVar((ObjPtr) selWinInfo, CURRENT);
        if (oldCurrent == object) return;
	FlushKeystrokes();
	SetVar((ObjPtr) selWinInfo, CURRENT, object);
	if (oldCurrent)
	{
	    ImInvalid(oldCurrent);
	    DeferMessage(oldCurrent, YOURENOTCURRENT);
	}
	if (object) ImInvalid(object);
    }
}

Bool AmICurrent(object)
ObjPtr object;
/*Returns true iff object is the current object in the current window*/
{
    if (selWinInfo)
    {
	return GetVar((ObjPtr) selWinInfo, CURRENT) == object ? true : false;
    }
    else
    {
	return false;
    }
}
