/*ScianDrawings.c
  Little drawing elements to put on space panels, etc.
  Eric Pepke
  March 3, 1992
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianLists.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianColors.h"
#include "ScianIDs.h"
#include "ScianEvents.h"
#include "ScianArrays.h"
#include "ScianErrors.h"
#include "ScianWindows.h"
#include "ScianDraw.h"
#include "ScianObjWindows.h"
#include "ScianVisWindows.h"
#include "ScianDialogs.h"
#include "ScianSliders.h"
#include "ScianTextBoxes.h"
#include "ScianTitleBoxes.h"
#include "ScianMethods.h"
#include "ScianStyle.h"
#include "ScianDrawings.h"
#include "ScianScripts.h"
#include "ScianPick.h"
#include "ScianTemplates.h"
#include "ScianTemplateHelper.h"
#include "ScianSymbols.h"

ObjPtr drawingClass, rectangleClass, lineClass;

int nAnnot = 1;
int nRect = 1;
int nLine = 1;
int nTimeReadout = 1;

ObjPtr DeleteSpacePanelDrawing(drawing)
ObjPtr drawing;
/*Additional stuff to do when deleting a drawing from a space panel*/
{
    return ObjTrue;
}

ObjPtr DuplicateDrawing(object)
ObjPtr object;
/*Duplicates a drawing*/
{
    ObjPtr parent, contents;
    FuncTyp method;

    parent = GetVar(object, PARENT);
    if (!parent)
    {
	ReportError("DuplicateDrawing", "Cannot find parent of drawing");
	return ObjFalse;
    }

    contents = GetListVar("DuplicateDrawing", parent, CONTENTS);
    if (!contents)
    {
	return ObjFalse;
    }

    method = GetMethodSurely("CloneObject", object, CLONE);
    if (!method)
    {
	return ObjFalse;
    }

    object = (*method)(object);
    if (object)
    {
	ObjPtr var;
	real bounds[4], loc[2];

	/*Change bounds, if any*/
	var = GetVar(object, BOUNDS);
	if (var && IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 4)
	{
	    Array2CArray(bounds, var);
	    bounds[0] += DUPOFFSET;
	    bounds[1] += DUPOFFSET;
	    bounds[2] -= DUPOFFSET;
	    bounds[3] -= DUPOFFSET;
	    var = NewRealArray(1, 4L);
	    CArray2Array(var, bounds);
	    SetVar(object, BOUNDS, var);
	}

	/*Change startpoint, if any*/
	var = GetVar(object, STARTPOINT);
	if (var && IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 2)
	{
	    Array2CArray(loc, var);
	    loc[0] += DUPOFFSET;
	    loc[1] -= DUPOFFSET;
	    var = NewRealArray(1, 2L);
	    CArray2Array(var, loc);
	    SetVar(object, STARTPOINT, var);
	}

	/*Change endpoint, if any*/
	var = GetVar(object, ENDPOINT);
	if (var && IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 2)
	{
	    Array2CArray(loc, var);
	    loc[0] += DUPOFFSET;
	    loc[1] -= DUPOFFSET;
	    var = NewRealArray(1, 2L);
	    CArray2Array(var, loc);
	    SetVar(object, ENDPOINT, var);
	}

	/*Unselect it, kludge*/
	SetVar(object, SELECTED, ObjFalse);

	PrefixList(contents, object);
	SetVar(object, PARENT, parent);
	ImInvalid(object);
    }
    return ObjTrue;
}

static ObjPtr SelectDrawing(object, selectp)
ObjPtr object;
Bool selectp;
/*Selects a drawing*/
{
    Bool alreadySelected;

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

    ImInvalid(object);

    return ObjTrue;
}

#ifdef PROTO
ObjPtr NewRectangle(int l, int r, int b, int t, char *name)
#else
ObjPtr NewRectangle(l, r, b, t, name)
int l; int r; int b; int t; char *name;
#endif
{
    ObjPtr retVal;

    retVal = NewObject(rectangleClass, 0L);
    Set2DIntBounds(retVal, l, r, b, t);
    SetVar(retVal, NAME, NewString(name));
    SetMethod(retVal, CLONE, CloneRectangle);

    return retVal;
}

#ifdef PROTO
ObjPtr NewLine(int x1, int y1, int x2, int y2, char *name)
#else
ObjPtr NewLine(x1, y1, x2, y2, name)
int x1; int y1; int x2; int y2; char *name;
#endif
{
    ObjPtr retVal;
    int l, r, b, t;
    real loc[2];
    ObjPtr var;

    l = MIN(x1, x2) - HANDLESIZE / 2;
    r = MAX(x1, x2) + HANDLESIZE / 2;
    b = MIN(y1, y2) - HANDLESIZE / 2;
    t = MAX(y1, y2) + HANDLESIZE / 2;
    retVal = NewObject(lineClass, 0L);
    Set2DIntBounds(retVal, l, r, b, t);
    SetVar(retVal, NAME, NewString(name));
    SetMethod(retVal, CLONE, CloneLine);

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

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

    return retVal;
}

