/*	ScianTextBoxes.c
    Routines for text boxes

    J. Lyons
    9/28/90

    3/26/91	added simple editing
    3/28/91 added routines to set font, size, line spacing
    3/29/91 added provision for different style text boxes
    4/19/91 added one-line editing 
    5/13/91 EMP moved an #ifdef FONTS4D around a fontHandle
    5/29/91 EMP removed lib headers
    7/17/91 added multiline editing
    7/22/91 added incredibly fancy frame for adjustable text boxes
    8/12/91 modified to use ScianFontSystem routines
    8/16/91 modified to use Set/Get2DIntBounds
    8/27/91 added ACTIVATED attribute
    8/28/91 added CUT, COPY, PASTE methods
    9/5/91	added TextHeight routine
    9/6/91	added help
    10/30/91 added shift-clicking to press method
    11/1/91 added double-clicking to press method
    11/4/91 added control window for textboxes (with EMP)
    1/8/92	added indentation control
    1/20/92	fixed linespace problem for large sizes
    1/22/92	made indentation behavior more like tabs, sort of
    4/9/92  EMP removed argument from NEWCTLWINDOW method
    6/13/92 EMP changed method prototypes to declarations
    8/30/92 EMP added opacity dependency and make method
    12/3/92 changed Press routine per new selection scheme
    12/8/92 added auto horiz scrolling to one-line text boxes
    1/21/93 did undo
*/

#include "Scian.h"
#include "ScianStyle.h"
#include "ScianTypes.h"
#include "ScianFontSystem.h"
#include "ScianWindows.h"
#include "ScianArrays.h"
#include "ScianLists.h"
#include "ScianDraw.h"
#include "ScianColors.h"
#include "ScianIDs.h"
#include "ScianErrors.h"
#include "ScianEvents.h"
#include "ScianScripts.h"
#include "ScianMethods.h"
#include "ScianDialogs.h"
#include "ScianControls.h"
#include "ScianHelp.h"
#include "ScianTitleBoxes.h"
#include "ScianTextBoxes.h"
#include "ScianSliders.h"
#include "ScianButtons.h"
#include "ScianDrawings.h"
#include "ScianSymbols.h"
#include "ScianSnap.h"

/* #define NEWWAY */

#define MAXINT	(~(1 << (8 * (int) sizeof(int) - 1)))
#define MAXLINE		255
#define MAXNLINES	20
#define MAXTEXTSIZE	96

/* Method declarations, NOT prototypes */
static ObjPtr EditTextBox();
static ObjPtr DrawTextBox();
static ObjPtr PressTextBox();
static ObjPtr ShowTextBoxControls();
static ObjPtr TextBoxNotCurrent();
static ObjPtr CutText();
static ObjPtr CopyText();
static ObjPtr PasteText();
static ObjPtr MakeHelpString();
static ObjPtr SetTBVal();
static ObjPtr MakeTextBoxOpaque();

/* static function prototypes */
#ifdef PROTO

static void NewText(ObjPtr);
static char *NextLine(char *, int, char *);

#else

static void NewText();
static char *NextLine();

#endif



/*********************************************************************** GLOBALS */
ObjPtr textBoxClass;
static char *textBuf; /* pointer to buffer for drawing and editing text */
static int textBufSize; /* current size of the buffer */

static int indent;	/* current indent amount, in pixels */

#define TEXTBUFFERSIZE	1000
#define TEXTBUFFERINCR	100

void InitTextBoxes()
{
    ObjPtr list;
    
    textBoxClass = NewObject(controlClass, 0);
    AddToReferenceList(textBoxClass);
    SetVar(textBoxClass, NAME, NewString("Text Box"));
    SetVar(textBoxClass, TYPESTRING, NewString("text box"));
    SetMethod(textBoxClass, DRAW, DrawTextBox);
    SetMethod(textBoxClass, SETVAL, SetTBVal);
    SetMethod(textBoxClass, PRESS, PressTextBox);
    SetMethod(textBoxClass, KEYDOWN, EditTextBox);
    SetMethod(textBoxClass, CUT, CutText);
    SetMethod(textBoxClass, COPY, CopyText);
    SetMethod(textBoxClass, PASTE, PasteText);
    SetMethod(textBoxClass, SELECTALL, SelectAll);
    SetMethod(textBoxClass, PICKUP, (FuncTyp) 0);
    SetMethod(textBoxClass, MAKE1HELPSTRING, MakeHelpString);
    SetMethod(textBoxClass, NEWCTLWINDOW, ShowTextBoxControls);
    SetMethod(textBoxClass, YOURENOTCURRENT, TextBoxNotCurrent);
    
    /* stuff for undo to work */
    list = NewList();
    PrefixList(list, NewSymbol(BOUNDS));
    PrefixList(list, NewSymbol(BGNSEL));
    PrefixList(list, NewSymbol(ENDSEL));
    PrefixList(list, NewSymbol(TEXTFONT));
    PrefixList(list, NewSymbol(TEXTSIZE));
    PrefixList(list, NewSymbol(LINESPACE));
    PrefixList(list, NewSymbol(ALIGNMENT));
    PrefixList(list, NewSymbol(COLOR));
    PrefixList(list, NewSymbol(BACKGROUND));
    PrefixList(list, NewSymbol(LASTKEY));
    PrefixList(list, NewSymbol(VALUE));
    SetVar(textBoxClass, SNAPVARS, list);

    /*EMP stuff for opacity to work*/
    DeclareDependency(textBoxClass, OPAQUE, BACKGROUND);
    SetMethod(textBoxClass, OPAQUE, MakeTextBoxOpaque);

    /* get a buffer for drawing and editing text */
    textBuf = (char *) malloc(textBufSize = TEXTBUFFERSIZE);
    if (!textBuf)
    {
	    OMErr();
	    textBufSize = 0;
    }
}

void KillTextBoxes()
{
	DeleteThing(textBoxClass);
	if (textBuf) free(textBuf);
}

static ObjPtr MakeTextBoxOpaque(textBox)
ObjPtr textBox;
/*EMP - Makes a text box's OPAQUE variable*/
{
	SetVar(textBox, OPAQUE, GetVar(textBox, BACKGROUND) ? ObjTrue : ObjFalse);
	return ObjTrue;
}

#ifdef PROTO
ObjPtr NewTextBox(int left, int right, int bottom , int top, 
					int style, char *name, char *text)
#else
ObjPtr NewTextBox(left, right, bottom, top, style, name, text)
int left, right, bottom, top, style;
char *name, *text;
#endif
{
	ObjPtr newBox;

	if (left > right)
	{
		register int n;
		n = left; left = right; right = n;
	}
	if (bottom > top)
	{
		register int n;
		n = bottom; bottom = top; top = n;
	}
	newBox = NewObject(textBoxClass, 0);
	Set2DIntBounds(newBox, left, right, bottom, top);
	SetVar(newBox, CLASSID, NewInt(CLASS_TEXTBOX));
	SetVar(newBox, ACTIVATED, NewInt(true));
	SetVar(newBox, STYLE, NewInt(style));
	SetVar(newBox, NAME, NewString(name));
	if (style & WITH_PIT) SetVar(newBox, BACKGROUND, NewInt(TEXTBOXCOLOR));
	SetVar(newBox, VALUE, NewString(text));
	SetVar(newBox, CHANGED, ObjTrue); /* flag to line-break text before drawing first time */
	if (style & EDITABLE)
	{
		ObjPtr theLen = NewInt(strlen(text));
		SetVar(newBox, BGNSEL, theLen);
		SetVar(newBox, ENDSEL, theLen);
	}
	return newBox;
}

static ObjPtr MakeHelpString(textBox, theClass)
ObjPtr textBox, theClass;
{
	int style = GetInt(GetVar(textBox, STYLE));

	*textBuf = '\0';

	if (style & EDITABLE) strcpy(textBuf,"This is an editable text box. \
Click the left mouse button anywhere in the text to place the insertion point \
for adding new text. Drag through text to select it for editing. ");

	if (style & ADJUSTABLE) strcat(textBuf, "To change the size or location \
of this text box, first click anywhere in the text to select it. A frame will \
appear around the text with eight small handles. Drag any of the handles to change \
the size of the text box. Drag the frame itself to reposition the text.");

	if (style & ONE_LINE) strcat(textBuf, "If all the text will not fit \
within the box, it will automatically scroll as you add or delete text, drag \
through text to select it, or use the arrow keys to move the insertion point.");

	SetVar(theClass, HELPSTRING, *textBuf ? NewString(textBuf) : NULLOBJ);
	return ObjTrue;
}