static ObjPtr PressRectangle(rectangle, x, y, flags)
ObjPtr rectangle;
int x, y;
long flags;
{
#ifdef INTERACTIVE
    ObjPtr var, retVal = ObjFalse;
    int left, right, bottom, top, hCent, vCent;
    Bool ml, mr, mb, mt;

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

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(rectangle);
	    return ObjTrue;
	}

	/* return if not active */
	if (!GetPredicate(rectangle, ACTIVATED)) return ObjFalse;

	hCent = (left + right)/2;
	vCent = (bottom + top)/2;

	MakeMeCurrent(rectangle);

	ml = mr = mb = mt = false;

	if (x < left + HANDLESIZE) /* on left side */
	{
	    if (y > top - HANDLESIZE) /* top-left handle */
		mt = ml = true;
	    else if (y < bottom + HANDLESIZE) /* bottom-left handle */
		mb = ml = true;
	    else if (y > vCent - HANDLESIZE/2 && y < vCent + HANDLESIZE/2)
		ml = true; /* bottom middle handle */
	    else ml = mr = mb = mt = true; /* in frame */
	}
	else if (x > right - HANDLESIZE) /* on right side */
	{
	    if (y > top - HANDLESIZE) /* top-right handle */
		mt = mr = true;
	    else if (y < bottom + HANDLESIZE) /* bottom-right handle */
		mb = mr = true;
	    else if (y > vCent - HANDLESIZE/2 && y < vCent + HANDLESIZE/2)
		mr = true;
	    else ml = mr = mb = mt = true; /* in frame */
	}
	else if (y < bottom + HANDLESIZE) /* on bottom */
	{
	    /* already handled (heh heh) corners */
	    if (x > hCent - HANDLESIZE/2 && x < hCent + HANDLESIZE/2)
		mb = true; /* bottom middle handle */
	    else ml = mr = mb = mt = true; /* in frame */
	}
	else if (y > top - HANDLESIZE) /* on top */
	{
	    /* already handled (heh heh) corners */
	    if (x > hCent - HANDLESIZE/2 && x < hCent + HANDLESIZE/2)
		mt = true; /* middle top handle */
	    else ml = mr = mb = mt = true; /* in frame */
	}

	if (mr || ml || mb || mt || GetVar(rectangle, BACKGROUND))
	{
	    if (!(flags & F_EXTEND) && !IsSelected(rectangle))
	    {
		/*It's a new selection and not already selected.  Deselect the
		  rest*/
		DeselectAll();
	    }

	    if ((flags & F_EXTEND) && IsSelected(rectangle))
	    {
		/*Deselect*/
		Select(rectangle, false);
		return ObjTrue;
	    }
	    else if (!IsSelected(rectangle))
	    {
		/*Must select it*/
		
		Select(rectangle, true);
		DrawMe(rectangle);
		UpdateDrawing();
		retVal = ObjTrue;
	    }
	}

	if (mr || ml || mb || mt) /* drag the incredibly fancy frame around */
	{
	    /* I am greatly indebted to my friend and colleague, Eric Pepke,
	       for the following code. Any errors or obfuscations are his. 
	       Since I stole this back from Jim, the errors are now his.*/
	    int initX = x, initY = y;
	    int mX, mY;
	    int newLeft, newRight, newBottom, newTop;
	    int oldNewLeft, oldNewRight, oldNewBottom, oldNewTop;

	    SaveForUndo(rectangle);

	    newLeft = oldNewLeft = left;
	    newRight = oldNewRight = right;
	    newBottom = oldNewBottom = bottom;
	    newTop = oldNewTop = top;

	    while (Mouse(&mX, &mY) && (mX == initX) && (mY == initY));

	    DrawSkeleton(true);
	    while (Mouse(&mX, &mY))
	    {
		if (ml) newLeft = left + mX - initX;
		if (mr) newRight = right + mX - initX;
		if (mb) newBottom = bottom + mY - initY;
		if (mt) newTop = top + mY - initY;

		if (flags & F_SHIFTDOWN)
		{
		    /*Grid drag*/
		    if (ml && mr && mb && mt)
		    {
			/*Special case--whole object gridded
				Only grid top left*/
			int width, height;
			width = newRight - newLeft;
			height = newTop - newBottom;
			newLeft = GRIDX(newLeft);
			newRight = newLeft + width;
			newTop = top - (GRIDY(top - newTop));
			newBottom = newTop - height;
		    }
		    else
		    {
			/*Normal case*/
			if (ml) newLeft = GRIDX(newLeft);
			if (mr) newRight = right - GRIDX(right - newRight);
			if (mb) newBottom = GRIDY(newBottom);
			if (mt) newTop = top - GRIDY(top - newTop);
		    }
		}
		if (ml && newLeft + 3 * HANDLESIZE > newRight)
				newLeft = newRight - 3 * HANDLESIZE;
		if (mr && newLeft + 3 * HANDLESIZE > newRight)
				newRight = newLeft + 3 * HANDLESIZE;
		if (mb && newBottom + 3 * HANDLESIZE > newTop)
				newBottom = newTop - 3 * HANDLESIZE;
		if (mt && newBottom + 3 * HANDLESIZE > newTop)
				newTop = newBottom + 3 * HANDLESIZE;
		if ((newLeft != oldNewLeft ||
				newRight != oldNewRight ||
				newBottom != oldNewBottom ||
				newTop != oldNewTop) &&
				newLeft < newRight &&
				newBottom < newTop)
		{
		    Set2DIntBounds(rectangle,
				   newLeft, newRight, newBottom, newTop);
		    oldNewLeft = newLeft;
		    oldNewRight = newRight;
		    oldNewBottom = newBottom;
		    oldNewTop = newTop;

		    DrawMe(rectangle);
		}
	    }

	    DrawSkeleton(false);

	    if (logging)
	    {
		char cmd[256];
		MakeObjectName(tempStr, rectangle);
		sprintf(cmd, "set bounds %s [%d %d %d %d]\n",
			tempStr, newLeft, newRight,
			newBottom, newTop);
		Log(cmd);
	    }
	    retVal = ObjTrue;
	}
	else if (GetVar(rectangle, BACKGROUND))
	{
	    retVal = ObjTrue;
	}

	return retVal;
    }
#endif
    return ObjFalse;
}

static ObjPtr PressLine(line, x, y, flags)
ObjPtr line;
int x, y;
long flags;
/*Presses in a line*/
{
#ifdef INTERACTIVE
    ObjPtr var;
    int left, right, bottom, top;
    Bool inLine = false;

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

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	real startPoint[2], endPoint[2];
	real xDisp[2], yDisp[2];
	real resultX, resultY;
	int startX, startY, newX, newY;
	ObjPtr pickedObjects;
	Bool justSelected = false;

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

	/* return if not active */
	if (!GetPredicate(line, ACTIVATED)) return ObjFalse;

	/*See if we picked the line*/
	SetOrthoPick();
	StartPick();
	MakeOrthoXform();

	PickObject(line, PR_CENTER);
#ifdef GRAPHICS
	DrawObject(line);
#endif
	EndPickObject(line);
	pickedObjects = StopPick();
	RestoreOrthoPick();

	if (pickedObjects)
	{
	    inLine = true;

	    if ((!(flags & F_EXTEND)) && !IsSelected(line))
	    {
		/*It's a new selection and not already selected.  Deselect the
		  rest*/
		DeselectAll();
	    }

	    if ((flags & F_EXTEND) && IsSelected(line))
	    {
		/*Deselect*/
		Select(line, false);
	    }
	    else if (!IsSelected(line))
	    {
		/*Must select it*/
		MakeMeCurrent(line);
		Select(line, true);
		DrawMe(line);
		UpdateDrawing();
		justSelected = true;
	    }
	}

	if (IsSelected(line))
	{
	    /*See if we're in either of the endpoints*/
	    var = GetFixedArrayVar("PressLine", line, STARTPOINT, 1, 2L);
	    Array2CArray(startPoint, var);

	    var = GetFixedArrayVar("PressLine", line, ENDPOINT, 1, 2L);
	    Array2CArray(endPoint, var);

	    if (justSelected)
	    {
		while(Mouse(&newX, &newY) &&
		    newX == x && newY == y);
	    }

	    /*Click in startPoint?*/
	    if (x >= startPoint[0] - HANDLESIZE / 2 && 
		x <= startPoint[0] + HANDLESIZE / 2 &&
		y >= startPoint[1] - HANDLESIZE / 2 &&
		y <= startPoint[1] + HANDLESIZE / 2)
	    {
		SaveForUndo(line);
		startX = x;
		startY = y;
		xDisp[0] = x - startPoint[0];
		yDisp[0] = y - startPoint[1];

		DrawSkeleton(true);

		while (Mouse(&newX, &newY))
		{
		    resultX = newX - xDisp[0];
		    resultY = newY - yDisp[0];
		    if (flags & F_SHIFTDOWN)
		    {
			resultX = GRIDX(resultX);
			resultY = GRIDY(resultY);
		    }

		    if (resultX != startPoint[0] || resultY != startPoint[1])
		    {
			startPoint[0] = resultX;
			startPoint[1] = resultY;
			var = NewRealArray(1, 2L);
			CArray2Array(var, startPoint);
			SetVar(line, STARTPOINT, var);
			Set2DIntBounds(line,
					MIN(startPoint[0], endPoint[0]) - HANDLESIZE / 2,
					MAX(startPoint[0], endPoint[0]) + HANDLESIZE / 2,
					MIN(startPoint[1], endPoint[1]) - HANDLESIZE / 2, 
					MAX(startPoint[1], endPoint[1]) + HANDLESIZE / 2);
			DrawMe(line);
		    }
		}

		DrawSkeleton(false);
		inLine = true;
	    }
	    /*Click in endPoint?*/
	    else if (x >= endPoint[0] - HANDLESIZE / 2 && 
		x <= endPoint[0] + HANDLESIZE / 2 &&
		y >= endPoint[1] - HANDLESIZE / 2 &&
		y <= endPoint[1] + HANDLESIZE / 2)
	    {
		SaveForUndo(line);
		startX = x;
		startY = y;
		xDisp[1] = x - endPoint[0];
		yDisp[1] = y - endPoint[1];
		DrawSkeleton(true);
		while (Mouse(&newX, &newY))
		{
		    resultX = newX - xDisp[1];
		    resultY = newY - yDisp[1];
		    if (flags & F_SHIFTDOWN)
		    {
			resultX = GRIDX(resultX);
			resultY = GRIDY(resultY);
		    }

		    if (resultX != endPoint[0] || resultY != endPoint[1])
		    {
			endPoint[0] = resultX;
			endPoint[1] = resultY;
			var = NewRealArray(1, 2L);
			CArray2Array(var, endPoint);
			SetVar(line, ENDPOINT, var);
			Set2DIntBounds(line,
				MIN(startPoint[0], endPoint[0]) - HANDLESIZE / 2,
				MAX(startPoint[0], endPoint[0]) + HANDLESIZE / 2,
				MIN(startPoint[1], endPoint[1]) - HANDLESIZE / 2, 
				MAX(startPoint[1], endPoint[1]) + HANDLESIZE / 2);
			DrawMe(line);
		    }
		}
		DrawSkeleton(false);
		inLine = true;
	    }
	    else if (inLine)
	    {
		/*Click on line.  Move but only grid deltas*/
		real dx, dy;

		SaveForUndo(line);
		startX = x;
		startY = y;
		DrawSkeleton(true);
		while (Mouse(&newX, &newY))
		{
		    dx = newX - x;
		    dy = newY - y;
		    if (flags & F_SHIFTDOWN)
		    {
			dx = DGX(dx);
			dy = DGY(dy);
		    }
		    if (dx != 0 || dy != 0)
		    {
			x = newX;
			y = newY;
			startPoint[0] += dx;
			startPoint[1] += dy;
			var = NewRealArray(1, 2L);
			CArray2Array(var, startPoint);
			SetVar(line, STARTPOINT, var);

			endPoint[0] += dx;
			endPoint[1] += dy;
			var = NewRealArray(1, 2L);
			CArray2Array(var, endPoint);
			SetVar(line, ENDPOINT, var);

			Set2DIntBounds(line,
				MIN(startPoint[0], endPoint[0]) - HANDLESIZE / 2,
				MAX(startPoint[0], endPoint[0]) + HANDLESIZE / 2,
				MIN(startPoint[1], endPoint[1]) - HANDLESIZE / 2, 
				MAX(startPoint[1], endPoint[1]) + HANDLESIZE / 2);
			DrawMe(line);
		    }
		}
		DrawSkeleton(false);
	    }
	    else
	    {
		inLine = false;
	    }
	    if (inLine)
	    if (logging)
	    {
		char cmd[256];
		MakeObjectName(tempStr, line);
		sprintf(cmd, "set endpoints %s %g %g %g %g\n",
			tempStr, startPoint[0], startPoint[1], endPoint[0], endPoint[1]);
		Log(cmd);
	    }
	}
    }
    return inLine ? ObjTrue : ObjFalse;
#else
    return ObjFalse;
#endif
}


static ObjPtr DrawScreenLine(line)
ObjPtr line;
/*Draws a line*/
{
#ifdef GRAPHICS
    real startPoint[2], endPoint[2];
    ObjPtr var;
    int shadow, arrowHead;
    real width;
    real arrowParams[4][2];

    var = GetFixedArrayVar("DrawScreenLine", line, STARTPOINT, 1, 2L);
    if (!var)
    {
	return ObjFalse;
    }
    Array2CArray(startPoint, var);

    var = GetFixedArrayVar("DrawScreenLine", line, ENDPOINT, 1, 2L);
    if (!var)
    {
	return ObjFalse;
    }
    Array2CArray(endPoint, var);

    var = GetIntVar("DrawScreenLine", line, SHADOW);
    if (var)
    {
	shadow = GetInt(var);
    }
    else
    {
	shadow = SH_NONE;
    }

    var = GetIntVar("DrawScreenLine", line, ARROWHEAD);
    if (var)
    {
	arrowHead = GetInt(var);
    }
    else
    {
	arrowHead = AH_NO_ARROW;
    }

    var = GetRealVar("DrawScreenLine", line, LINEWIDTH);
    if (var)
    {
	width = GetReal(var);
    }
    else
    {
	width = 1.0;
    }

    arrowParams[0][0] = 0.0;
    arrowParams[0][1] = 0.5;

    arrowParams[1][0] = -2.2;
    arrowParams[1][1] = 0.5;

    arrowParams[2][0] = -4.25;
    arrowParams[2][1] = 2.0;

    arrowParams[3][0] = -3.5;
    arrowParams[3][1] = 2.0;

    /*Draw shadow if need be*/
    if (shadow == SH_NONE)
    {
    }
    else if (shadow == SH_BLACK)
    {
	/*Solid black shadow*/
	SetUIColor(UIBLACK);
	DrawSpacePanelLine(startPoint[0] + LINE_SHADOW, startPoint[1] - LINE_SHADOW,
	    endPoint[0] + LINE_SHADOW, endPoint[1] - LINE_SHADOW,
	    width, arrowHead, arrowParams);
    }
    else if ((shadow == SH_ALPHA) && rgbp && hasTransparency)
    {
	/*50% alpha black shadow*/
	float cv[4];

	blendfunction(BF_SA, BF_MSA);
	cv[0] = cv[1] = cv[2] = 0.0;
	cv[3] = 0.5;
	c4f(cv);
	DrawSpacePanelLine(startPoint[0] + LINE_SHADOW, startPoint[1] - LINE_SHADOW,
	    endPoint[0] + LINE_SHADOW, endPoint[1] - LINE_SHADOW,
	    width, arrowHead, arrowParams);
	blendfunction(BF_ONE, BF_ZERO);
    }
    else
    {
	/*Dithered shadow or fallback if no alpha*/
	SetUIColor(UIBLACK);
	setpattern(GREYPAT);
	DrawSpacePanelLine(startPoint[0] + LINE_SHADOW, startPoint[1] - LINE_SHADOW,
	    endPoint[0] + LINE_SHADOW, endPoint[1] - LINE_SHADOW,
	    width, arrowHead, arrowParams);
	setpattern(SOLIDPAT);
    }

    /*Draw line*/
    var = GetVar(line, COLOR);
    if (var)
    {
	SetObjectColor(var);
    }
    else
    {
	return ObjFalse;
    }

    DrawSpacePanelLine(startPoint[0], startPoint[1], endPoint[0], endPoint[1],
	width, arrowHead, arrowParams);

    /*Draw Handles*/
    if (IsSelected(line))
    {
	DrawHandle((int) startPoint[0], (int) startPoint[1]);
	DrawHandle((int) endPoint[0], (int) endPoint[1]);
    }
#endif
    return ObjTrue;
}