#ifdef PROTO
void ActivateTextBox(ObjPtr textBox, Bool act)
#else
void ActivateTextBox(textBox, act)
ObjPtr textBox;
Bool act;
#endif
{
	if (act) SetVar(textBox, ACTIVATED, ObjTrue);
	else
	{
		SetVar(textBox, ACTIVATED, ObjFalse);
		Select(textBox, false);
	}
	ImInvalid(textBox);
}

static ObjPtr SetTBVal(textBox, theText)
ObjPtr textBox, theText;
{
	Bool deferChange;

	if (IsString(theText)) SetTextBox(textBox, GetString(theText));
	else if (IsInt(theText))
	{
		sprintf(tempStr, "%d", GetInt(theText));
		SetTextBox(textBox, tempStr);
	}
	else if (IsReal(theText))
	{
		sprintf(tempStr, "%g", GetReal(theText));
		SetTextBox(textBox, tempStr);
	}
	else return ObjFalse;

	return ObjTrue;
}

/******************************************************************** SET TEXT ROUTINES */
#ifdef PROTO
ObjPtr SetTextBox(ObjPtr textBox, char *text)
#else
ObjPtr SetTextBox(textBox, text)
ObjPtr textBox;
char *text;
#endif
{
	int style;
	
	style = GetInt(GetVar(textBox, STYLE));
	SetVar(textBox, VALUE, NewString(text));
	if (style & EDITABLE)
	{
		ObjPtr theLen = NewInt(strlen(text));
		SetVar(textBox, BGNSEL, theLen);
		SetVar(textBox, ENDSEL, theLen);
	}
	if (style & ONE_LINE)
	{
	    SetVar(textBox, HOROFFSET, NewInt(0));
	}
	SetVar(textBox, CHANGED, ObjTrue); 
	ImInvalid(textBox);
	ChangedValue(textBox);
	if (logging) LogControl(textBox);
	return NULLOBJ;
}

#ifdef PROTO
ObjPtr SetTextFont(ObjPtr textBox, char *fontName)
#else
ObjPtr SetTextFont(textBox, fontName)
ObjPtr textBox;
char *fontName;
#endif
{
	SetVar(textBox, TEXTFONT, NewString(fontName));
	SetVar(textBox, CHANGED, ObjTrue); 
	ImInvalid(textBox);
	return NULLOBJ;
}

#ifdef PROTO
ObjPtr SetTextSize(ObjPtr textBox, int textSize)
#else
ObjPtr SetTextSize(textBox, textSize)
ObjPtr textBox;
int textSize;
#endif
{
	if (textSize > 0 && textSize <= MAXTEXTSIZE)
		SetVar(textBox, TEXTSIZE, NewInt(textSize));
	SetVar(textBox, CHANGED, ObjTrue); 
	ImInvalid(textBox);
	return NULLOBJ;
}

#ifdef PROTO
ObjPtr SetTextLineSpace(ObjPtr textBox, int lineSpace)
#else
ObjPtr SetTextSize(textBox, lineSpace)
ObjPtr textBox;
int lineSpace;
#endif
{
	if (lineSpace > 0)
		SetVar(textBox, LINESPACE, NewInt(lineSpace));
	ImInvalid(textBox);
	return NULLOBJ;
}

#ifdef PROTO
ObjPtr SetTextAlign(ObjPtr textBox, int value)
#else
ObjPtr SetTextAlign(textBox, value)
ObjPtr textBox;
int value;
#endif
{
	SetVar(textBox, ALIGNMENT, NewInt(value));
	ImInvalid(textBox);
	return NULLOBJ;
}

#ifdef PROTO
ObjPtr SetTextColor(ObjPtr textBox, ObjPtr color)
#else
ObjPtr SetTextColor(textBox, color)
ObjPtr textBox, color;
#endif
{
	SetVar(textBox, COLOR, color);
	ImInvalid(textBox);
	return NULLOBJ;
}

#ifdef PROTO
ObjPtr SetTextBGColor(ObjPtr textBox, ObjPtr color)
#else
ObjPtr SetTextBGColor(textBox, color)
ObjPtr textBox, color;
#endif
{
	SetVar(textBox, BACKGROUND, color);
	ImInvalid(textBox);
	return NULLOBJ;
}

#ifdef PROTO
ObjPtr SetTextBoxStyle(ObjPtr textBox, int style)
#else
ObjPtr SetTextBoxStyle(textBox, style)
ObjPtr textBox;
int style;
#endif
{
	SetVar(textBox, STYLE, NewInt(style));
	SetVar(textBox, CHANGED, ObjTrue); 
	ImInvalid(textBox);
	return NULLOBJ;
}

#ifdef NEWWAY
/*********************************************************** NEW TEXT */
#ifdef PROTO
static void NewText(ObjPtr textBox)
#else
static void NewText(textBox)
ObjPtr textBox;
#endif
{
	/* puts "soft" eols (\r is used) into the text as needed */

	int left, right, bottom, top;
	int textLeft, textRight;
	ObjPtr theObj;
	char *text, *nxtLine, *bgnLine, *s, *t, *fontName, lineBuf[MAXLINE + 1];
	int len, width, size, style;

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

	MakeVar(textBox, VALUE);
	text = GetString(GetVar(textBox, VALUE));

	theObj = GetVar(textBox, STYLE);
	if (theObj) style = GetInt(theObj);
	else style = PLAIN;

	if (style & ONE_LINE)
	{
		SetVar(textBox, VALUE, NewString(text));
		return;
	}

	theObj = GetVar(textBox, TEXTFONT);
	if (theObj) fontName = GetString(theObj);
	else fontName = DEFFONT;

	theObj = GetVar(textBox, TEXTSIZE);
	if (theObj) size = GetInt(theObj);
	else size = DEFTEXTSIZE;

	if (style & WITH_PIT)
	{
		textLeft = left + TEXTMARGIN;
		textRight = right - TEXTMARGIN;
	}
	else if (style & (EDITABLE | ADJUSTABLE) || GetVar(textBox, BACKGROUND))
	{
		textLeft = left + HANDLESIZE + 2;
		textRight = right - HANDLESIZE - 2;
	}
	else /* default is no inset */
	{
		textLeft = left;
		textRight = right;
	}

	/*** CHANGE FOR INDENTATION ***/
	SetupFont(fontName, size);
	width = textRight - textLeft;
	nxtLine = text; /* set up for loop */
	t = textBuf;
	do {
		if (t - textBuf + MAXLINE > textBufSize) /* increase buffer size */
		{
			int tIndex = t - textBuf; /* remember position */
			textBuf = (char *) realloc(textBuf, textBufSize + TEXTBUFFERINCR);
			if (!textBuf) /* failed */
			{
				OMErr();
				textBufSize = 0;
				return;
			}
			t = tIndex + textBuf; /* reset pointer in new buffer */
		}
		bgnLine = nxtLine;
		nxtLine = NextLine(bgnLine, width, s=lineBuf); /* (ignores existing soft CRs) */
		while (*t++ = *s++) ; /* copy line into textBuf */
		--t; /* back up to terminator */
		if (*nxtLine == '\n') ++nxtLine, *t++ = '\n';	/* paragraph */
		else *t++ = '\r'; /* soft eol */
	} while (*nxtLine);
	*t = '\0'; /* stuff terminator */
	SetVar(textBox, VALUE, NewString(textBuf));
	SetVar(textBox, CHANGED, ObjFalse);
	/* calling routine will handle draw, changed value */
}
#endif