static ObjPtr DrawRectangle(rectangle)
ObjPtr rectangle;
/*Draws a rectangle*/
{
#ifdef GRAPHICS
    int left, right, bottom, top;
    int l, r, b, t;
    ObjPtr cvar, background, var;
    int shadow;
    real width;

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

    /*Get shadow*/
    var = GetVar(rectangle, SHADOW);
    if (var)
    {
	shadow = GetInt(var);
    }
    else
    {
	shadow = SH_NONE;
    }

    l = left + HANDLESIZE / 2;
    r = right - HANDLESIZE / 2;
    t = top - HANDLESIZE / 2;
    b = bottom + HANDLESIZE / 2;

    /*Draw shadow if need be*/
    if (shadow == SH_NONE)
    {
    }
    else if (shadow == SH_BLACK)
    {
	/*Solid black shadow*/
	FillUIRect(r, r + RECT_SHADOW, b - RECT_SHADOW, t - RECT_SHADOW, UIBLACK);
	FillUIRect(l + RECT_SHADOW, r - 1, b - RECT_SHADOW, b, UIBLACK);
    }
    else if ((shadow == SH_ALPHA) && rgbp && hasTransparency)
    {
	/*50% alpha black shadow*/
	float cv[4];

	blendfunction(BF_SA, BF_MSA);
	cv[0] = cv[1] = cv[2] = 0.0;
	cv[3] = 0.5;
	c4f(cv);
	FillRect(r, r + RECT_SHADOW, b - RECT_SHADOW, t - RECT_SHADOW);
	FillRect(l + RECT_SHADOW, r - 1, b - RECT_SHADOW, b);
	blendfunction(BF_ONE, BF_ZERO);
    }
    else
    {
	/*Dithered shadow or fallback if no alpha*/
	FillUIGauzeRect(r, r + RECT_SHADOW, b - RECT_SHADOW, t - RECT_SHADOW, UIBLACK);
	FillUIGauzeRect(l + RECT_SHADOW, r - 1, b - RECT_SHADOW, b, UIBLACK);
    }

    /*Draw background*/
    background = GetVar(rectangle, BACKGROUND);
    if (background)
    {
	SetObjectColor(background);
	FillRect(l, r, b, t);
    }

    /*Draw border*/
    cvar = GetVar(rectangle, COLOR);
    if (cvar)
    {
	SetObjectColor(cvar);
    }
    else
    {
	return ObjFalse;
    }

    var = GetRealVar("DrawRectangle", rectangle, LINEWIDTH);
    if (var)
    {
	width = GetReal(var);
    }
    else
    {
	width = 2.0;
    }
    r += 1;
    t += 1;

    if (GetPredicate(rectangle, BEVELED))
    {
	float clr[3];

	clr[0] = ((real *) ELEMENTS(cvar))[0];
	clr[1] = ((real *) ELEMENTS(cvar))[1];
	clr[2] = ((real *) ELEMENTS(cvar))[2];

	/*Draw top edge*/
	if (!overDraw)
	{
	    RGBC(clr);
	}
	FillRealQuad(l, t, l + width, t - width,
		     r - width, t - width, r, t);

	/*Draw left edge*/
	clr[0] *= 0.75;
	clr[1] *= 0.75;
	clr[2] *= 0.75;
	if (!overDraw)
	{
	    RGBC(clr);
	}
	FillRealQuad(l, t, l, b,
		     l + width, b + width, l + width, t - width);

	/*Draw right edge*/
	clr[0] *= 0.333;
	clr[1] *= 0.333;
	clr[2] *= 0.333;
	if (!overDraw)
	{
	    RGBC(clr);
	}
	FillRealQuad(r - width, t - width, r - width, b + width,
		     r, b, r, t);

	/*Draw bottom edge*/
	clr[0] *= 0.5;
	clr[1] *= 0.5;
	clr[2] *= 0.5;
	if (!overDraw)
	{
	    RGBC(clr);
	}
	FillRealQuad(l, b, r, b,
			     r - width, b + width, l + width, b + width);
    }
    else
    {
	FrameRealWideRect((real) l, (real) r, (real) b, (real) t, width);
    }

    if (IsSelected(rectangle))
    {
	/*Thanks for the code, Jim.*/
	int horCent = (left + right)/2;
	int vertCent = (bottom + top)/2;

	/* Now draw the handles */

	/* center of sides */
	DrawHandle(left + HANDLESIZE / 2, vertCent);
	DrawHandle(right - HANDLESIZE / 2, vertCent); 

	/* top edge */
	DrawHandle(horCent, top - HANDLESIZE / 2);
	DrawHandle(left + HANDLESIZE / 2, top - HANDLESIZE / 2);
	DrawHandle(right - HANDLESIZE / 2, top - HANDLESIZE / 2);


	/* bottom edge */
	DrawHandle(horCent, bottom + HANDLESIZE / 2);
	DrawHandle(left + HANDLESIZE / 2, bottom + HANDLESIZE / 2);
	DrawHandle(right - HANDLESIZE / 2, bottom + HANDLESIZE / 2);
    }
#endif
    return ObjTrue;
}

static ObjPtr ChangeShadow(control)
ObjPtr control;
/*Changes the shadow of a screen object according to control*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeShadow", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, SHADOW, GetValue(control));

    ImInvalid(repObj);
    return ObjTrue;
}


static ObjPtr ChangeLineWidth(control)
ObjPtr control;
/*Changes the linewidth of a screen object according to control*/
{
    ObjPtr repObj;
    int width;
    ObjPtr value;

    repObj = GetObjectVar("ChangeLineWidth", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(control);
    SetVar(repObj, LINEWIDTH, NewReal((GetReal(value))));

    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr ChangeBeveled(checkBox)
ObjPtr checkBox;
/*Changes an object's BEVELED according to a check box*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeBeveled", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, BEVELED, GetValue(checkBox));
    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr ShowRectangleControls(display, windowName)
ObjPtr display;
char *windowName;
/*Makes a new control window to control a rectangle*/
{
    WinInfoPtr controlWindow;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr corral;
    ObjPtr contents;
    real rgb[3], hsv[3];
    WinInfoPtr dialogExists;
    Bool hasBackground;
    int mid;

    dialogExists = DialogExists((WinInfoPtr) display, NewString("Controls"));
    controlWindow = GetDialog((WinInfoPtr) display, NewString("Controls"), windowName, 
	RCWINWIDTH, RCWINHEIGHT, RCWINWIDTH,
	RCWINHEIGHT, WINDBUF + WINFIXEDSIZE);
    
    if (!dialogExists)
    {
	long info;
	ObjPtr value;
	
	ObjPtr checkBox, icon, name, colorBar, titleBox, textBox, button;
	ObjPtr colorWheel, slider, radioGroup;
	int left, right, bottom, top, center;

	SetVar((ObjPtr) controlWindow, REPOBJ, display);

	/*Set help string*/
	SetVar((ObjPtr) controlWindow, HELPSTRING, NewString("This window \
shows controls for a rectangle.  For information about any of the controls \
in the window, use Help In Context on the control.\n"));

	/*Add in a panel*/
	panel = NewPanel(greyPanelClass, 0, RCWINWIDTH, 0, RCWINHEIGHT);
	if (!panel)
	{
	    return NULLOBJ;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);
	ContentsExpectWindowSize(controlWindow, RCWINWIDTH, RCWINHEIGHT);

	contents = GetVar(panel, CONTENTS);

	/*Add in the group of controls for Border*/
	left = MINORBORDER;
	top = RCWINHEIGHT - MINORBORDER;
	right = RCWINWIDTH - MINORBORDER;
	center = (right + left) / 2;
	right = center - MINORBORDER / 2;
	bottom = MINORBORDER;
	
	titleBox = NewTitleBox(left, right,
			/*top - TITLEBOXTOP - 3 * MINORBORDER - COLORWHEELWIDTH
				- CHECKBOXHEIGHT - TEXTBOXHEIGHT - TEXTBOXSEP,*/
			bottom,
			top, "Border");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);
	left += MINORBORDER;
	right  -= MINORBORDER;
	top -= TITLEBOXTOP + MINORBORDER;

	/*Get the color for priming the controls*/
	var = GetVar(display, COLOR);
	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("ShowRectangleDisplayControls", "Bad color");
	    rgb[0] = rgb[1] = rgb[2] = 1.0;
	}
	RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), (rgb[0]), (rgb[1]), (rgb[2]));

	/*Make the color wheel*/
	colorWheel = NewColorWheel(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH, top, "Border Color");
	var = NewRealArray(1, 2L);
	CArray2Array(var, hsv);
	SetValue(colorWheel, var);
	SetVar(colorWheel, PARENT, panel);
	SetVar(colorWheel, REPOBJ, display);
	PrefixList(contents, colorWheel);
	SetMethod(colorWheel, CHANGEDVALUE, ChangeTextColorWheel);
	SetVar(colorWheel, HELPSTRING, NewString("This color wheel controls the \
hue and saturation of the color used to draw the border of the rectangle.  \
The final color is a combination of this hue and saturation and the value, or brightness, \
given by the Value slider."));
	
	/*Make the text box below*/
	textBox = NewTextBox(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Border Color Label", "Color");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);

	/*Make the brightness slider*/
	slider = NewSlider(right - SLIDERWIDTH, right, 
		       top - COLORWHEELWIDTH, top,
		       PLAIN, "Border Color Value");
	SetVar(slider, PARENT, panel);
	PrefixList(contents, slider);
	SetSliderRange(slider, 1.0, 0.0, 0.0);
	SetSliderValue(slider, hsv[2]);
	SetVar(slider, REPOBJ, display);
	SetMethod(slider, CHANGEDVALUE, ChangeTextColorSlider);
	SetVar(slider, HELPSTRING, NewString("This slider controls the \
value, or brightness, of the color used to draw the text and lines in the palette legend.  \
The final color is a combination of this value and the hue and saturation \
given by the Color color wheel."));

	/*Make the text box below*/
	textBox = NewTextBox(right - SLIDERWIDTH - MINORBORDER, right + MINORBORDER,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Text Value Label", "Value");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);
	top = top - COLORWHEELWIDTH - TEXTBOXSEP - MINORBORDER - TEXTBOXHEIGHT;

	/*Cross link the slider and color wheel*/
	SetVar(colorWheel, SLIDER, slider);
	SetVar(slider, COLORWHEEL, colorWheel);

	/*Make group of controls for border width*/
	left = MINORBORDER;
	bottom = MINORBORDER;
	right = RCWINWIDTH - MINORBORDER;
	center = (right + left) / 2;
	right = center - MINORBORDER / 2;

	left += MINORBORDER;	
	right -= MINORBORDER;

	/*Make the border width slider*/
	top -= SCALESLOP;
	slider = NewSlider(left, right, top - SLIDERWIDTH, top, SCALE,
		"Width Slider");
	PrefixList(contents, slider);
	SetVar(slider, PARENT, panel);
	SetSliderRange(slider, 10.0, 0.0, 1.0);
	SetSliderScale(slider, 2.0, 1.0, 0.0, "%g");
	var = GetVar(display, LINEWIDTH);
	SetValue(slider, var ? var : NewReal(3.0));
	SetVar(slider, REPOBJ, display);
	SetMethod(slider, CHANGEDVALUE, ChangeLineWidth);
	SetVar(slider, HELPSTRING, NewString("This slider controls the width of \
the rectangle border in screen pixels.  A width of 0 means no border."));
	top = top - SLIDERWIDTH - TEXTBOXSEP;

	/*Make a legend*/
	textBox = NewTextBox(left, right, top - TEXTBOXHEIGHT, top,
		0, "Border Width Legend", "Border Width");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);	
	top = top - TEXTBOXHEIGHT - MINORBORDER;

	/*Make a slider readout*/
	mid = top - MAX(TEXTBOXHEIGHT, EDITBOXHEIGHT) / 2;
	textBox = NewTextBox(left, (left + right) / 2,
		mid - EDITBOXHEIGHT / 2, 
		mid + EDITBOXHEIGHT / 2,
		EDITABLE + WITH_PIT + ONE_LINE, "Width Slider Readout", "");
	SetVar(textBox, REPOBJ, display);
	SetVar(textBox, PARENT, panel);
	SetTextAlign(textBox, RIGHTALIGN);
	PrefixList(contents, textBox);
	SliderReadout(slider, textBox);

	/*And "pixels"*/
	textBox = NewTextBox((left + right) / 2 + MINORBORDER, right,
		mid - TEXTBOXHEIGHT / 2 + EDITBOXDOWN, 
		mid + TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
		0, "Pixels Legend", "Pixels");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);

	/*And a "beveled" check box*/
	bottom += MINORBORDER;
	checkBox = NewCheckBox(left, right, 
		bottom, bottom + CHECKBOXHEIGHT,
		"Beveled", GetPredicate(display, BEVELED));
	PrefixList(contents, checkBox);
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	SetMethod(checkBox, CHANGEDVALUE, ChangeBeveled);

	/*Make the background controls*/
	top = RCWINHEIGHT - MINORBORDER;
	right = RCWINWIDTH - MINORBORDER;
	left = MINORBORDER;
	center = (right + left) / 2;
	left = center + MINORBORDER / 2;
	
	titleBox = NewTitleBox(left, right,
			top - TITLEBOXTOP - MINORBORDER - 2 * MINORBORDER - COLORWHEELWIDTH
				- CHECKBOXHEIGHT - TEXTBOXHEIGHT - TEXTBOXSEP,
			top, "Background");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);
	left += MINORBORDER;
	right -= MINORBORDER;
	top -= TITLEBOXTOP + MINORBORDER;

	/*Get the color for priming the controls*/
	var = GetVar(display, BACKGROUND);
	if (var && IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 3)
	{
	    Array2CArray(rgb, var);
	    hasBackground = true;
	}
	else if (var && 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;
	    hasBackground = true;
	}
	else
	{
	    rgb[0] = rgb[1] = rgb[2] = 0.5;
	    hasBackground = false;
	}
	RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), (rgb[0]), (rgb[1]), (rgb[2]));

	/*Make the color wheel*/
	colorWheel = NewColorWheel(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH, top, "Background Color");
	var = NewRealArray(1, 2L);
	CArray2Array(var, hsv);
	SetValue(colorWheel, var);
	SetVar(colorWheel, PARENT, panel);
	SetVar(colorWheel, REPOBJ, display);
	PrefixList(contents, colorWheel);
	SetMethod(colorWheel, CHANGEDVALUE, ChangeBackgroundColorWheel);
	SetVar(colorWheel, HELPSTRING, NewString("This color wheel controls the \
hue and saturation of the color used to draw the background of the rectangle.  \
The final color is a combination of this hue and saturation and the value, or brightness, \
given by the Value slider."));
	
	/*Make the text box below*/
	textBox = NewTextBox(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Background Color Label", "Color");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);

	/*Make the brightness slider*/
	slider = NewSlider(right - SLIDERWIDTH, right, 
		       top - COLORWHEELWIDTH, top,
		       PLAIN, "Background Value");
	SetVar(slider, PARENT, panel);
	PrefixList(contents, slider);
	SetSliderRange(slider, 1.0, 0.0, 0.0);
	SetSliderValue(slider, hsv[2]);
	SetVar(slider, REPOBJ, display);
	SetMethod(slider, CHANGEDVALUE, ChangeBackgroundColorSlider);
	SetVar(slider, HELPSTRING, NewString("This slider controls the \
value, or brightness, of the color used to draw the background of the rectangle.  \
The final color is a combination of this value and the hue and saturation \
given by the Color color wheel."));

	/*Make the text box below*/
	textBox = NewTextBox(right - SLIDERWIDTH - MINORBORDER, right + MINORBORDER,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Background Value Label", "Value");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);

	/*Cross link the slider and color wheel*/
	SetVar(colorWheel, SLIDER, slider);
	SetVar(slider, COLORWHEEL, colorWheel);

	/*Make the check box*/
	top -= COLORWHEELWIDTH + TEXTBOXSEP + TEXTBOXHEIGHT + MINORBORDER;
	checkBox = NewCheckBox(left, right, 
		top - CHECKBOXHEIGHT, top,
		"No background", hasBackground ? false : true);
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	SetVar(checkBox, SLIDER, slider);
	SetVar(checkBox, COLORWHEEL, colorWheel);
	PrefixList(contents, checkBox);
	SetMethod(checkBox, CHANGEDVALUE, ChangeNoBackground);
	SetVar(checkBox, HELPSTRING, NewString("This checkbox controls whether \
a background is shown.  If it is selected, no background is shown, and the \
objects behind can be seen."));

	/*Link the checkbox to the slider and color wheel*/
	SetVar(colorWheel, CHECKBOX, checkBox);
	SetVar(slider, CHECKBOX, checkBox);

	/*Make group of controls for shadow*/
	left = MINORBORDER;
	bottom = MINORBORDER;
	top = bottom + 4 * CHECKBOXHEIGHT + 3 * CHECKBOXSPACING + 2 * MINORBORDER + TITLEBOXTOP;
	right = RCWINWIDTH - MINORBORDER;
	center = (right + left) / 2;
	left = center + MINORBORDER / 2;

	/*Make the title box*/
	titleBox = NewTitleBox(left, right,
			bottom, top, "Shadow");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);
	left += MINORBORDER;
	right  -= MINORBORDER;
	top -= TITLEBOXTOP + MINORBORDER;

	/*Set up the radio group*/
	radioGroup = NewRadioButtonGroup("Shadow Radio");
	    SetVar(radioGroup, HELPSTRING,
	   NewString("This is a group of radio buttons which control the shadow \
behing the rectangle.  For information on a particular shadow, use Help in Context on \
the desired button."));
	SetVar(radioGroup, PARENT, panel);
	PrefixList(contents, radioGroup);
	SetVar(radioGroup, HALTHELP, ObjTrue);

	/*Make the Plain button*/
	button = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top, "None");
	SetVar(button, HELPSTRING, 
	    NewString("This button selects no shadow."));
	AddRadioButton(radioGroup, button);
	top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

	/*Make the Gray dithered button*/
	button = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top, "Dithered Gray");
	SetVar(button, HELPSTRING, 
	    NewString("This button selects a gray shadow.  The shadow will appear \
behind the rectangle at the lower right.\
The shadow is drawn in black with a 50% dither pattern, so it only partially \
obscures what is below.  As it uses the same dither pattern that \
translucent objects use, it will entirely obscure translucent objects.  \
Transparent objects will be partially obscured"));
	AddRadioButton(radioGroup, button);
	top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

	/*Make the Beveled button*/
	button = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top, "Alpha Gray");
	SetVar(button, HELPSTRING, 
	    NewString("This button selects a gray shadow.  The shadow will appear \
behind the rectangle at the lower right.\
The shadow is drawn in black using 50% alpha transparency, so it only partially \
obscures what is below.  This kind of gray shadow provides a better effect than \
dithered gray with translucent objects, but it will only work if the hardware \
supports alpha blending and the window is set to full color mode.  \
If not, this shadow behaves the same as the dithered gray shadow."));
	AddRadioButton(radioGroup, button);
	top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

	/*Make the Black shadow button*/
	button = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top, "Black");
	SetVar(button, HELPSTRING, 
	    NewString("This button selects a black shadow.  \
The shadow will appear behind the rectangle at the lower right.\
The shadow is drawn in solid black, so it entirely obscures what is below."));
	AddRadioButton(radioGroup, button);
	top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

	/*Set its value*/
	SetValue(radioGroup, GetIntVar("ShowRectangleControls", display, SHADOW));
	SetVar(radioGroup, REPOBJ, display);
	SetMethod(radioGroup, CHANGEDVALUE, ChangeShadow);
    }
    return (ObjPtr) controlWindow;
}