/*********************************************************** TEXT HEIGHT */
#ifdef PROTO
int TextHeight(ObjPtr textBox)
#else
int TextHeight(textBox)
ObjPtr textBox;
#endif
{
#ifndef NEWWAY
	int left, right, bottom, top;
	int textLeft, textRight, textTop;
	ObjPtr theObj;
	char *text, *nxtLine, *bgnLine, lineBuf[MAXLINE + 1], *fontName;
	int y, width, size, align, lineSpace, style;
	int tabWid;

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

	MakeVar(textBox, VALUE);
	theObj = GetVar(textBox, VALUE);
	if (theObj) text = GetString(theObj);
	else text = "\0";
		
	theObj = GetVar(textBox, STYLE);
	if (theObj) style = GetInt(theObj);
	else style = PLAIN;

	theObj = GetVar(textBox, TEXTFONT);
	if (theObj) fontName = GetString(theObj);
	else fontName = DEFFONT;

	theObj = GetVar(textBox, TEXTSIZE);
	if (theObj) size = GetInt(theObj);
	else size = DEFTEXTSIZE;

	theObj = GetVar(textBox, ALIGNMENT);
	if (theObj) align = GetInt(theObj);
	else align = LEFTALIGN;

	theObj = GetVar(textBox, LINESPACE);
	if (theObj) lineSpace = GetInt(theObj);
	else lineSpace = 4 + size/3;

	MakeVar(textBox, BACKGROUND);

	if (style & WITH_PIT)
	{
		textLeft = left + TEXTMARGIN;
		textRight = right - TEXTMARGIN;
		textTop = top - TEXTMARGIN;
	}
	else if (style & (EDITABLE | ADJUSTABLE) || GetVar(textBox, BACKGROUND))
	{
		textLeft = left + HANDLESIZE + 2;
		textRight = right - HANDLESIZE - 2;
		textTop = top - HANDLESIZE - 2;
	}
	else /* default is no inset */
	{
		textLeft = left;
		textRight = right;
		textTop = top;
	}

	SetupFont(fontName, size);
	tabWid = TABWID*ChrWidth('0');
	y = textTop - size;
	width = textRight - textLeft;
	indent = 0;
	bgnLine = nxtLine = text; /* set up for loop */
	do {
		bgnLine = nxtLine;
		nxtLine = NextLine(bgnLine, width - indent, lineBuf);
		y -= size + lineSpace;
		if (*nxtLine == '\n')
		{
			++nxtLine; /* paragraph */
			indent = 0;
		}
		else indent += ChrCount(lineBuf, '\t')*tabWid;
	} while (*nxtLine);
	return textTop - y + size;
#else
	ObjPtr theObj;
	int n, size, lineSpace;
	char *t;
		
	if (GetPredicate(textBox, CHANGED)) NewText(textBox);

	MakeVar(textBox, VALUE);
	t = GetString(GetVar(textBox, VALUE));

	theObj = GetVar(textBox, TEXTSIZE);
	if (theObj) size = GetInt(theObj);
	else size = DEFTEXTSIZE;

	theObj = GetVar(textBox, LINESPACE);
	if (theObj) lineSpace = GetInt(theObj);
	else lineSpace = 4 + size/3;

	n = 0;
	while (*t)
	{
		if (*t == '\r' || *t == '\n') ++n;
		++t;
	}
	return n*(size + lineSpace);
#endif
}

int ChrCount(s, c)
char *s, c;
{
	int n=0;
	while (*s) if (*s++ == c) ++n;
	return n;
}