static ObjPtr ChangeArrowAtStart(checkBox)
ObjPtr checkBox;
/*Changes the at start arrow head for checkBox*/
{
    ObjPtr repObj, var;
    int newArrowhead;

    repObj = GetObjectVar("ChangeArrowAtStart", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetVar(repObj, ARROWHEAD);
    if (var)
    {
	newArrowhead = GetInt(var);
    }
    else
    {
	newArrowhead = AH_NO_ARROW;
    }

    if (GetInt(GetValue(checkBox)))
    {
	/*Set at start*/
	newArrowhead |= AH_AT_START;
    }
    else
    {
	/*Clear at start*/
	newArrowhead &= ~AH_AT_START;
    }
    SetVar(repObj, ARROWHEAD, NewInt(newArrowhead));
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr ChangeArrowAtEnd(checkBox)
ObjPtr checkBox;
/*Changes the at start arrow head for checkBox*/
{
    ObjPtr repObj, var;
    int newArrowhead;

    repObj = GetObjectVar("ChangeArrowAtEnd", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetVar(repObj, ARROWHEAD);
    if (var)
    {
	newArrowhead = GetInt(var);
    }
    else
    {
	newArrowhead = AH_NO_ARROW;
    }

    if (GetInt(GetValue(checkBox)))
    {
	/*Set at start*/
	newArrowhead |= AH_AT_END;
    }
    else
    {
	/*Clear at start*/
	newArrowhead &= ~AH_AT_END;
    }
    SetVar(repObj, ARROWHEAD, NewInt(newArrowhead));
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr ShowLineControls(display, windowName)
ObjPtr display;
char *windowName;
/*Makes a new control window to control a line*/
{
    WinInfoPtr controlWindow;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr corral;
    ObjPtr contents;
    real rgb[3], hsv[3];
    WinInfoPtr dialogExists;
    Bool hasBackground;
    int mid;

    dialogExists = DialogExists((WinInfoPtr) display, NewString("Controls"));
    controlWindow = GetDialog((WinInfoPtr) display, NewString("Controls"), windowName, 
	LCWINWIDTH, LCWINHEIGHT, LCWINWIDTH,
	LCWINHEIGHT, WINDBUF + WINFIXEDSIZE);
    
    if (!dialogExists)
    {
	long info;
	ObjPtr value;
	
	ObjPtr checkBox, icon, name, colorBar, titleBox, textBox, button;
	ObjPtr colorWheel, slider, radioGroup;
	int left, right, bottom, top, center, arrowhead;

	SetVar((ObjPtr) controlWindow, REPOBJ, display);

	/*Set help string*/
	SetVar((ObjPtr) controlWindow, HELPSTRING, NewString("This window \
shows controls for a line.  For information about any of the controls \
in the window, use Help In Context on the control.\n"));

	/*Add in a panel*/
	panel = NewPanel(greyPanelClass, 0, LCWINWIDTH, 0, LCWINHEIGHT);
	if (!panel)
	{
	    return NULLOBJ;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);
	ContentsExpectWindowSize(controlWindow, LCWINWIDTH, LCWINHEIGHT);

	contents = GetVar(panel, CONTENTS);

	/*Add in the group of controls for Line*/
	left = MINORBORDER;
	top = LCWINHEIGHT - MINORBORDER;
	right = LCWINWIDTH - MINORBORDER;
	center = (right + left) / 2;
	right = center - MINORBORDER / 2;
	bottom = MINORBORDER;

	titleBox = TemplateTitleBox(LineTemplate, "Line");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);
	left += MINORBORDER;
	right  -= MINORBORDER;
	top -= TITLEBOXTOP + MINORBORDER;

	/*Get the color for priming the controls*/
	var = GetVar(display, COLOR);
	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("ShowLineControls", "Bad color");
	    rgb[0] = rgb[1] = rgb[2] = 1.0;
	}
	RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), (rgb[0]), (rgb[1]), (rgb[2]));

	/*Make the color wheel*/
	colorWheel = NewColorWheel(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH, top, "Line Color");
	var = NewRealArray(1, 2L);
	CArray2Array(var, hsv);
	SetValue(colorWheel, var);
	SetVar(colorWheel, PARENT, panel);
	SetVar(colorWheel, REPOBJ, display);
	PrefixList(contents, colorWheel);
	SetMethod(colorWheel, CHANGEDVALUE, ChangeTextColorWheel);
	SetVar(colorWheel, HELPSTRING, NewString("This color wheel controls the \
hue and saturation of the color used to draw the line.  \
The final color is a combination of this hue and saturation and the value, or brightness, \
given by the Value slider."));
	
	/*Make the text box below*/
	textBox = NewTextBox(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Line Color Label", "Color");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);

	/*Make the brightness slider*/
	slider = NewSlider(right - SLIDERWIDTH, right, 
		       top - COLORWHEELWIDTH, top,
		       PLAIN, "Line Color Value");
	SetVar(slider, PARENT, panel);
	PrefixList(contents, slider);
	SetSliderRange(slider, 1.0, 0.0, 0.0);
	SetSliderValue(slider, hsv[2]);
	SetVar(slider, REPOBJ, display);
	SetMethod(slider, CHANGEDVALUE, ChangeTextColorSlider);
	SetVar(slider, HELPSTRING, NewString("This slider controls the \
value, or brightness, of the color used to draw the text and lines in the palette legend.  \
The final color is a combination of this value and the hue and saturation \
given by the Color color wheel."));

	/*Make the text box below*/
	textBox = NewTextBox(right - SLIDERWIDTH - MINORBORDER, right + MINORBORDER,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Text Value Label", "Value");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);
	top = top - COLORWHEELWIDTH - TEXTBOXSEP - MINORBORDER - TEXTBOXHEIGHT;

	/*Cross link the slider and color wheel*/
	SetVar(colorWheel, SLIDER, slider);
	SetVar(slider, COLORWHEEL, colorWheel);

	/*Make group of controls for line width*/
	left = MINORBORDER;
	bottom = MINORBORDER;
	right = LCWINWIDTH - MINORBORDER;
	center = (right + left) / 2;
	right = center - MINORBORDER / 2;

	left += MINORBORDER;	
	right -= MINORBORDER;

	/*Make the line width slider*/
	top -= SCALESLOP;
	slider = NewSlider(left, right, top - SLIDERWIDTH, top, SCALE,
		"Width Slider");
	PrefixList(contents, slider);
	SetVar(slider, PARENT, panel);
	SetSliderRange(slider, 10.0, 0.0, 0.0);
	SetSliderScale(slider, 2.0, 1.0, 0.0, "%g");
	var = GetVar(display, LINEWIDTH);
	SetValue(slider, var ? var : NewReal(3.0));
	SetVar(slider, REPOBJ, display);
	SetMethod(slider, CHANGEDVALUE, ChangeLineWidth);
	SetVar(slider, HELPSTRING, NewString("This slider controls the width of \
the line in screen pixels."));

	/*Make a legend*/
	textBox = TemplateTextBox(LineTemplate, "Line Width Legend", 0, "Line Width");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);	
	SetVar(textBox, HELPSTRING, NewString("This text box controls the width of \
the line in screen pixels."));

	/*Make a slider readout*/
	textBox = TemplateTextBox(LineTemplate, "Width Slider Readout", EDITABLE + WITH_PIT + ONE_LINE, "");
	SetVar(textBox, REPOBJ, display);
	SetVar(textBox, PARENT, panel);
	SetTextAlign(textBox, RIGHTALIGN);
	PrefixList(contents, textBox);
	SliderReadout(slider, textBox);

	/*And "pixels"*/
	textBox = TemplateTextBox(LineTemplate, "Pixels Legend", 0, "Pixels");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);

	/*Make group of controls for arrow*/

	/*Get arrowhead for check boxes later
	var = GetVar(display, ARROWHEAD);
	if (var)
	{
	    arrowhead = GetInt(var);
	}
	else
	{
	    arrowhead = AH_NOARROW;
	}

	/*Make the title box*/
	titleBox = TemplateTitleBox(LineTemplate, "Arrowhead");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);

	/*Make the two check boxes for arrowheads*/
	checkBox = TemplateCheckBox(LineTemplate, "At Start", (arrowhead & AH_AT_START) ? 1 : 0);
	PrefixList(contents, checkBox);
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	SetVar(checkBox, HELPSTRING, NewString("If this box is checked, an arrowhead \
will appear at the start of the line.  If both the At Start and At End boxes are \
checked, arrowheads will appear at both ends."));
	SetMethod(checkBox, CHANGEDVALUE, ChangeArrowAtStart);

	checkBox = TemplateCheckBox(LineTemplate, "At End", (arrowhead & AH_AT_END) ? 1 : 0);
	PrefixList(contents, checkBox);
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	SetVar(checkBox, HELPSTRING, NewString("If this box is checked, an arrowhead \
will appear at the end of the line.  If both the At Start and At End boxes are \
checked, arrowheads will appear at both ends."));
	SetMethod(checkBox, CHANGEDVALUE, ChangeArrowAtEnd);

	/*Make group of controls for shadow*/

	/*Make the title box*/
	titleBox = TemplateTitleBox(LineTemplate, "Shadow");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);

	/*Set up the radio group*/
	radioGroup = NewRadioButtonGroup("Shadow Radio");
	    SetVar(radioGroup, HELPSTRING,
	   NewString("This is a group of radio buttons which control the shadow \
behing the line.  For information on a particular shadow, use Help in Context on \
the desired button."));
	SetVar(radioGroup, PARENT, panel);
	PrefixList(contents, radioGroup);
	SetVar(radioGroup, HALTHELP, ObjTrue);

	/*Make the Plain button*/
	button = TemplateRadioButton(LineTemplate, "None");
	SetVar(button, HELPSTRING, 
	    NewString("This button selects no shadow."));
	AddRadioButton(radioGroup, button);
	
	/*Make the Gray dithered button*/
	button = TemplateRadioButton(LineTemplate, "Dithered Gray");
	SetVar(button, HELPSTRING, 
	    NewString("This button selects a gray shadow.  The shadow will appear \
behind the line at the lower right.\
The shadow is drawn in black with a 50% dither pattern, so it only partially \
obscures what is below.  As it uses the same dither pattern that \
translucent objects use, it will entirely obscure translucent objects.  \
Transparent objects will be partially obscured"));
	AddRadioButton(radioGroup, button);

	/*Make the Beveled button*/
	button = TemplateRadioButton(LineTemplate, "Alpha Gray");
	SetVar(button, HELPSTRING, 
	    NewString("This button selects a gray shadow.  The shadow will appear \
behind the line at the lower right.\
The shadow is drawn in black using 50% alpha transparency, so it only partially \
obscures what is below.  This kind of gray shadow provides a better effect than \
dithered gray with translucent objects, but it will only work if the hardware \
supports alpha blending and the window is set to full color mode.  \
If not, this shadow behaves the same as the dithered gray shadow."));
	AddRadioButton(radioGroup, button);

	/*Make the Black shadow button*/
	button = TemplateRadioButton(LineTemplate, "Black");
	SetVar(button, HELPSTRING, 
	    NewString("This button selects a black shadow.  \
The shadow will appear behind the line at the lower right.\
The shadow is drawn in solid black, so it entirely obscures what is below."));
	AddRadioButton(radioGroup, button);

	/*Set its value*/
	SetValue(radioGroup, GetIntVar("ShowLineControls", display, SHADOW));
	SetVar(radioGroup, REPOBJ, display);
	SetMethod(radioGroup, CHANGEDVALUE, ChangeShadow);
    }
    return (ObjPtr) controlWindow;
}

ObjPtr PushDrawingToBottom(drawing)
ObjPtr drawing;
/*Pushes a drawing to the bottom*/
{
    ObjPtr parent, contents;

    /*Get the parent*/
    parent = GetObjectVar("PushDrawingToBottom", drawing, PARENT);
    if (!parent)
    {
	return ObjFalse;
    }

    /*And parent's contents*/
    contents = GetListVar("PushDrawingToBottom", parent, CONTENTS);
    if (!contents)
    {
	return ObjFalse;
    }

    /*Delete and replace it*/
    DeleteFromList(contents, drawing);
    PostfixList(contents, drawing);
    ImInvalid(parent);

    return ObjTrue;
}

ObjPtr BringDrawingToTop(drawing)
ObjPtr drawing;
/*Brings a drawing to the top*/
{
    ObjPtr parent, contents;

    /*Get the parent*/
    parent = GetObjectVar("BringDrawingToTop", drawing, PARENT);
    if (!parent)
    {
	return ObjFalse;
    }

    /*And parent's contents*/
    contents = GetListVar("BringDrawingToTop", parent, CONTENTS);
    if (!contents)
    {
	return ObjFalse;
    }

    /*Delete and replace it*/
    DeleteFromList(contents, drawing);
    PrefixList(contents, drawing);
    ImInvalid(parent);

    return ObjTrue;
}