/************************************************************************** DRAW TEXT BOX */
static ObjPtr DrawTextBox(textBox)
ObjPtr textBox;
{
#ifdef GRAPHICS
	Bool active;
	int left, right, bottom, top;
	int textLeft, textRight, textBottom, textTop;
	int x,y;
	ObjPtr theObj, color, bgColor;
	char *s, *t, *text, *nxtLine, *bgnLine, lineBuf[MAXLINE + 1], *fontName;
	int lineStart, selStart, selEnd; /* distances for line and selection */
	int bgnSel, endSel; /* index to text selection */
	int align, length, width, center, size, lineSpace, style, horOffset; 
	int tabWid, nTabs;

	Get2DIntBounds(textBox, &left, &right, &bottom, &top);
	if (IsDrawingRestricted(left, right, bottom, top)) return ObjFalse;

	active = GetPredicate(textBox, ACTIVATED);
	
	MakeVar(textBox, VALUE);
	theObj = GetStringVar("DrawTextBox", textBox, VALUE);
	if (theObj) text = GetString(theObj);
	else text = "\0";
		
	if ((length = strlen(text)) > textBufSize) /* grow buffer */
	{
		textBuf = (char *) realloc(textBuf, length + TEXTBUFFERINCR);
		if (!textBuf) /* failed */
		{
			OMErr();
			textBufSize = 0;
			return NULLOBJ;
		}
	}

	theObj = GetIntVar("DrawTextBox", textBox, STYLE);
	if (theObj) style = GetInt(theObj);
	else style = PLAIN;

	theObj = GetVar(textBox, ALIGNMENT);
	if (theObj) align = GetInt(theObj);
	else align = LEFTALIGN;

	theObj = GetVar(textBox, TEXTFONT);
	if (theObj) fontName = GetString(theObj);
	else fontName = DEFFONT;

	theObj = GetVar(textBox, TEXTSIZE);
	if (theObj) size = GetInt(theObj);
	else size = DEFTEXTSIZE;

	theObj = GetVar(textBox, LINESPACE);
	if (theObj) lineSpace = GetInt(theObj);
	else lineSpace = 4 + size/3;

	MakeVar(textBox, COLOR);
	color = GetVar(textBox, COLOR);

	MakeVar(textBox, BACKGROUND);
	bgColor = GetVar(textBox, BACKGROUND);

	if (active && AmICurrent(textBox) && style & EDITABLE)
	{
		theObj = GetVar(textBox, BGNSEL);
		if (theObj) bgnSel = GetInt(theObj);
		else bgnSel = strlen(text);

		theObj = GetVar(textBox, ENDSEL);
		if (theObj) endSel = GetInt(theObj);
		else endSel = bgnSel;
	}

	if (style & WITH_PIT)
	{
		DrawSunkenRect(left, right, bottom, top,
			active ? TEXTBOXCOLOR : UIBACKGROUND);
		textLeft = left + TEXTMARGIN;
		textRight = right - TEXTMARGIN;
		textBottom = bottom + TEXTMARGIN;
		textTop = top - TEXTMARGIN;
	}
	else if (bgColor)
	{
		SetObjectColor(bgColor);
		FillRect(left, right, bottom, top);
		textLeft = left + HANDLESIZE + 2;
		textRight = right - HANDLESIZE - 2;
		textBottom = bottom + HANDLESIZE + 2;
		textTop = top - HANDLESIZE - 2;
	}
	else if (style & (EDITABLE | ADJUSTABLE))
	{
		textLeft = left + HANDLESIZE + 2;
		textRight = right - HANDLESIZE - 2;
		textBottom = bottom + HANDLESIZE + 2;
		textTop = top - HANDLESIZE - 2;
	}
	else /* default is no inset */
	{
		textLeft = left;
		textRight = right;
		textBottom = bottom;
		textTop = top;
	}
	if (style & ADJUSTABLE) SetClipRect(left, right, bottom, top);
	else SetClipRect(textLeft-1, textRight+1, textBottom-1, textTop+1);
	
	theObj = GetVar(textBox, HOROFFSET);
	if (theObj) horOffset = GetInt(theObj);
	else horOffset = 0;

	/* wake up, time to draw */
	SetupFont(fontName, size);
	y = textTop - size;
	width = style & ONE_LINE ? MAXINT : textRight - textLeft;
	tabWid = TABWID*ChrWidth('0');
	indent = 0;
	center = (textLeft + textRight)/2;
	nxtLine = text; /* set up for loop */
	do {
		bgnLine = nxtLine;
		nxtLine = NextLine(bgnLine, width - indent, lineBuf);
		length = StrWidth(lineBuf);
		switch (align)
		{
		case CENTERALIGN:
           		lineStart = center - length/2 - horOffset;
			break;

		case RIGHTALIGN:
			lineStart = textRight - length - horOffset;
			break;

		case LEFTALIGN:
		default:
			lineStart = textLeft + indent - horOffset;
			break;
		}
		if (active && AmICurrent(textBox) && style & EDITABLE)
		{
			/* draw text cursor if in this line */
			if (bgnSel == endSel && text + bgnSel <= nxtLine
				&& text + bgnSel >= bgnLine)
			{
				/* find position of insertion point */
				int i = bgnSel - (bgnLine - text);

				strncpy(textBuf, bgnLine, i);
				textBuf[i] = '\0';
				selStart = StrWidth(textBuf);

				/* draw text cursor before bgnSel */
				FillUIRect(lineStart + selStart, lineStart + selStart + 1,
					y - lineSpace + 2, y + size + 2, TEXTCURSORCOLOR);
			}
			else if (text + endSel > bgnLine && text + bgnSel < nxtLine)
			{
				int selectColor;
				/* some of the selection is on this line */
				if (text + bgnSel < bgnLine) selStart = lineStart;
				else /* compute dist to start of selection */
				{
					int i = bgnSel - (bgnLine - text);

					strncpy(textBuf, bgnLine, i);
					textBuf[i] = '\0';
					selStart = lineStart + StrWidth(textBuf);
				}
				if (text + endSel >= nxtLine) selEnd = lineStart + length;
				else /* compute dist to end of selection */
				{
					int i = endSel - (bgnLine - text);

					strncpy(textBuf, bgnLine, i);
					textBuf[i] = '\0';
					selEnd = lineStart + StrWidth(textBuf);
				}
				/* now draw the selection rectangle for this line */
				selectColor = ChooseSelectionColor(textBox);
				FillUIRect(selStart, selEnd, y - lineSpace + 2, y + size + 2,
							selectColor);
			}
		}
		/* now draw the text */
		if (color) SetObjectColor(color);
		else SetUIColor(UITEXT);
		nTabs = 0; s = t = lineBuf; /* set up for loop */
		while (true)
		{
			if (*t == '\0')	/* end of line */
			{
				switch (align)
				{
				    case CENTERALIGN:
					DrawAString(CENTERALIGN, center, y, s);
					break;
					
				    case RIGHTALIGN:
					DrawAString(RIGHTALIGN, textRight, y, s);
					break;
					
				    case LEFTALIGN:
				    default:
					DrawAString(LEFTALIGN, lineStart, y, s);
					break;
				}
				break;
			}
			else if (*t == '\t') /* handle tab */
			{
				*t = '\0'; /* stuff terminator for this segment */
				DrawAString(LEFTALIGN, lineStart, y, s);
				lineStart = textLeft + ( indent = (++nTabs)*tabWid ) - horOffset;
				s = ++t;
				continue;
			}
			else ++t;						
		}
		if ((y -= size + lineSpace) < textBottom) break; /* whoa! past bottom of box */
		if (*nxtLine == '\n')
		{
			++nxtLine; /* paragraph */
			indent = 0;
		}
	} while (*nxtLine);

	if (IsSelected(textBox) && style & ADJUSTABLE)
	{
		/* Draw incredibly fancy frame for moving and resizing text box */

		int horCent = (left + right)/2;
		int vertCent = (bottom + top)/2;

		FrameUIWideRect(left+INSET, right-INSET,
				bottom+INSET, top-INSET,
				OUTSIDEFRAMEWEIGHT, OUTSIDEFRAMECOLOR);
		FrameUIWideRect(left+INSET+OUTSIDEFRAMEWEIGHT,
				right-INSET-OUTSIDEFRAMEWEIGHT,
				bottom+INSET+OUTSIDEFRAMEWEIGHT,
				top-INSET-OUTSIDEFRAMEWEIGHT,
				INSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);
		FrameUIWideRect(left+INSET+OUTSIDEFRAMEWEIGHT+INSIDEFRAMEWEIGHT,
				right-INSET-OUTSIDEFRAMEWEIGHT-INSIDEFRAMEWEIGHT,
				bottom+INSET+OUTSIDEFRAMEWEIGHT+INSIDEFRAMEWEIGHT,
				top-INSET-OUTSIDEFRAMEWEIGHT-INSIDEFRAMEWEIGHT,
				OUTSIDEFRAMEWEIGHT, OUTSIDEFRAMECOLOR);

		/* Now draw the handles */
		/* center of sides */
		FillUIRect(left, left+HANDLESIZE,
				vertCent-HANDLESIZE/2, vertCent+HANDLESIZE/2, OUTSIDEFRAMECOLOR);
		FillUIRect(left+OUTSIDEFRAMEWEIGHT,
				left+HANDLESIZE-OUTSIDEFRAMEWEIGHT,
				vertCent-HANDLESIZE/2+OUTSIDEFRAMEWEIGHT,
				vertCent+HANDLESIZE/2-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

		FillUIRect(right-HANDLESIZE, right,
				vertCent-HANDLESIZE/2, vertCent+HANDLESIZE/2, OUTSIDEFRAMECOLOR);
		FillUIRect(right-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
				right-OUTSIDEFRAMEWEIGHT,
				vertCent-HANDLESIZE/2+OUTSIDEFRAMEWEIGHT,
				vertCent+HANDLESIZE/2-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

		/* top edge */
		FillUIRect(horCent-HANDLESIZE/2, horCent+HANDLESIZE/2,
				top-HANDLESIZE, top, OUTSIDEFRAMECOLOR);
		FillUIRect(horCent-HANDLESIZE/2+OUTSIDEFRAMEWEIGHT,
				horCent+HANDLESIZE/2-OUTSIDEFRAMEWEIGHT,
				top-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
				top-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

		FillUIRect(left, left+HANDLESIZE,
				top-HANDLESIZE, top, OUTSIDEFRAMECOLOR);
		FillUIRect(left+OUTSIDEFRAMEWEIGHT,
				left+HANDLESIZE-OUTSIDEFRAMEWEIGHT,
				top-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
				top-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);
		
		FillUIRect(right-HANDLESIZE, right,
				top-HANDLESIZE, top, OUTSIDEFRAMECOLOR);
		FillUIRect(right-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
				right-OUTSIDEFRAMEWEIGHT,
				top-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
				top-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

		/* bottom edge */
		FillUIRect(horCent-HANDLESIZE/2, horCent+HANDLESIZE/2,
				bottom, bottom+HANDLESIZE, OUTSIDEFRAMECOLOR);
		FillUIRect(horCent-HANDLESIZE/2+OUTSIDEFRAMEWEIGHT,
				horCent+HANDLESIZE/2-OUTSIDEFRAMEWEIGHT,
				bottom+OUTSIDEFRAMEWEIGHT,
				bottom+HANDLESIZE-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

		FillUIRect(left, left+HANDLESIZE,
				bottom, bottom+HANDLESIZE, OUTSIDEFRAMECOLOR);
		FillUIRect(left+OUTSIDEFRAMEWEIGHT,
				left+HANDLESIZE-OUTSIDEFRAMEWEIGHT,
				bottom+OUTSIDEFRAMEWEIGHT,
				bottom+HANDLESIZE-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);
		
		FillUIRect(right-HANDLESIZE, right,
				bottom, bottom+HANDLESIZE, OUTSIDEFRAMECOLOR);
		FillUIRect(right-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
				right-OUTSIDEFRAMEWEIGHT,
				bottom+OUTSIDEFRAMEWEIGHT,
				bottom+HANDLESIZE-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);
	}
	if (!active) /* cover with gauze */
		FillUIGauzeRect(left, right, bottom, top, UIBACKGROUND);
#endif
	RestoreClipRect();
	return NULLOBJ;
}

static ObjPtr PressTextBox(textBox, mouseX, mouseY, flags)
ObjPtr textBox;
int mouseX, mouseY;
long flags;
{
#ifdef INTERACTIVE
    ObjPtr theObj;
    char *text, *t, *fontName, lineBuf[MAXLINE + 1];
    int i, j, cj, l, n, width, length, horOffset, cHorOffset;
    int style, align, size, nLines, lineSpace;
    float x;
    int y, mX, mY, offset[MAXNLINES * MAXLINE], lnx[3*(MAXNLINES + 1)];
    int left, right, bottom, top, hCent, vCent;
    int textLeft, textRight, textBot, textTop;
    int tabWid, nTabs;

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

    /* return if mouse outside text box */
    if (mouseX < left || mouseX > right || mouseY < bottom 
		    || mouseY > top) return ObjFalse;

    /* get text parameters and scan text */

    theObj = GetIntVar("PressTextBox", textBox, STYLE);
    if (!theObj) style = PLAIN;
    else style = GetInt(theObj);

    MakeVar(textBox, VALUE);
    theObj = GetVar(textBox, VALUE);
    if (theObj) text = GetString(theObj);
    else text = "\0";

    theObj = GetVar(textBox, ALIGNMENT);
    if (theObj) align = GetInt(theObj);
    else align = LEFTALIGN;

    theObj = GetVar(textBox, TEXTFONT);
    if (theObj) fontName = GetString(theObj);
    else fontName = DEFFONT;

    theObj = GetVar(textBox, TEXTSIZE);
    if (theObj) size = GetInt(theObj);
    else size = DEFTEXTSIZE;

    theObj = GetVar(textBox, LINESPACE);
    if (theObj) lineSpace = GetInt(theObj);
    else lineSpace = 4 + size/3;
    
    theObj = GetVar(textBox, HOROFFSET);
    if (theObj) horOffset = GetInt(theObj);
    else horOffset = 0;

    MakeVar(textBox, BACKGROUND);

    /* adjust bounds for background or pit */
    if (style & WITH_PIT)
    {
	textLeft = left + TEXTMARGIN;
	textRight = right - TEXTMARGIN;
	textBot = bottom + TEXTMARGIN;
	textTop = top - TEXTMARGIN;
    }
    else if (style & ADJUSTABLE || GetVar(textBox, BACKGROUND))
    {
	textLeft = left + (HANDLESIZE + 2);
	textRight = right - (HANDLESIZE + 2);
	textBot = bottom + (HANDLESIZE + 2);
	textTop = top - (HANDLESIZE + 2);
    }
    else
    {
	textLeft = left;
	textRight = right;
	textBot = bottom;
	textTop = top;
    }
    width = style & ONE_LINE ? MAXINT : textRight - textLeft;

    SetupFont(fontName, size);
    tabWid = TABWID*ChrWidth('0');
/*
*	Build two arrays to keep character positions of text.
*	lnx[l] is the index of the first character of line l in text.
*	offset[i] is the total horiz offset to the middle of character i in text.
*/
    t = text; l = 0; nLines = 0; lnx[0] = 0; nTabs = 0; indent = 0; /* setup for loop */
    do {
	t = NextLine(t, width - indent, lineBuf);
	lnx[++l] = t - text; /* index to first char of next line */
	length = StrWidth(lineBuf);
		
	/* figure out where line begins */
	switch (align)
	{
	case CENTERALIGN:
		x = (textLeft + textRight)/2 - length/2;
		break;

	case RIGHTALIGN:
		x = textRight - length;
		break;

	case LEFTALIGN:
	default:
		x = textLeft + indent;
		break;
	}

	/* build next line of char offsets */
	for (i=lnx[l-1]; i<lnx[l]; ++i)
	{
		register int w;
		if (text[i] == '\t')
		{
			w = x;
			x = textLeft + ( indent = (++nTabs)*tabWid );
			offset[i] = (x + w)/2;
		}
		else
		{
			w = ChrWidth(text[i]);
			offset[i] = x + w/2;
			x += w;
		}
	}
	if (*t == '\n')
	{
		++t; /* paragraph */
		indent = 0;
		nTabs = 0;
	}
	if (l > MAXNLINES) break;
    } while (*t);

    nLines = l; /* number of lines of text */

    if (!IsSelected(textBox) && !GetVar(textBox, BACKGROUND) && !(style & ONE_LINE))
    {
	/* see if the click is actually on text; if not, no hit */
	l = (textTop - mouseY)/(size + lineSpace);
	if (l < 0 || l >= nLines) return ObjFalse; /* above or below text */
	if (mouseX < offset[lnx[l]] - ChrWidth(text[lnx[l]])
	    || mouseX > offset[lnx[l+1]-1])
		return ObjFalse; /* to left or right of text */
    }

    if (TOOL(flags) == T_HELP) /* help mode? */
    {
	ContextHelp(textBox);
	return ObjTrue;
    }
    
    /* return if not active */
    if (!GetPredicate(textBox, ACTIVATED)) return ObjTrue;

    /* return if not editable or adjustable */
    if (!(style & (EDITABLE | ADJUSTABLE))) return ObjTrue;
    
    if (style & ADJUSTABLE)	/* only adjustable TBs affect object selection */
    {
	if (!(flags & F_EXTEND) && !IsSelected(textBox))
	{
		/* new selection not already selected. Deselect the rest */
		DeselectAll();
	}

	if ((flags & F_EXTEND) && IsSelected(textBox) && !AmICurrent(textBox))
	{
		/*Deselect*/
		Select(textBox, false);
		return ObjTrue;
	}
	else if (!IsSelected(textBox))
	{
		/*Must select it*/
		Select(textBox, true);
	}
    }
    MakeMeCurrent(textBox);
    DrawMe(textBox);

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

    if (style & ADJUSTABLE) /* see if click is in frame */
    {
	Bool ml, mr, mb, mt;
	ml = mr = mb = mt = false;

	if (mouseX < left + HANDLESIZE) /* on left side */
	{
		if (mouseY > top - HANDLESIZE) /* top-left handle */
			mt = ml = true;
		else if (mouseY < bottom + HANDLESIZE) /* bottom-left handle */
			mb = ml = true;
		else if (mouseY > vCent - HANDLESIZE/2 && mouseY < vCent + HANDLESIZE/2)
			ml = true; /* middle-left handle */
		else ml = mr = mb = mt = true; /* in frame */
	}
	else if (mouseX > right - HANDLESIZE) /* on right side */
	{
		if (mouseY > top - HANDLESIZE) /* top-right handle */
			mt = mr = true;
		else if (mouseY < bottom + HANDLESIZE) /* bottom-right handle */
			mb = mr = true;
		else if (mouseY > vCent - HANDLESIZE/2 && mouseY < vCent + HANDLESIZE/2)
			mr = true; /* middle-right handle */
		else ml = mr = mb = mt = true; /* in frame */
	}
	else if (mouseY < bottom + HANDLESIZE) /* on bottom */
	{
		/* already handled (heh heh) corners */
		if (mouseX > hCent - HANDLESIZE/2 && mouseX < hCent + HANDLESIZE/2)
			mb = true; /* bottom middle handle */
		else ml = mr = mb = mt = true; /* in frame */
	}
	else if (mouseY > top - HANDLESIZE) /* on top */
	{
		/* already handled (heh heh) corners */
		if (mouseX > hCent - HANDLESIZE/2 && mouseX < hCent + HANDLESIZE/2)
			mt = true; /* middle top handle */
		else ml = mr = mb = mt = true; /* in frame */
	}
	else /* not on incredibly fancy frame */
	{
		if (!(style & EDITABLE)) return ObjTrue;
	}
	SaveForUndo(textBox);
	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. */
	    int initX = mouseX, initY = mouseY;
	    int newLeft, newRight, newBottom, newTop;
	    int oldNewLeft, oldNewRight, oldNewBottom, oldNewTop;

	    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_CONSTRAIN)
		{
		    /*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(textBox,
				newLeft, newRight, newBottom, newTop);
			oldNewLeft = newLeft;
			oldNewRight = newRight;
			oldNewBottom = newBottom;
			oldNewTop = newTop;
			DrawMe(textBox);
		}
	    }
	    DrawSkeleton(false);
	    if (logging)
	    {
		    char cmd[256];
		    MakeObjectName(tempStr, textBox);
		    sprintf(cmd, "set bounds %s [%d %d %d %d]\n",
			    tempStr, newLeft, newRight,
			    newBottom, newTop);
		    Log(cmd);
	    }
	    return ObjTrue;
	}
    }

    /* click is in text. First reset LASTKEY to 'no previous character' state */
    SetVar(textBox, LASTKEY, NewInt(-1));
    /* SaveForUndo(textBox); */
    
    if (flags & F_EXTEND)
    {
	i = GetInt(GetVar(textBox, BGNSEL));
    }
    else
    {
	/* find line and character position of mouse down */
	l = (textTop - mouseY)/(size + lineSpace);
	if (l < 0) i = 0;
	else if (l >= nLines)
	{
		i = lnx[nLines];
	}
	else
	{
		i=lnx[l];
		while (offset[i] - horOffset < mouseX && i<lnx[l+1]) ++i;
	}
    }

    /* now track mouse and find line and char pos of end of selection */
    /* auto-scroll horizontally if one-line text box */
    mX = mouseX; mY = mouseY; /* setup for at least one loop */
    cHorOffset = 0;
    cj = -1;	/* force redraw first time */
    do {
	l = (textTop - mY)/(size + lineSpace);
	if (l < 0) j = 0;
	else if (l >= nLines)
	{
		j = lnx[nLines];
	}
	else 
	{
	    if (style & ONE_LINE) /* auto scroll if necessary */
	    {
		if (mX < textLeft)
		{
		    /* scroll right */
		    if (horOffset >= 0) SetVar(textBox, HOROFFSET, NewInt(horOffset -= 2));
		}
		else if (mX > textRight)
		{
		    /* scroll left */
		    if (textLeft + StrWidth(text) - horOffset > textRight)
			    SetVar(textBox, HOROFFSET, NewInt(horOffset += 2));
		}
	    }
	    j=lnx[l];
	    while (offset[j] - horOffset < mX && j<lnx[l+1]) ++j;
	}
	if (j != cj || cHorOffset != horOffset)	/* selection or scroll has changed */
	{
	    if (i > j) /* make sure bgnSel < endSel */
	    {
		    SetVar(textBox, BGNSEL, NewInt(j));
		    SetVar(textBox, ENDSEL, NewInt(i));
	    }
	    else
	    {
		    SetVar(textBox, BGNSEL, NewInt(i));
		    SetVar(textBox, ENDSEL, NewInt(j));
	    }
	    DrawMe(textBox);
	    cj = j; cHorOffset = horOffset;	/* remember current j, horOffset */
	}
    } while (Mouse(&mX, &mY));

    if (i == j && flags & F_DOUBLECLICK)
    {
	/* select the word at insPt */
	while (i > 0)
	{
		if (IsAlphaNum(text[i-1])) --i;
		else break;
	}
	while (text[j] != '\0')
	{
		if (IsAlphaNum(text[j])) ++j;
		else break;
	}
	SetVar(textBox, BGNSEL, NewInt(i));
	SetVar(textBox, ENDSEL, NewInt(j));
    }
#endif
    return ObjTrue;
}

int IsAlphaNum(c)
char c;
{
    return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <='9';
}

static ObjPtr EditTextBox(textBox, key, flags)
ObjPtr textBox;
int key;
long flags;
{
#ifdef INTERACTIVE
    ObjPtr theObj, theText, theStyle, theInsPt, theEndSel;
    char *text;
    int i, j, len, style, insPt, endSel, horOffset, insOffset, lastKey;
    int left, right, bottom, top;

    if (!AmICurrent(textBox)) return ObjFalse; /* keyboard input not for me */

    theStyle = GetIntVar("EditTextBox", textBox, STYLE);
    if (!theStyle) return ObjTrue;
    style = GetInt(theStyle);
    if (!(style & EDITABLE)) return ObjTrue;

#if 0
    Not really reliable enough yet
    /*EMP Set up owner for faster redrawing*/
    if (style & ADJUSTABLE)
    {
	ObjPtr parent;
	parent = GetVar(textBox, PARENT);
	if (parent)
	{
	    if (key)
	    {printf("tempObscured is true\n");
		SetVar(parent, TEMPOBSCURED, ObjTrue);
	    }
	    else
	    {printf("tempObscured is false\n");
		SetVar(parent, TEMPOBSCURED, ObjFalse);
	    }
	}
    }
#endif
    Get2DIntBounds(textBox, &left, &right, &bottom, &top);

    /* adjust bounds */
    if (style & WITH_PIT)
    {
	    left += TEXTMARGIN;
	    right -= TEXTMARGIN;
	    bottom += TEXTMARGIN;
	    top -= TEXTMARGIN;
    }
    else if (style & ADJUSTABLE || GetVar(textBox, BACKGROUND))
    {
	    left += HANDLESIZE + 2;
	    right -= HANDLESIZE + 2;
	    bottom += HANDLESIZE + 2;
	    top -= HANDLESIZE + 2;
    }

    theText = GetStringVar("EditTextBox", textBox, VALUE);
    if (theText) text = GetString(theText);
    else text = "\0";
    
    if ((len = strlen(text)) > textBufSize) /* grow buffer */
    {
	    textBuf = (char *) realloc(textBuf, len + TEXTBUFFERINCR);
	    if (!textBuf) /* failed */
	    {
		    OMErr();
		    textBufSize = 0;
		    return NULLOBJ;
	    }
    }

    theObj = GetVar(textBox, LASTKEY);
    if (theObj) lastKey = GetInt(theObj);
    else lastKey = -1;

    theInsPt = GetVar(textBox, BGNSEL);
    if (!theInsPt) insPt = len;
    else insPt = GetInt(theInsPt);

    theEndSel = GetVar(textBox, ENDSEL);
    if (!theEndSel) endSel = len;
    else endSel = GetInt(theEndSel);

    /* copy up to the insertion point */
    for(i=0; i<insPt; ++i) textBuf[i] = text[i];
    
    if (style & ONE_LINE)
    {
	/* compute size for autoscroll */
	textBuf[i] = '\0';
	insOffset = StrWidth(textBuf);	/* width of text up to ins pt */
	theObj = GetVar(textBox, HOROFFSET);
	if (theObj) horOffset = GetInt(theObj);
	else horOffset = 0;
    }
    
    /* process every key press */
    if (key >= ' ' && key <= '~' || key == '\t' && !(style & ONE_LINE))
    {
	if (lastKey < 0 || lastKey == '\b')
	{
	    SaveForUndo(textBox);
	}
	SetVar(textBox, LASTKEY, NewInt(key));

	if (style & ONE_LINE && len + 1 - (endSel - insPt) > MAXLINE)
		return ObjTrue; /* no more text will fit */
	
	/* add key at insertion point */
	textBuf[insPt] = key;
	for(i = endSel, j = ++insPt; i < len; ++i, ++j)
		textBuf[j] = text[i];
	textBuf[j] = '\0';
	if (style & ONE_LINE) /* check if ins pt in view */
	{
	    int h = insOffset + ChrWidth(key) - horOffset - (right - left);
	    if (h > 0) /* scroll left */
		SetVar(textBox, HOROFFSET, NewInt(horOffset + h));
	}
	SetVar(textBox, VALUE, NewString(textBuf));
	SetVar(textBox, BGNSEL, NewInt(insPt));
	SetVar(textBox, ENDSEL, NewInt(insPt));
	SetVar(textBox, CHANGED, ObjTrue);
	DrawMe(textBox);
    }
    else if (key == '\b' || key == '\377')
    {
	if (lastKey < 0 || lastKey != '\b')
	{
	    SaveForUndo(textBox);
	}
	SetVar(textBox, LASTKEY, NewInt('\b'));
	
	if (insPt == endSel && insPt > 0) /* delete character before insPt */
	{
	    for(i = insPt, j = --insPt; i<len; ++i, ++j)
		    textBuf[j] = text[i];
	    textBuf[j] = '\0';
	}
	else /* just delete selection */
	{
	    for(i = endSel, j = insPt; i < len; ++i, ++j)
		    textBuf[j] = text[i];
	    textBuf[j] = '\0';
	}
	if (style & ONE_LINE && horOffset > insOffset) /* scroll right until ins pt is in view */
	{
	    if (insOffset < right - left) horOffset = 0;
	    else horOffset = insOffset - (right - left)/2;
	    SetVar(textBox, HOROFFSET, NewInt(horOffset));
	}
	SetVar(textBox, VALUE, NewString(textBuf));
	SetVar(textBox, BGNSEL, NewInt(insPt));
	SetVar(textBox, ENDSEL, NewInt(insPt));
	SetVar(textBox, CHANGED, ObjTrue);
	DrawMe(textBox);
    }
    else if (key == '\r')
    {
	    if (style & ONE_LINE) /* check for ENTERMETHOD */
	    {
		    FuncTyp EnterMethod = GetMethod(textBox, ENTERMETHOD);
		    if (EnterMethod) EnterMethod(textBox);
	    }
	    else /* put newline into string at insertion point */
	    {
		    if (lastKey < 0 || lastKey == '\b')
		    {
			SaveForUndo(textBox);
		    }
		    SetVar(textBox, LASTKEY, NewInt(key));

		    textBuf[insPt] = '\n';
		    for(i=insPt; i<len; ++i) textBuf[i+1] = text[i + endSel - insPt];
		    textBuf[len+1] = '\0';
		    SetVar(textBox, VALUE, NewString(textBuf));
		    SetVar(textBox, BGNSEL, NewInt(insPt + 1));
		    SetVar(textBox, ENDSEL, NewInt(insPt + 1));
		    SetVar(textBox, CHANGED, ObjTrue);
		    DrawMe(textBox);
	    }
    }
    else if (key == FK_LEFT_ARROW)
    {
	/* just move the insertion point (and endSel) back */
	if (insPt > 0) --insPt;
	if (style & ONE_LINE && horOffset > insOffset - ChrWidth(text[insPt]))
	{
	    /* scroll right until ins pt is in view */
	    if (insOffset < right - left) horOffset = 0;
	    else horOffset = insOffset - (right - left)/2;
	    SetVar(textBox, HOROFFSET, NewInt(horOffset));
	}
	SetVar(textBox, BGNSEL, theObj = NewInt(insPt));
	SetVar(textBox, ENDSEL, theObj);
	SetVar(textBox, LASTKEY, NewInt(-1));
	DrawMe(textBox);
    }
    else if (key == FK_RIGHT_ARROW)
    {
	/* just move endSel (and insPt) forward */
	if (endSel < len) ++endSel;
	if (style & ONE_LINE) /* check ins pt in view */
	{
	    int h = insOffset + ChrWidth(text[insPt]) - horOffset - (right - left);
	    if (h > 0) /* scroll left */
		SetVar(textBox, HOROFFSET, NewInt(horOffset + h));
	}
	SetVar(textBox, BGNSEL, theObj = NewInt(endSel));
	SetVar(textBox, ENDSEL, theObj);
	SetVar(textBox, LASTKEY, NewInt(-1));
	DrawMe(textBox);
    }
    else if (key == FK_UP_ARROW || key == FK_DOWN_ARROW)
    {
	if (style & ONE_LINE)
	{
	    /* just move to first or last char; scroll if necessary */
	    if (key == FK_UP_ARROW)
	    {
		SetVar(textBox, BGNSEL, theObj = NewInt(0));
		SetVar(textBox, ENDSEL, theObj);
		SetVar(textBox, HOROFFSET, theObj);
	    }
	    else
	    {
		int l = StrWidth(text);
		SetVar(textBox, BGNSEL, theObj = NewInt(len));
		SetVar(textBox, ENDSEL, theObj);
		if (l > right - left)
		{
		    SetVar(textBox, HOROFFSET, NewInt(l - (right - left)));
		}
		else
		{
		    SetVar(textBox, HOROFFSET, NewInt(0));
		}
	    }
	    SetVar(textBox, LASTKEY, NewInt(-1));
	    DrawMe(textBox);
	}
	else
	{
	    /* Oh, so you want to move the cursor up or down, eh? Well,
	       we can do that, it's no bother.  Really.  So what if we have 
	       to read all of the text and create a few arrays?  We don't
	       mind. It's no trouble at all, really.  Have a seat. */
    
	    int size, align, l, offset[MAXNLINES * MAXLINE], lnx[MAXNLINES + 1];
	    char *t, *fontName, lineBuf[MAXLINE + 1];
	    int width, length, tabWid, nTabs;
	    float x;
    
	    theObj = GetVar(textBox, ALIGNMENT);
	    if (theObj) align = GetInt(theObj);
	    else align = LEFTALIGN;
    
	    theObj = GetVar(textBox, TEXTFONT);
	    if (theObj) fontName = GetString(theObj);
	    else fontName = DEFFONT;
    
	    theObj = GetVar(textBox, TEXTSIZE);
	    if (theObj) size = GetInt(theObj);
	    else size = DEFTEXTSIZE;
    
	    MakeVar(textBox, BACKGROUND);
    
	    width = style & ONE_LINE ? MAXINT : right - left;

	    SetupFont(fontName, size);
	    tabWid = TABWID*ChrWidth('0');
    
	    t = text; l = 0; lnx[0] = 0; indent = 0; /* setup for loop */
	    do {
		    t = NextLine(t, width - indent, lineBuf);
		    lnx[++l] = t - text; /* index to first char of next line */
		    length = StrWidth(lineBuf);
		    
		    /* figure out where line begins */
		    switch (align)
		    {
		    case CENTERALIGN:
			    x = (left + right)/2 - length/2;
			    break;
    
		    case RIGHTALIGN:
			    x = right - length;
			    break;
    
		    case LEFTALIGN:
		    default:
			    x = left + indent;
			    break;
		    }
    
		    /* build next line of char offsets */
		    for (i=lnx[l-1]; i<lnx[l]; ++i)
		    {
			    register int w;
			    if (text[i] == '\t')
			    {
				    w = x;
				    x = left + ( indent = (++nTabs)*tabWid );
				    offset[i] = (x + w)/2;
			    }
			    else
			    {
				    w = ChrWidth(text[i]);
				    offset[i] = x + w/2;
				    x += w;
			    }
		    }
		    if (*t == '\n')
		    {
			    ++t; /* paragraph */
			    indent = 0;
			    nTabs = 0;
		    }
		    if (l > MAXNLINES) break;
	    } while (*t);
    
	    /* find the line containing insPt */
	    for (i=1; i<=l; ++i) if (lnx[i] >= insPt) break;
    
	    /* line is i-1 */
    
	    if (key == FK_UP_ARROW)
	    {
		    int k;
    
		    if (i-1 == 0) return ObjTrue; /* already on first line */
    
		    for (k=lnx[i-2]; k<lnx[i-1]; ++k)
			    if (offset[k] > offset[insPt]) break;
		    SetVar(textBox, BGNSEL, theObj = NewInt(k));
		    SetVar(textBox, ENDSEL, theObj);
		    SetVar(textBox, LASTKEY, NewInt(-1));
		    DrawMe(textBox);
	    }
	    else if (key == FK_DOWN_ARROW)
	    {
		    int k;
    
		    if (i == l) return ObjTrue; /* already on last line */
    
		    for (k=lnx[i]; k<lnx[i+1]; ++k)
			    if (offset[k] > offset[insPt]) break;
		    SetVar(textBox, BGNSEL, theObj = NewInt(k));
		    SetVar(textBox, ENDSEL, theObj);
		    SetVar(textBox, LASTKEY, NewInt(-1));
		    DrawMe(textBox);
	    }
	}
    }
    else if (key == '\0')
    {
	    if (logging) LogControl(textBox);
	    ChangedValue(textBox);

	    /*** MakeMeCurrent(NULLOBJ); ***/
    }
#endif
    return ObjTrue;
}

/************************************************************ CUT, COPY, PASTE*/
static ObjPtr CutText(textBox)
ObjPtr textBox;
{
	ObjPtr p;
	int i, j, len, style, insPt, endSel, horOffset, insOffset;
	char *text;
	Bool deferChange;

	if (p = GetStringVar("CutText", textBox, VALUE)) text = GetString(p);
	else text = "\0";
	
	len = strlen(text);

	if (p = GetVar(textBox, BGNSEL)) insPt = GetInt(p);
	else insPt = len;

	if (p = GetVar(textBox, ENDSEL)) endSel = GetInt(p);
	else endSel = len;
	
	if (insPt == endSel) return NewString("\0");
	
	SaveForUndo(textBox);
	
	style = GetInt(GetVar(textBox, STYLE));

	/* copy the selection to textBuf */
	for (i=insPt, j=0; i<endSel; ++i, ++j) textBuf[j] = text[i];
	textBuf[j] = '\0';

	if (style & ONE_LINE) /* check if scroll necessary */
	{
	    int left, right, bottom, top;
	    
	    Get2DIntBounds(textBox, &left, &right, &bottom, &top);
	    if (style & WITH_PIT)
	    {
		left = left + TEXTMARGIN;
		right = right - TEXTMARGIN;
	    }
	    text[insPt] = '\0'; insOffset = StrWidth(text);
	    if (p = GetVar(textBox, HOROFFSET)) horOffset = GetInt(p);
	    else horOffset = 0;
	    if (insOffset < horOffset) /* scroll right */
	    {
		if (insOffset < right - left) horOffset = 0;
		else horOffset = insOffset - (right - left)/2;
		SetVar(textBox, HOROFFSET, NewInt(horOffset));
	    }
	}

	/* delete selection */
	for (j=insPt, i=endSel; text[i]!='\0'; ++i, ++j) text[j] = text[i];
	text[j] = '\0';

	SetVar(textBox, VALUE, NewString(text));
	SetVar(textBox, ENDSEL, NewInt(insPt));
	SetVar(textBox, CHANGED, ObjTrue);
	
	ChangedValue(textBox);
	if (logging) LogControl(textBox);
	
	ImInvalid(textBox);
	return NewString(textBuf);
}

static ObjPtr CopyText(textBox)
ObjPtr textBox;
{
	ObjPtr p;
	int i, j, len, insPt, endSel;
	char *text;

	if (p = GetStringVar("CopyText", textBox, VALUE))
	    text = GetString(p);
	else text = "\0";
	
	len = strlen(text);

	if (p = GetVar(textBox, BGNSEL)) insPt = GetInt(p);
	else insPt = len;

	if (p = GetVar(textBox, ENDSEL)) endSel = GetInt(p);
	else endSel = len;

	/* copy the selection to textBuf */
	for (i=insPt, j=0; i<endSel; ++i, ++j) textBuf[j] = text[i];

	/* terminate and return */
	textBuf[j] = '\0';
	return NewString(textBuf);
}

static ObjPtr PasteText(textBox, newText)
ObjPtr textBox, newText;
{
    ObjPtr p;
    int i, j, len, style, insPt, endSel;
    char *text, *t;
    Bool deferChange;

    if (!IsString(newText)) /* check paste object */
    {
	    ReportError("PasteText", "Paste object not a string");
	    return NULLOBJ;
    }

    t = GetString(newText);

    if (p = GetStringVar("PasteText", textBox, VALUE))
	text = GetString(p);
    else text = "\0";
    
    len = strlen(text);

    if (p = GetVar(textBox, BGNSEL)) insPt = GetInt(p);
    else insPt = len;

    if (p = GetVar(textBox, ENDSEL)) endSel = GetInt(p);
    else endSel = len;

    if ((len += strlen(t)) > textBufSize) /* grow buffer */
    {
	textBuf = (char *) realloc(textBuf, len + TEXTBUFFERINCR);
	if (!textBuf) /* failed */
	{
	    OMErr();
	    textBufSize = 0;
	    return NULLOBJ;
	}
    }
    
    style = GetInt(GetVar(textBox, STYLE));
    if (style & ONE_LINE && len > MAXLINE) return NULLOBJ; /* won't fit */
    
    SaveForUndo(textBox);

    /* copy up to the insertion point */
    for (i=0; i<insPt; ++i) textBuf[i] = text[i];

    /* copy the paste text */
    for (j=0; t[j]!='\0'; ++i, ++j) textBuf[i] = t[j];

    insPt = i; /* remember new insertion point */

    if (style & ONE_LINE) /* check if scroll necessary */
    {
	int left, right, bottom, top, insOffset, horOffset;
	
	Get2DIntBounds(textBox, &left, &right, &bottom, &top);
	if (style & WITH_PIT)
	{
	    left = left + TEXTMARGIN;
	    right = right - TEXTMARGIN;
	}
	if (p = GetVar(textBox, HOROFFSET)) horOffset = GetInt(p);
	else horOffset = 0;
	textBuf[insPt] = '\0'; insOffset = StrWidth(textBuf);
	len = insOffset - horOffset - (right - left);
	if (len > 0) /* scroll left */
	    SetVar(textBox, HOROFFSET, NewInt(horOffset + len));
    }
    /* copy text after endSel */
    for (j=endSel; text[j]!='\0'; ++i, ++j) textBuf[i] = text[j];

    textBuf[i] = '\0';
    SetVar(textBox, VALUE, NewString(textBuf));
    SetVar(textBox, BGNSEL, p = NewInt(insPt));
    SetVar(textBox, ENDSEL, p);
    SetVar(textBox, CHANGED, ObjTrue);

    ChangedValue(textBox);
    if (logging) LogControl(textBox);
    
    ImInvalid(textBox);
    return NULLOBJ;
}

/********************************************************** SELECT ALL */
ObjPtr SelectAll(textBox)
ObjPtr textBox;
{
	SetVar(textBox, BGNSEL, NewInt(0));
	MakeVar(textBox, VALUE);
	SetVar(textBox, ENDSEL, NewInt(strlen(GetString(GetVar(textBox,VALUE)))));
	MakeMeCurrent(textBox);
	return ObjTrue;
}

static ObjPtr TextBoxNotCurrent(textBox)
ObjPtr textBox;
/*Makes a textbox not current*/
{
    ImInvalid(textBox);
    SetVar(textBox, LASTKEY, NewInt(-1));
}

/* adapted from ShowPaletteDisplayControls by Eric Pepke */
static ObjPtr ShowTextBoxControls(display, windowName)
ObjPtr display;
char *windowName;
/* Makes a new control window to control a textbox */
{
    WinInfoPtr controlWindow;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr corral;
    ObjPtr contents;
    real rgb[3], hsv[3];
    WinInfoPtr dialogExists;
    Bool hasBackground;

    dialogExists = DialogExists((WinInfoPtr) display, NewString("Controls"));
    controlWindow = GetDialog((WinInfoPtr) display, NewString("Controls"), windowName, 
	TBPALWINWIDTH, TBPALWINHEIGHT, TBPALWINWIDTH,
	TBPALWINHEIGHT, 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;

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

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

	/*Add in a panel*/
	GetWindowBounds(&left, &right, &bottom, &top);
	panel = NewPanel(greyPanelClass, 0, right - left, 0, top - bottom);
	if (!panel)
	{
	    return NULLOBJ;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);

	contents = GetVar(panel, CONTENTS);

	/*Add in the group of controls for text color*/
	left = MAJORBORDER;
	top = TBPALWINHEIGHT - MAJORBORDER;
	right = left + 4 * MAJORBORDER + COLORWHEELWIDTH + SLIDERWIDTH;
	
	titleBox = NewTitleBox(left, right,
			top - TITLEBOXTOP - MAJORBORDER - 2 * MINORBORDER - COLORWHEELWIDTH
				- CHECKBOXHEIGHT - TEXTBOXHEIGHT - TEXTBOXSEP,
			top, "Text");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);
	left += MAJORBORDER;
	right -= MAJORBORDER;
	top -= TITLEBOXTOP + MAJORBORDER;

	/*Get the color for priming the controls*/
	var = GetVar(display, COLOR);
	if (IsArray(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("ShowTextBoxControls", "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, "Text 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 text in the annotation.  \
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, "Text 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, "Text 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 in the annotation.  \
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);

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

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

	/*Get the color for priming the controls*/
	var = GetVar(display, BACKGROUND);
	if (var && IsArray(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 annotation.  \
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 annotation.  \
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);

	left -= MINORBORDER;	
	right += MINORBORDER;
	/*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);

	top -= CHECKBOXHEIGHT + MINORBORDER + MINORBORDER;
	left = MAJORBORDER;
	right = TBPALWINWIDTH - MAJORBORDER;
    }
    return (ObjPtr) controlWindow;
}

/************************************************************* LINE COUNT */
#ifdef PROTO
int LineCount(char *text, int width)
#else
int LineCount(text, width)
char *text;
int width;
#endif
{
	char lineBuf[MAXLINE + 1];
	int lineCount = 0;
	int tabWid = TABWID*ChrWidth('0');

	indent = 0;
	do {
		text = NextLine(text, width - indent, lineBuf);
		if (*text == '\n')
		{
			++text;
			indent = 0;
		}
		else indent += ChrCount(lineBuf, '\t')*tabWid;
		if (*lineBuf == '\0') break; /* no text or width too small */
		++lineCount;
	} while (*text);
	return lineCount;
}

/*************************************************************** NEXTLINE ******/

/*	This function assembles words into line buffer from text buffer. It
	returns a pointer to the next word in text or to terminator if done. 
	A newline indicates end of paragraph and terminates current line. The 
	return pointer points to the newline. All spaces are copied to the 
	new line except extra spaces that won't fit at the end of a line.  
	If a single word is too long for the line, it will be broken
	after the last letter that fits. If the line buffer is not big enough 
	to hold the number of characters that will fit in the width given, the 
	line will be terminated after the last word that fits.  Other than 
	newlines and soft eol (\r), no control characters should be in text.

	1/21/92: indentation scheme added. Now the tab character can be included
	in the text. There are fixed tab stops at multiples of TABWID * width
	of a digit in the current font. The indentation is a function only of the
	number of tabs, not the size of the text between tabs; to avoid overlapping
	text, it may be necessary to use multiple tabs. After the last tab, wrapped 
	lines will continue to be indented to that tab stop until a newline is 
	encountered. NOTE: Indentation only applies to left-aligned text.  
*/

static char *NextLine(text, width, line)
char *text; 	/* pointer into source text buffer */
int width;	/* width of destination space in pixels */
char *line; 	/* line output buffer MAXLINE+1 chars long */
{
	char *curWord;
	int inSpace, tabWid, nTabs;
	int totWid, n, m, spWid;

	spWid = ChrWidth(' ');
	tabWid = TABWID*ChrWidth('0');
	curWord = text;	/* point to beginning of current word in input text */
	totWid = n = m = 0;	/* reset offsets in output line */
	nTabs = 0;
	inSpace = true;	/* reading space flag */
	/* move words until:
		-fill up line buffer
		-width of characters in line buffer exceed given width
		-run out of input text
		-encounter newline
	*/
	while (n <= MAXLINE)
	{
		if (*text == '\r') /* "soft" EOL -- ignore */
		{
			++text;
		}
		else if (*text == ' ')
		{
			if (!inSpace)
			{
				/* the previous word fit; update fallback pointer */
				m = n;	/* where line will end if next word doesn't fit */
				inSpace = true;
			}
			curWord = text++;
			line[n++] = ' ';
			totWid += spWid;
		}
		else if (*text == '\t') /* handle tab */
		{
			if (!inSpace)
			{
				m = n;
				inSpace = true;
			}
			curWord = text++;
			line[n++] = '\t';
			totWid = (++nTabs)*tabWid;
		}
		else if (*text == '\n')
		{
			/* terminate line right here */
			if (inSpace) line[m] = '\0';
			else line[n] = '\0';
			return text;
		}
		else if (*text == '\0')
		{
			/* terminate line right here */
			if (inSpace) line[m] = '\0';
			else line[n] = '\0';
			return text;
		}
		else
		{
			/* non-space; move it */
			totWid += ChrWidth(*text);
			if (totWid > width) break;
			if (inSpace)	/* beginning new word */
			{
				curWord = text;
				inSpace = false;
			}
			line[n++] = *text++;
		}
	}
	if (m == 0 && !inSpace)	/* single word too long for line */
	{
		/* look for a good place to break it */
		char *b;

		line[n] = '\0';
		if ((b = strrchr(line, '/')) || (b = strrchr(line, '-')))
		{
			*(b + 1) = '\0';
			return text - (line + n - (b + 1));
		}
		else
		{
			/* oh, well, this is a good place */
			return text;
		}
	}
	else
	{
		line[m] = '\0';
		if (inSpace)	/* skip to next word */
		{
			while (*curWord==' ') ++curWord;
		}
		return curWord;
	}
}