ObjPtr MoveDrawingToBackPanel(drawing)
ObjPtr drawing;
/*Moves a drawing to the (top of the) back panel*/
{
    ObjPtr parent, contents, space, newPanel, newContents;

    /*Get the parent*/
    parent = GetObjectVar("MoveDrawingToBackPanel", drawing, PARENT);
    if (!parent)
    {
	return ObjFalse;
    }

    /*And parent's contents*/
    contents = GetListVar("MoveDrawingToBackPanel", parent, CONTENTS);
    if (!contents)
    {
	return ObjFalse;
    }

    /*And the space*/
    space = GetObjectVar("MoveDrawingToBackPanel", parent, SPACE);
    if (!space)
    {
	return ObjFalse;
    }

    /*And it's back panel*/
    newPanel = GetObjectVar("MoveDrawingToBackPanel", space, BACKPANEL);
    if (!newPanel)
    {
	return ObjFalse;
    }

    /*And the panel's contents*/
    newContents = GetListVar("MoveDrawingToBackPanel", newPanel, CONTENTS);
    if (!newContents)
    {
	return ObjFalse;
    }


    /*Delete the drawing*/
    ImInvalid(drawing);
    DeleteFromList(contents, drawing);

    /*Add it to the new contents*/
    PrefixList(newContents, drawing);
    SetVar(drawing, PARENT, newPanel);
    ImInvalid(drawing);

    return ObjTrue;
}

ObjPtr MoveDrawingToFrontPanel(drawing)
ObjPtr drawing;
/*Moves a drawing to the (top of the) front panel*/
{
    ObjPtr parent, contents, space, newPanel, newContents;

    /*Get the parent*/
    parent = GetObjectVar("MoveDrawingToBackPanel", drawing, PARENT);
    if (!parent)
    {
	return ObjFalse;
    }

    /*And parent's contents*/
    contents = GetListVar("MoveDrawingToBackPanel", parent, CONTENTS);
    if (!contents)
    {
	return ObjFalse;
    }

    /*And the space*/
    space = GetObjectVar("MoveDrawingToBackPanel", parent, SPACE);
    if (!space)
    {
	return ObjFalse;
    }

    /*And it's front panel*/
    newPanel = GetObjectVar("MoveDrawingToBackPanel", space, FRONTPANEL);
    if (!newPanel)
    {
	return ObjFalse;
    }

    /*And the panel's contents*/
    newContents = GetListVar("MoveDrawingToBackPanel", newPanel, CONTENTS);
    if (!newContents)
    {
	return ObjFalse;
    }


    /*Delete the drawing*/
    ImInvalid(drawing);
    DeleteFromList(contents, drawing);

    /*Add it to the new contents*/
    PrefixList(newContents, drawing);
    SetVar(drawing, PARENT, newPanel);
    ImInvalid(drawing);

    return ObjTrue;
}

ObjPtr CloneAnnotation(drawing)
ObjPtr drawing;
/*Clones a drawing*/
{
    ObjPtr retVal;

    retVal = Clone(drawing);
    sprintf(tempStr, "Annotation %d", nAnnot++);
    SetVar(retVal, NAME, NewString(tempStr));
    return retVal;
}

ObjPtr CloneTimeReadout(drawing)
ObjPtr drawing;
/*Clones a drawing*/
{
    ObjPtr retVal;

    retVal = Clone(drawing);
    sprintf(tempStr, "Time Readout %d", nTimeReadout++);
    SetVar(retVal, NAME, NewString(tempStr));
    return retVal;
}

ObjPtr CloneLine(drawing)
ObjPtr drawing;
/*Clones a drawing*/
{
    ObjPtr retVal;

    retVal = Clone(drawing);
    sprintf(tempStr, "Line %d", nLine++);
    SetVar(retVal, NAME, NewString(tempStr));
    return retVal;
}

ObjPtr CloneRectangle(drawing)
ObjPtr drawing;
/*Clones a drawing*/
{
    ObjPtr retVal;

    retVal = Clone(drawing);
    sprintf(tempStr, "Rectangle %d", nRect++);
    SetVar(retVal, NAME, NewString(tempStr));
    return retVal;
}

void InitDrawings()
/*Initializes all the drawings*/
{
    ObjPtr color;
    ObjPtr list;

    drawingClass = NewObject(controlClass, 0L);
    AddToReferenceList(drawingClass);
    SetMethod(drawingClass, SELECT, SelectDrawing);
    SetMethod(drawingClass, DELETE, DeleteObject);
    SetMethod(drawingClass, CLONE, Clone);
    SetMethod(drawingClass, DUPLICATE, DuplicateDrawing);
    SetMethod(drawingClass, PUSHTOBOTTOM, PushDrawingToBottom);
    SetMethod(drawingClass, BRINGTOTOP, BringDrawingToTop);
    SetMethod(drawingClass, MOVETOBACKPANEL, MoveDrawingToBackPanel);
    SetMethod(drawingClass, MOVETOFRONTPANEL, MoveDrawingToFrontPanel);
    SetMethod(drawingClass, PICKUP, (FuncTyp) 0);

    rectangleClass = NewObject(drawingClass, 0L);
    AddToReferenceList(rectangleClass);
    color = NewRealArray(1, 3L);
    ((real *) ELEMENTS(color))[0] = 1.0;
    ((real *) ELEMENTS(color))[1] = 1.0;
    ((real *) ELEMENTS(color))[2] = 1.0;
    SetVar(rectangleClass, COLOR, color);
    SetVar(rectangleClass, LINEWIDTH, NewReal(3.0));
    SetVar(rectangleClass, SHADOW, NewInt(SH_NONE));
    SetMethod(rectangleClass, DRAW, DrawRectangle);
    SetMethod(rectangleClass, PRESS, PressRectangle);
    SetMethod(rectangleClass, NEWCTLWINDOW, ShowRectangleControls);
    SetMethod(rectangleClass, DELETEICON, DeleteSpacePanelDrawing);
    SetVar(rectangleClass, TYPESTRING, NewString("rectangle"));
    SetVar(rectangleClass, HELPSTRING,
	NewString("The rectangle is a graphical object that can be used on \
space panels to dress up a visualization.  To change the size or location \
of this rectangle, first click anywhere on the border of the rectangle to select it. A frame will \
appear around the rectangle with eight small handles. Drag any of the handles to change \
the size of the rectangle. Drag the frame itself to reposition the rectangle.  You can \
change the style and color from within the rectangle's control panel."));
    list = NewList();
    PrefixList(list, NewSymbol(BOUNDS));
    SetVar(rectangleClass, SNAPVARS, list);

    lineClass = NewObject(drawingClass, 0L);
    AddToReferenceList(lineClass);
    color = NewRealArray(1, 3L);
    ((real *) ELEMENTS(color))[0] = 1.0;
    ((real *) ELEMENTS(color))[1] = 1.0;
    ((real *) ELEMENTS(color))[2] = 1.0;
    SetVar(lineClass, COLOR, color);
    SetVar(lineClass, LINEWIDTH, NewReal(5.0));
    SetVar(lineClass, SHADOW, NewInt(SH_NONE));
    SetVar(lineClass, ARROWHEAD, NewInt(AH_NO_ARROW));
    SetVar(lineClass, TYPESTRING, NewString("line"));
    SetVar(lineClass, HELPSTRING,
	NewString("The line is a graphical object that can be used on \
space panels to dress up a visualization.  To change the size or location \
of this line, first click anywhere on the line to select it.  Two small handles \
will appear, on at each end of the line.  Drag a handle to change the endpoint.  \
To move the entire line, click on the line and drag."));
    SetMethod(lineClass, DRAW, DrawScreenLine);
    SetMethod(lineClass, NEWCTLWINDOW, ShowLineControls);
    SetMethod(lineClass, PRESS, PressLine);
    SetMethod(lineClass, DELETEICON, DeleteSpacePanelDrawing);
    list = NewList();
    PrefixList(list, NewSymbol(BOUNDS));
    PrefixList(list, NewSymbol(STARTPOINT));
    PrefixList(list, NewSymbol(ENDPOINT));
    SetVar(lineClass, SNAPVARS, list);
}

void KillDrawings()
/*Kills all the drawings*/
{
    RemoveFromReferenceList(lineClass);
    RemoveFromReferenceList(rectangleClass);
    RemoveFromReferenceList(drawingClass);
}
