/*ScianTrackControls.c
  Eric Pepke
  7 February 1993

  Track controls for SciAn

  Track controls are used to edit time tracks, such as for keyframe animation.
*/

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

#define DENSITYFACTOR	1.50
#define CEDGE	3			/*Edge in control*/
#define MINNAMESPACE	90
#define MINNOTNAMESPACE	130

ObjPtr densityButtonClass;
ObjPtr pictureButtonClass;

/*Internal prototypes*/
#ifdef PROTO
static int TimeToPixel(ObjPtr control, real time);
static real PixelToTime(ObjPtr, int);
#else
static int TimeToPixel();
static real PixelToTime();
#endif

real timeSteps[] = 
    {
	0.001,
	0.05,
	0.1,
	0.25,
	0.5,
	1.0,
	5.0,
	10.0,
	15.0,
	30.0,
	60.0,
	300.0,
	600.0,
	900.0,
	1800.0,
	3600.0
    };

static ObjPtr trackControlClass;	/*Class of all track controls*/

#ifdef PROTO
void CalcGeneralSteps(double diff, int pixWidth, int minMajorPix, int minMinorPix, int nSteps, real *steps, double *majorWidth, double *minorWidth)
#else
void CalcGeneralSteps(diff, pixWidth, minMajorPix, minMinorPix, nSteps, steps, majorWidth, nMinorSteps)
double diff;
int pixWidth;
int minMajorPix;
int minMinorPix;
int nSteps;
real *steps;
double *majorWidth;
double *minorWidth;
#endif
/*Calculates good steps for a general scale
	diff is the difference between minimum and max
	pixWidth is the width in pixels between minimum and max
	minMajorPix is the minimum number of pixels between major tics
	minMinorPix is the minimum number of pixels between minor tics
	nSteps is the number of elements in the steps array
	steps is an array of strictly ascending real numbers giving steps
	*majorWidth will be the width of a major step
	*minorWidth will be the width of a minor step

  If steps doesn't get big enough, the function will go up in 5, 10 increments
  until arriving at a good value.  If steps doesn't get small enough, the
  function will return the smallest possibility.
*/
{
    Bool majorSet;
    Bool minorSet;
    int testPixelWidth;
    int k;
    Bool fiveP;
    real step;

    /*Start off with neither set*/
    majorSet = minorSet = false;

    /*First excessive increment will be a five*/

    for (k = 0; !(majorSet && minorSet); ++k)
    {
	if (k < nSteps)
	{
	    step = steps[k];
	}
	else
	{
	    if (fiveP)
	    {
		step *= 5.0;
		fiveP = false;
	    }
	    else
	    {
		step *= 2.0;
		fiveP = true;
	    }
	}

	testPixelWidth = step / diff * pixWidth;

	if (!majorSet)
	{
	    if (testPixelWidth > minMajorPix)
	    {
		majorSet = true;
		*majorWidth = step;
	    }
	}
	if (!minorSet)
	{
	    if (testPixelWidth > minMinorPix)
	    {
		minorSet = true;
		*minorWidth = step;
	    }
	}
    }
}

#define DBINSET		5

static ObjPtr DrawDensityButtonContents(theButton)
ObjPtr theButton;
/*Draws the contents of a density button*/
{
    int l, r, b, t;
    int x;
    int step;
    ObjPtr var;

    var = GetIntVar("DrawDensityButtonContents", theButton, TICDENSITY);
    if (!var)
    {
	return ObjFalse;
    }
    step = GetInt(var);

    Get2DIntBounds(theButton, &l, &r, &b, &t);

    for (x = l + DBINSET; x < r - DBINSET; x += step)
    {
	DrawUILine(x, t - DBINSET - 1, x, b + DBINSET, UIBLACK);
    }

    return ObjTrue;
}

static ObjPtr DrawPictureButton(theButton)
ObjPtr	theButton;
/*Method to draw a picture button.  Adapted from DrawButtonObj*/
{
#ifdef GRAPHICS
    int left, right, bottom, top;
    int		depth;
    Bool	hilight, active;
    ObjPtr	theLabel;
    char	*label;
    ObjPtr	theColor;
    int		color;
    FuncTyp method;

    if (!Get2DIntBounds(theButton, &left, &right, &bottom, &top))
    {
	return NULLOBJ;
    }

    if (IsDrawingRestricted(left, right, bottom, top))
    {
	return ObjFalse;
    }

    hilight = GetPredicate(theButton, HIGHLIGHTED);
    active = GetPredicate(theButton, ACTIVATED);

    theLabel = GetVar(theButton, NAME);
    if (theLabel)
    {
	label = GetString(theLabel);
    }
    else
    {
	label = (char *) NIL;
    }

    theColor = GetVar(theButton, COLOR);
    if (!theColor)
    {
	color = UIBACKGROUND;
    }
    else
    {
	color = GetPredicate(theButton, VALUE) ? GetInt(theColor) : UIBACKGROUND;
    }

    if (!active)
    {
	setpattern(GREYPAT);
	DrawRaisedRect(left, right, bottom, top, hilight ? UIHIBACKGROUND : color);
	setpattern(SOLIDPAT);
    }
    else
    {
	DrawRaisedRect(left, right, bottom, top, hilight ? UIHIBACKGROUND : color);
    }

    /*Draw the stuff on the top here*/
    method = GetMethod(theButton, DRAWCONTENTS);
    if (method)
    {
	(*method)(theButton);
    }

    return ObjTrue;
#endif /* GRAPHICS */
}

#ifdef PROTO
ObjPtr NewDensityButton(int l, int r, int b, int t, char *name, int density)
#else
ObjPtr NewDensityButton(l, r, b, t, name, density)
int l; int r; int b; int t; char *name; int density;
#endif
/*Creates a new density button*/
{
    ObjPtr retVal;

    retVal = NewObject(densityButtonClass, 0L);

    Set2DIntBounds(retVal, l, r, b, t);

    SetVar(retVal, VALUE, NewInt(0));
    SetVar(retVal, HIGHLIGHTED, ObjFalse);
    SetVar(retVal, NAME, NewString(name));
    SetVar(retVal, ACTIVATED, ObjTrue);
    SetVar(retVal, TICDENSITY, NewInt(density));
    return retVal;
}

#ifdef PROTO
static int TimeToPixel(ObjPtr control, real time)
#else
static int TimeToPixel(control, time)
ObjPtr control;
real time;
#endif
/*Returns the x pixel for a specific time in a track control.
  Check this against bounds of thing before drawing*/
{
    ObjPtr var, scrollbar;
    real value, tpp;
    int l, r, b, t, mid;
    int leftWidth;
    int pixWidth;

    Get2DIntBounds(control, &l, &r, &b, &t);
    
    var = GetIntVar("PixelToTime", control, LEFTSIDEWIDTH);
    if (var)
    {
	leftWidth = GetInt(var);
    }
    else
    {
	leftWidth = 100;
    }


    l += leftWidth + 2 * TC_GAP + BARWIDTH + 1;
    --r;

    pixWidth = r - l;

    MakeVar(control, TIMEPERPIXEL);
    var = GetRealVar("PixelToTime", control, TIMEPERPIXEL);
    if (var)
    {
	tpp = GetReal(var);
    }
    else
    {
	tpp = 0.1;
    }

    mid = (l + r) / 2;

    scrollbar = GetVar(control, HSCROLL);
    if (scrollbar)
    {
	var = GetValue(scrollbar);
	if (var)
	{
	    value = GetReal(var);
	}
	else
	{
	    value = 0.0;
	}
    }
    else
    {
	value = 0.0;
    }

    return (time - value) / tpp + 0.5 + mid;
}

static real PixelToTime(control, pixel)
ObjPtr control;
int pixel;
/*Returns the time for a specific x pixel in a track control.
  Check this against bounds of thing before drawing*/
{
    ObjPtr var, scrollbar;
    real value, tpp;
    int l, r, b, t, mid;
    int leftWidth;
    int pixWidth;

    Get2DIntBounds(control, &l, &r, &b, &t);
    
    var = GetIntVar("PixelToTime", control, LEFTSIDEWIDTH);
    if (var)
    {
	leftWidth = GetInt(var);
    }
    else
    {
	leftWidth = 100;
    }


    l += leftWidth + 2 * TC_GAP + BARWIDTH + 1;
    --r;

    pixWidth = r - l;

    MakeVar(control, TIMEPERPIXEL);
    var = GetRealVar("PixelToTime", control, TIMEPERPIXEL);
    if (var)
    {
	tpp = GetReal(var);
    }
    else
    {
	tpp = 0.1;
    }

    mid = (l + r) / 2;

    scrollbar = GetVar(control, HSCROLL);
    if (scrollbar)
    {
	var = GetValue(scrollbar);
	if (var)
	{
	    value = GetReal(var);
	}
	else
	{
	    value = 0.0;
	}
    }
    else
    {
	value = 0.0;
    }

    return (pixel - mid) * tpp + value;
}

static ObjPtr ChangeTrackControlVScroll(scrollbar)
ObjPtr scrollbar;
/*Changes in response to a vertical scrollbar*/
{
    ObjPtr repObj;
    int l, r, b, t, dummy;

    repObj = GetVar(scrollbar, REPOBJ);
    if (repObj)
    {
	Get2DIntBounds(repObj, &l, &r, &dummy, &dummy);
	Get2DIntBounds(scrollbar, &dummy, &dummy, &b, &t);
	ImInvalidBounds(repObj, l + 1, r - 1, b + 1, t - 1);
    }
    return ObjTrue;
}

static ObjPtr ChangeTrackControlHScroll(scrollbar)
ObjPtr scrollbar;
/*Changes in response to a horizontal scrollbar*/
{
    ObjPtr repObj;
    int l, r, b, t, dummy;

    repObj = GetVar(scrollbar, REPOBJ);
    if (repObj)
    {
	Get2DIntBounds(scrollbar, &l, &r, &dummy, &dummy);
	Get2DIntBounds(repObj, &dummy, &dummy, &b, &t);
	ImInvalidBounds(repObj, l + 1, r - 1, b + 1, t - 1);
    }
    return ObjTrue;
}

static ObjPtr MakeDenser(button)
ObjPtr button;
/*Makes a track control denser*/
{
    ObjPtr repObj, var;

    repObj = GetObjectVar("MakeDenser", button, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    MakeVar(repObj, TIMEPERPIXEL);
    var = GetVar(repObj, TIMEPERPIXEL);
    if (var)
    {
	SaveForUndo(repObj);
	SetVar(repObj, TIMEPERPIXEL, NewReal(GetReal(var) * DENSITYFACTOR));
	RecalcScroll(repObj);
	return ObjTrue;
    }

    return ObjFalse;
}

static ObjPtr MakeSparser(button)
ObjPtr button;
/*Makes a track control sparser*/
{
    ObjPtr repObj, var;

    repObj = GetObjectVar("MakeSparser", button, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    MakeVar(repObj, TIMEPERPIXEL);
    var = GetVar(repObj, TIMEPERPIXEL);
    if (var)
    {
	SaveForUndo(repObj);
	SetVar(repObj, TIMEPERPIXEL, NewReal(GetReal(var) / DENSITYFACTOR));
	RecalcScroll(repObj);
	return ObjTrue;
    }

    return ObjFalse;
}

#ifdef PROTO
ObjPtr NewTrackControl(int l, int r, int b, int t, char *name, real low, real high)
#else
ObjPtr NewTrackControl(l, r, b, t, name, low, high)
int l, r, b, t;
char *name;
real low, high;
#endif
/*Returns a new track control*/
{
    ObjPtr retVal, scrollbar, readout, button;
    int leftWidth;
    real min, max, portionShown;
    ObjPtr var;

    retVal = NewObject(trackControlClass, 0L);
    if (!retVal)
    {
	return NULLOBJ;
    }
    Set2DIntBounds(retVal, l, r, b, t);
    SetVar(retVal, NAME, NewString(name));

    /*Find out parameters of the control*/
    var = GetIntVar("NewTrackControl", retVal, LEFTSIDEWIDTH);
    if (var)
    {
	leftWidth = GetInt(var);
    }
    else
    {
	leftWidth = 100;
    }

    /*Create the scroll bars*/
#ifdef SCROLLINMIDDLE
    scrollbar = NewScrollbar(l + leftWidth + TC_GAP, l + leftWidth + TC_GAP + BARWIDTH,
			     b + BARWIDTH + TC_GAP,
			     t - TC_TIMEHEIGHT - TC_CURHEIGHT - TC_GAP,
			     "Track Scroll");
#else
    scrollbar = NewScrollbar(l, l + BARWIDTH,
			     b + BARWIDTH + TC_GAP,
			     t - TC_TIMEHEIGHT - TC_CURHEIGHT - TC_GAP,
			     "Track Scroll");
#endif
    SetVar(scrollbar, PARENT, retVal);
    SetVar(scrollbar, REPOBJ, retVal);
    SetMethod(scrollbar, CHANGEDVALUE, ChangeTrackControlVScroll);
    SetVar(retVal, VSCROLL, scrollbar);

    scrollbar = NewScrollbar(l + leftWidth + 2 * TC_GAP + BARWIDTH, r,
			     b,
			     b + BARWIDTH,
			     "Time Scroll");
    SetVar(scrollbar, PARENT, retVal);
    SetVar(scrollbar, REPOBJ, retVal);
    SetMethod(scrollbar, CHANGEDVALUE, ChangeTrackControlHScroll);
    SetVar(retVal, HSCROLL, scrollbar);

    /*Create the time readout*/
    readout = NewTextBox(l + TC_LEFTLABEL, l + leftWidth + TC_GAP + BARWIDTH,
	t - TC_TIMEHEIGHT + TC_GAP, t,
	EDITABLE + WITH_PIT + ONE_LINE, "Time Readout", "0.0");
/*
    SetMethod(readout, CHANGEDVALUE, ChangeTrackTimeReadout);
*/
    SetVar(readout, PARENT, retVal);
    SetTextAlign(readout, RIGHTALIGN);
    SetVar(retVal, READOUT, readout);

    /*Create the frame readout*/
    readout = NewTextBox(l + TC_LEFTLABEL, l + leftWidth + TC_GAP + BARWIDTH,
	t - TC_TIMEHEIGHT - TC_CURHEIGHT, t - TC_TIMEHEIGHT,
	EDITABLE + WITH_PIT + ONE_LINE, "Frame Readout", "0");
/*
    SetMethod(readout, CHANGEDVALUE, ChangeTrackFrameReadout);
*/
    SetVar(readout, PARENT, retVal);
    SetTextAlign(readout, RIGHTALIGN);
    SetVar(retVal, READOUT2, readout);

    SetVar(retVal, LOVALUE, NewReal(low));
    SetVar(retVal, HIVALUE, NewReal(high));

    SetVar(retVal, TIMEPERPIXEL, NewReal(TC_TPP));

    portionShown = (r - (l + leftWidth + TC_GAP * 2 + BARWIDTH) - 2) * TC_TPP;
    min = low + portionShown * (0.5 - TC_MARGIN);
    max = high - portionShown * (0.5 - TC_MARGIN);
    if (min > max)
    {
	    min = (min + max) * 0.5;
	    max = min;
    }
    SetSliderValue(scrollbar, min);
    SetSliderRange(scrollbar, min, max, portionShown / 10.0);

    SetVar(retVal, FRAMERATE, NewReal(30.0));
    SetVar(retVal, VALUE, NewReal(low));

    /*Now make buttons*/
    button = NewDensityButton(l + leftWidth - TC_DBWIDTH,
		l + leftWidth,
		b, b + BARWIDTH, "Expand", 6);
    SetVar(button, PARENT, retVal);
    SetVar(retVal, SPARSERBUTTON, button);
    SetVar(button, REPOBJ, retVal);
    SetMethod(button, CHANGEDVALUE, MakeSparser);

    button = NewDensityButton(l + leftWidth - TC_DBWIDTH * 2 - TC_GAP,
		l + BARWIDTH  + leftWidth - BARWIDTH - TC_DBWIDTH - TC_GAP,
		b, b + BARWIDTH, "Contract", 3);
    SetVar(button, PARENT, retVal);
    SetVar(retVal, DENSERBUTTON, button);
    SetVar(button, REPOBJ, retVal);
    SetMethod(button, CHANGEDVALUE, MakeDenser);

    return retVal;
}

ObjPtr DrawTrackControl(object)
ObjPtr object;
/*Draws a track control*/
{
#ifdef GRAPHICS
    ObjPtr hScroll, vScroll, readout, var, timeVar, button;
    int left, right, bottom, top, timePix, x, y, height;
    long k, lowestTrack;
    int shownHeight;
    real value;
    long downOffset;
    long line;
    int leftWidth;
    ObjPtr repObj;
    ObjPtr tracks;
    ObjPtr *elements;
    real loValue, hiValue;

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

    if (IsDrawingRestricted(left, right, bottom, top))
    {
	return ObjTrue;
    }

    /*Get the value*/
    var = GetRealVar("DrawTrackControl", object, VALUE);
    if (var)
    {
	value = GetReal(var);
    }
    else
    {
	value = 0.0;
    }

    /*Get the low and high values*/
    var = GetRealVar("DrawTrackControl", object, LOVALUE);
    if (var)
    {
	loValue = GetReal(var);
    }
    else
    {
	loValue = 0.0;
    }
    var = GetRealVar("DrawTrackControl", object, HIVALUE);
    if (var)
    {
	hiValue = GetReal(var);
    }
    else
    {
	hiValue = 0.0;
    }

    /*Find out parameters of the control*/
    var = GetIntVar("DrawTrackControl", object, LEFTSIDEWIDTH);
    if (var)
    {
	leftWidth = GetInt(var);
    }
    else
    {
	leftWidth = 100;
    }

    /*Get the repObj*/
    repObj = GetVar(object, REPOBJ);

    if (repObj)
    {
	tracks = GetVar(repObj, TRACKS);
    }
    else
    {
	tracks = NULLOBJ;
    }

    SetupFont(TCFONT, TCFONTSIZE);

    /*Draw the scroll bars*/
    hScroll = GetVar(object, HSCROLL);
    if (hScroll)
    {
	DrawObject(hScroll);
    }

    vScroll = GetVar(object, VSCROLL);
    if (vScroll)
    {
	DrawObject(vScroll);
    }

    /*Draw the readouts*/
    readout = GetVar(object, READOUT);
    if (readout)
    {
	DrawObject(readout);
    }

    readout = GetVar(object, READOUT2);
    if (readout)
    {
	DrawObject(readout);
    }

    /*Draw the buttons*/
    button = GetVar(object, DENSERBUTTON);
    if (button)
    {
	DrawObject(button);
    }

    button = GetVar(object, SPARSERBUTTON);
    if (button)
    {
	DrawObject(button);
    }

    /*Draw the arrows control*/
    x = left + leftWidth + TC_GAP + BARWIDTH / 2;
    y = bottom + BARWIDTH / 2;
    FillUITri(x - TC_ARROWGAP / 2, y + TC_ARROWSIZE / 2,
	      x - TC_ARROWGAP / 2 - TC_ARROWSIZE / 2, y,
	      x - TC_ARROWGAP / 2, y - TC_ARROWSIZE / 2, UIBLACK);
    FillUITri(x + TC_ARROWGAP / 2, y - TC_ARROWSIZE / 2,
	      x + TC_ARROWGAP / 2 + TC_ARROWSIZE / 2, y,
	      x + TC_ARROWGAP / 2, y + TC_ARROWSIZE / 2, UIBLACK);
    FillUIRect(x - TC_VLINEWIDTH / 2, x + TC_VLINEWIDTH / 2,
	       y - TC_VLINEHEIGHT / 2, y + TC_VLINEHEIGHT / 2, UIBLACK);

    /*Draw the track names box*/
#ifdef SCROLLINMIDDLE
    FillUIRect(left + 1, left + leftWidth - 1, bottom + TC_GAP + BARWIDTH + 1, top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1, UIGRAY62);
#else
    FillUIRect(left + BARWIDTH + TC_GAP + 1, left + BARWIDTH + TC_GAP + leftWidth - 1, bottom + TC_GAP + BARWIDTH + 1, top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1, UIGRAY62);
#endif

    /*Get downOffset from vertical scroll bar*/
    if (vScroll)
    {
	downOffset = (long) GetReal(GetValue(vScroll));
    }
    else
    {
	downOffset = 0;
    }

    if (tracks)
    {
	/*Draw the contents of the tracks*/

	elements = ELEMENTS(tracks);

	/*Search for a place to begin*/
	y = -downOffset;

	for (k = 0; k < DIMS(tracks)[0]; ++k)
	{
	    MakeVar(elements[k], TRACKHEIGHT);
	    var = GetVar(elements[k], TRACKHEIGHT);
	    if (var)
	    {
		height = GetInt(var);
		if ((y - height) < 0) break;
		y -= height;
	    }
	}

	if (k < DIMS(tracks)[0])
	{
	    /*There's something to print*/
	    FuncTyp method;

	    lowestTrack = k;

	    shownHeight = top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 
		  (bottom + TC_GAP + BARWIDTH) - 2;

#ifdef SCROLLINMIDDLE
	    SetClipRect(left + 1, left + leftWidth - 1, bottom + TC_GAP + BARWIDTH + 1, top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1);
#else
	    SetClipRect(left + BARWIDTH + TC_GAP + 1, left + BARWIDTH + TC_GAP + leftWidth - 1, bottom + TC_GAP + BARWIDTH + 1, top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1);
#endif
	    for (k = lowestTrack; (k < DIMS(tracks)[0]) && (y > -shownHeight); ++k)
	    {
		MakeVar(elements[k], TRACKHEIGHT);
		var = GetVar(elements[k], TRACKHEIGHT);
		if (var)
		{
		    height = GetInt(var);
		    method = GetMethod(elements[k], DRAWNAME);
		    if (method)
		    {
#ifdef SCROLLINMIDDLE
			(*method)(elements[k],
				left + 1, left + leftWidth - 1,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 + y - height,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 + y);
#else
			(*method)(elements[k],
				left + BARWIDTH + TC_GAP + 1, left + BARWIDTH + TC_GAP + leftWidth - 1,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 + y - height,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 + y);
#endif
		    }

		    y -= height;
		}
	    }
	    RestoreClipRect();
	}

    }

    /*Draw the timeline box*/
    FillUIRect(	left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
		right - 1,
		top - TC_TIMEHEIGHT,
		top - 1,
		UIGRAY62);

    FillUIRect(	left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
		right - 1,
		top - TC_TIMEHEIGHT - CEDGE,
		top - TC_TIMEHEIGHT,
		UIBOTTOMEDGE);
    FillUIRect(	left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
		right - 1,
		top - TC_TIMEHEIGHT - TC_CURHEIGHT + CEDGE,
		top - TC_TIMEHEIGHT - CEDGE,
		timeVar ? UIPGREEN : UIGRAY50);

    FillUIRect(left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
		right - 1,
		top - TC_TIMEHEIGHT - TC_CURHEIGHT,
		top - TC_TIMEHEIGHT - TC_CURHEIGHT + CEDGE,
		UITOPEDGE);

    FillUIRect(	left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
		right - 1,
		bottom + TC_GAP + BARWIDTH + 1,
		top - TC_TIMEHEIGHT - TC_CURHEIGHT - TC_GAP,
		UIGRAY62);

    FillUIRect(	left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
		right - 1,
		top - TC_TIMEHEIGHT - TC_CURHEIGHT - TC_GAP,
		top - TC_TIMEHEIGHT - TC_CURHEIGHT,
		UIGRAY62);

    /*Draw the contents of the timeline box*/
    if (hScroll)
    {
	ObjPtr var;
	char timeStr[256];
	int format;
	real timePerPixel, timeAtPixel;
	double majorWidth, minorWidth;
	double frameWidth;
	int curPixel;
	int timePixel;
	real curTime;
	int k;
	int pixWidth;
	real portionShown;

	MakeVar(object, TIMEFORMAT);
	var = GetVar(object, TIMEFORMAT);
	if (var)
	{
	    format = GetInt(var);
	}
	else
	{
	    format = TF_SECONDS + TF_SUBSECONDS;
	}

	/*Draw the time numbers*/

	pixWidth = (right - 1) - (left + leftWidth + 2 * TC_GAP + BARWIDTH + 1);

	MakeVar(object, TIMEPERPIXEL);
	var = GetRealVar("PixelToTime", object, TIMEPERPIXEL);
	if (var)
	{
	    portionShown = GetReal(var) * pixWidth;
	}
	else
	{
	    portionShown = 60.0;
	}
	timePerPixel = portionShown / ((real) pixWidth);

	/*Get the step and tics*/
	
	CalcGeneralSteps(portionShown, pixWidth, TC_MAJORSTEP, TC_MINORSTEP,
		sizeof(timeSteps) / sizeof(real), timeSteps, 
		&majorWidth, &minorWidth);

	/*Get a pixel that's way off the left*/
	timePixel = left + leftWidth + 2 * TC_GAP + BARWIDTH + 1 - TCMAXTIMEWIDTH;
	timeAtPixel = PixelToTime(object, timePixel);
	if (timeAtPixel < loValue - minorWidth * 0.5) timeAtPixel = loValue;
	timeAtPixel = floor(timeAtPixel / majorWidth) * majorWidth;
	timePixel = TimeToPixel(object, timeAtPixel);

	/*Write out the times*/
	SetClipRect(left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
			right - 1,
			bottom + 1,
			top - 1);
	SetupFont(TCTIMEFONT, TCTIMEFONTSIZE);
	SetUIColor(UIBLACK);

	curPixel = timePixel;
	curTime = timeAtPixel;
	do
	{
	    if (format)
	    {
		PrintTime(timeStr, curTime, format);
	    }
	    else
	    {
		sprintf(timeStr, "%g", curTime);
	    }
	    if (timeStr[0] == '-') strcat(timeStr, " ");
	    DrawAString(CENTERALIGN, curPixel, 
			   top - TC_TIMEHEIGHT + 1 + TC_TIMEBOFF,
			   timeStr);
	    DrawUILine(curPixel, top - TC_TIMEHEIGHT + TC_TIMEBOFF - 1,
		       curPixel, top - TC_TIMEHEIGHT, UIBLACK);
#if 0
	    DrawUILine(curPixel,
			bottom + BARWIDTH + TC_GAP + 1, curPixel,
			top - TC_TIMEHEIGHT - TC_CURHEIGHT - TC_GAP,
			UIBLACK);
#endif
	    curTime += majorWidth;
	    curTime = floor(curTime / majorWidth + 0.5) * majorWidth;
	    curPixel = TimeToPixel(object, curTime);
	}
	while (curPixel < right + TCMAXTIMEWIDTH &&
	       curTime < hiValue + minorWidth / 2);

	/*Now do minor tics*/
	curPixel = timePixel;
	curTime = timeAtPixel;
	do
	{
	    DrawUILine(curPixel, top - TC_TIMEHEIGHT + TC_TIMEBOFF / 2 - 1,
		       curPixel, top - TC_TIMEHEIGHT, UIBLACK);
	    curTime += minorWidth;
	    curTime = floor(curTime / minorWidth + 0.5) * minorWidth;
	    curPixel = TimeToPixel(object, curTime);
	}
	while (curPixel < right &&
	       curTime < hiValue + minorWidth / 2);

	/*Now do frame lines*/
	MakeVar(object, FRAMERATE);
	var = GetVar(object, FRAMERATE);
	if (var)
	{
	    frameWidth = 1.0 / GetReal(var);

	    /*Get a pixel that's way off the left*/
	    timePixel = left + leftWidth + 2 * TC_GAP + BARWIDTH;
	    timeAtPixel = PixelToTime(object, timePixel);
	    timeAtPixel = floor(timeAtPixel / frameWidth) * frameWidth;
	    if (timeAtPixel < loValue) timeAtPixel = loValue;
	    timePixel = TimeToPixel(object, timeAtPixel);

	    /*Test to see if it's worth doing*/
	    if (frameWidth / timePerPixel > 6.0)
	    {
		/*Now do minor tics*/
		curPixel = timePixel;
		curTime = timeAtPixel;
		do
		{
		    DrawUILine(curPixel,
			bottom + BARWIDTH + TC_GAP + 1, curPixel,
			top - TC_TIMEHEIGHT - TC_CURHEIGHT - TC_GAP - 1,
			UIGRAY50);
		    curTime += frameWidth;
		    curTime = floor(curTime / frameWidth + 0.5) * frameWidth;
		    curPixel = TimeToPixel(object, curTime);
		}
		while (curPixel < right &&
	 	       curTime < hiValue - frameWidth / 2);
	    }
	}

	RestoreClipRect();

	/*Draw the tracks*/
	if (tracks)
	{
	    /*Draw the contents of the tracks*/

	    elements = ELEMENTS(tracks);

	    /*Search for a place to begin*/
	    y = -downOffset;

	    for (k = 0; k < DIMS(tracks)[0]; ++k)
	    {
		MakeVar(elements[k], TRACKHEIGHT);
		var = GetVar(elements[k], TRACKHEIGHT);
		if (var)
		{
		    height = GetInt(var);
		    if ((y - height) < 0) break;
		    y -= height;
		}
	    }

	    if (k < DIMS(tracks)[0])
	    {
		/*There's something to draw*/
		FuncTyp method;
		real minTime, maxTime;

		minTime = PixelToTime(object, left + BARWIDTH + 2 * TC_GAP + leftWidth + 1);
		maxTime = PixelToTime(object, right - 1);

		lowestTrack = k;

		shownHeight = top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 
		  (bottom + TC_GAP + BARWIDTH) - 2;
		SetClipRect(left + BARWIDTH + 2 * TC_GAP + leftWidth + 1,
			    right - 1,
			    bottom + TC_GAP + BARWIDTH + 1,
			    top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1);
		for (k = lowestTrack; (k < DIMS(tracks)[0]) && (y > -shownHeight); ++k)
		{
		    MakeVar(elements[k], TRACKHEIGHT);
		    var = GetVar(elements[k], TRACKHEIGHT);
		    if (var)
		    {
			height = GetInt(var);
			method = GetMethod(elements[k], DRAWTRACK);
			if (method)
			{
			    (*method)(elements[k],
				left + BARWIDTH + 2 * TC_GAP + leftWidth + 1,
				right - 1,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 + y - height,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 + y,
				minTime, maxTime);
			}

			y -= height;
		    }
		}
		RestoreClipRect();
	    }
	}

#if 0
	/*Draw the time lines*/
	line = (-downOffset / TC_CELLHEIGHT);
	if (line < DIMS(datasets)[0])
	{
	    /*It's worth printing*/
	    int midy;
	    ObjPtr element;
	    SetClipRect(left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
			right - 1,
			bottom + TC_GAP + BARWIDTH + 1,
			top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1);
	    midy = top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 - (line + 1) * TC_CELLHEIGHT + TCLINEBOFF - downOffset;
	    SetUIColor(UIBLACK);
	    while (line < DIMS(datasets)[0] &&
		   midy > bottom + TC_GAP + BARWIDTH + 1 - TC_CELLHEIGHT)
	    {
		/*Print out the timeline of the object*/
		Bool interpolateP;

		ObjPtr timeSteps;
		element = GetObjectElement(datasets, &line);
		MakeVar(element, TIMESTEPS);
		timeSteps = GetVar(element, TIMESTEPS);
		MakeVar(element, INTERPOLATEP);
		interpolateP = GetPredicate(element, INTERPOLATEP);

		if (timeSteps)
		{
		    /*Object with time steps.  Find the first*/
		    int pixel;
		    long ts1, ts2;
		    real leftTime, rightTime;

		    /*Get a pixel that's clear off the left side*/
		    pixel = left;
		    leftTime = PixelToTime(object, pixel);

		    /*Get its index*/
		    ts1 = SearchReal(timeSteps, leftTime);
		    if (ts1 > 0)
		    {
			--ts1;
		    }
		    {
			/*There's a chance it can be drawn.  Get a pixel 
			  clear off the right*/
			pixel = right + TCSAMPLESIZE;
			rightTime = PixelToTime(object, pixel);

			/*Get its index*/
			ts2 = SearchReal(timeSteps, rightTime);
			if (ts2 >= DIMS(timeSteps)[0])
			{
			    --ts2;
			}
			if (ts2 >= ts1)
			{
			    /*DrawIt*/
			    real *timeElements;
			    long k;
			    long coords[2];
			    int p1, p2;

			    timeElements = (real *) ELEMENTS(timeSteps);
			    element = GetObjectElement(datasets, &line);

			    p1 = TimeToPixel(object, timeElements[ts1]);
			    for (k = ts1 + 1; k <= ts2; ++k)
			    {
				p2 = TimeToPixel(object, timeElements[k]);
				if (!interpolateP)
				{
				    DrawUILine((p1 + p2) / 2, midy + TCMIDTICHEIGHT,
					   (p1 + p2) / 2, midy - TCMIDTICHEIGHT - 1,
					   UIBLACK);
				    DrawUILine((p1 + p2) / 2 + 1, midy + TCMIDTICHEIGHT,
					   (p1 + p2) / 2 + 1, midy - TCMIDTICHEIGHT - 1,
					   UIBLACK);
				    setlinestyle(DASHEDLINE);
				}
				DrawUILine(p1, midy,
				           p2, midy,
				           UIBLACK);
				DrawUILine(p1, midy - 1,
				           p2, midy - 1,
				           UIBLACK);
				if (!interpolateP)
				{
				    setlinestyle(SOLIDLINE);
				}
				p1 = p2;
			    }
			    for (k = ts1; k <= ts2; ++k)
			    {
				pixel = TimeToPixel(object, timeElements[k]);
				SetUIColor(TCSAMPLECOLOR);
				FillRealQuad((real) pixel, (real) midy + TCSAMPLESIZE / 2,
					(real) pixel - TCSAMPLESIZE / 2, (real) midy,
					(real) pixel, (real) midy - TCSAMPLESIZE / 2,
					(real) pixel + TCSAMPLESIZE / 2, (real) midy);
			    }
			}
		    }
		}
		else
		{
		    /*Eternal object*/
		    SetLineWidth(2);
		    DrawUILine(left + leftWidth + 2 * TC_GAP + BARWIDTH + 1, midy,
			       right - 1, midy, TCSAMPLECOLOR);
		    SetLineWidth(1);
		}
		midy -= TC_CELLHEIGHT;
		++line;
	    }
	    RestoreClipRect();
	}
#endif
    }

    if (timeVar == NULLOBJ)
    {
	FillUIGauzeRect(left + leftWidth + 2 * TC_GAP + BARWIDTH + 1,
			right - 1,
			top - TC_TIMEHEIGHT - TC_CURHEIGHT,
			top - TC_TIMEHEIGHT,
			UIBACKGROUND);
    }

    if (timeVar)
    {
	timePix = TimeToPixel(object, value);
	if (timePix > left + leftWidth + 2 * TC_GAP + BARWIDTH - TCCURWIDTH &&
	    timePix < right + TCCURWIDTH)
	{
	    /*Draw the time cursor*/
	    SetClipRect(left + leftWidth + 2 * TC_GAP + BARWIDTH + 1, right - 1, bottom + TC_GAP + BARWIDTH + 1, top - 1);
	    DrawVCursor(timePix, top - 1 - TC_TIMEHEIGHT + TC_TIMEBOFF, bottom + TC_GAP + BARWIDTH + 1);
	    DrawRaisedRect(timePix - TCCURWIDTH / 2, timePix + TCCURWIDTH / 2,
		    top - TC_TIMEHEIGHT - TC_CURHEIGHT + 1 + CEDGE, top - TC_TIMEHEIGHT - 1 - CEDGE,
		    GetPredicate(object, HIGHLIGHTED) ? UIHIBACKGROUND : UIBACKGROUND);
	    RestoreClipRect();
	}
    }

    /*Draw the frames of the boxes*/
#ifdef SCROLLINMIDDLE
    FrameUIRect(left, left + leftWidth, bottom + TC_GAP + BARWIDTH, top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP, UIBLACK);
#else
    FrameUIRect(left + BARWIDTH + TC_GAP, left + BARWIDTH + TC_GAP + leftWidth, bottom + TC_GAP + BARWIDTH, top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP, UIBLACK);
#endif

    FrameUIRect(left + leftWidth + 2 * TC_GAP + BARWIDTH,
		right,
		bottom + TC_GAP + BARWIDTH,
		top, UIBLACK);
    DrawUILine(	left + leftWidth + 2 * TC_GAP + BARWIDTH,
		top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1,
		right - 1,
		top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1,
		UIBLACK);

    /*Draw "Time:" and "Frame:"*/
    SetUIColor(UIBLACK);
    SetupFont(TCFONT, TCFONTSIZE);
    DrawAString(LEFTALIGN, left, top - TC_TIMEHEIGHT + TC_GAP + TC_LABELUP, "Time:");

    SetupFont(TCFONT, TCFONTSIZE);
    DrawAString(LEFTALIGN, left, top - TC_TIMEHEIGHT - TC_CURHEIGHT + TC_LABELUP, "Frame:");
#endif
    return ObjTrue;
}

ObjPtr ReshapeTrackControl(object, ol, or, ob, ot, left, right, bottom, top)
ObjPtr object;
int ol, or, ob, ot, left, right, bottom, top;
/*Reshapes a track control*/
{
    ObjPtr var;
    int leftWidth, l, r, b, t, width, height;
    int stickiness;
    real wr, hr;			/*Width and height ratios*/

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

    /*Find out parameters of the control*/
    var = GetIntVar("DrawTrackControl", object, LEFTSIDEWIDTH);
    if (var)
    {
	leftWidth = GetInt(var);
    }
    else
    {
	leftWidth = 100;
    }

    /*Get the current bounds*/
    Get2DIntBounds(object, &l, &r, &b, &t);
    width = r - l;
    height = t - b;

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

    if ((stickiness & STICKYLEFT) || (stickiness & FLOATINGLEFT))
    {
	if (stickiness & FLOATINGLEFT)
	{
	    l = (l - ol) * wr + left;
	}
	else
	{
	    l += left - ol;
	}
	if (!((stickiness & STICKYRIGHT) || (stickiness & FLOATINGRIGHT)))
	{
	    r = l + width;
	}
    }

    if ((stickiness & STICKYRIGHT) || (stickiness & FLOATINGRIGHT))
    {
	if (stickiness & FLOATINGRIGHT)
	{
	    r = (r - ol) * wr + left;
	}
	else
	{
	    r += right - or;
	}
	if (!((stickiness & STICKYLEFT) || (stickiness & FLOATINGLEFT)))
	{
	    l = r - width;
	}
    }

    if ((stickiness & STICKYBOTTOM) || (stickiness & FLOATINGBOTTOM))
    {
	if (stickiness & FLOATINGBOTTOM)
	{
	    b = (b - ob) * hr + bottom;
	}
	else
	{
	    b += bottom - ob;
	}
	if (!((stickiness & STICKYTOP) || (stickiness & FLOATINGTOP)))
	{
	    t = b + height;
	}
    }

    if ((stickiness & STICKYTOP) || (stickiness & FLOATINGTOP))
    {
	if (stickiness & FLOATINGTOP)
	{
	    t = (t - ob) * hr + bottom;
	}
	else
	{
	    t += top - ot;
	}
	if (!((stickiness & STICKYBOTTOM) || (stickiness & FLOATINGBOTTOM)))
	{
	    b = t - height;
	}
    }
    Set2DIntBounds(object, l, r, b, t);

    /*Possibly adjust the left width*/
    if (r - l - leftWidth < MINNOTNAMESPACE)
    {
	leftWidth = r - l - MINNOTNAMESPACE;
	SetVar(object, LEFTSIDEWIDTH, NewInt(leftWidth));
    }

    /*Adjust the readouts*/
    var = GetVar(object, READOUT);
    if (var)
    {
	Set2DIntBounds(var, l + TC_LEFTLABEL, l + leftWidth + TC_GAP + BARWIDTH,
	t - TC_TIMEHEIGHT + TC_GAP, t);
    }

    var = GetVar(object, READOUT2);
    if (var)
    {
	Set2DIntBounds(var, l + TC_LEFTLABEL, l + leftWidth + TC_GAP + BARWIDTH,
	t - TC_TIMEHEIGHT - TC_CURHEIGHT, t - TC_TIMEHEIGHT);
    }

    /*Adjust the buttons*/
    var = GetVar(object, SPARSERBUTTON);
    if (var)
    {
	Set2DIntBounds(var, l + leftWidth - TC_DBWIDTH,
		l + leftWidth, b, b + BARWIDTH);
    }

    var = GetVar(object, DENSERBUTTON);
    if (var)
    {
	Set2DIntBounds(var, l + leftWidth - TC_DBWIDTH * 2 - TC_GAP,
		l + BARWIDTH  + leftWidth - BARWIDTH - TC_DBWIDTH - TC_GAP,
		b, b + BARWIDTH);
    }

    /*Adjust the scroll bars*/
    var = GetVar(object, VSCROLL);
    if (var)
    {
#ifdef SCROLLINMIDDLE
	Set2DIntBounds(var, l + leftWidth + TC_GAP, l + leftWidth + TC_GAP + BARWIDTH,
			     b + BARWIDTH + TC_GAP,
			     t - TC_TIMEHEIGHT - TC_CURHEIGHT - TC_GAP);
#else
	Set2DIntBounds(var, l, l + BARWIDTH,
			     b + BARWIDTH + TC_GAP,
			     t - TC_TIMEHEIGHT - TC_CURHEIGHT - TC_GAP);
#endif
    }
    var = GetVar(object, HSCROLL);
    if (var)
    {
	Set2DIntBounds(var, l + leftWidth + 2 * TC_GAP + BARWIDTH, r,
			     b,
			     b + BARWIDTH);
    }

    RecalcScroll(object);

    return ObjTrue;
}

ObjPtr TrackControlBoundsInvalid(object, changeCount)
ObjPtr object;
unsigned long changeCount;
/*For a time control, tests to see if it needs drawing.  Returns
  NULLOBJ	if it does not
  array[4]	giving bounds if it does
  ObjTrue	it it needs to be redrawn but does not know where
*/
{
    ObjPtr myBounds;
    real boundsElements[4];
    real testElements[4];
    ObjPtr test;
    real myBoundsElements[4];
    ObjPtr scrollBar, readout, button;
    Bool firstTime = true;
    Bool doubleNoBounds = false;
    ObjPtr tracks, repObj;

    MakeVar(object, APPEARANCE);

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

	myBounds = GetVar(object, CHANGEDBOUNDS);
	if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
		&& DIMS(myBounds)[0] == 4)
	{
	    return myBounds;
	}
	else
	{
	    return ObjTrue;
	}
    }

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


	scrollBar = GetVar(object, HSCROLL);
	if (scrollBar);
	{
	    test = BoundsInvalid(scrollBar, changeCount);
	    if (test)
	    {
		/*Hey, the scroll bar needs redrawing*/
		if (IsRealArray(test))
		{
		    /*It has a bounds*/
		    if (firstTime)
		    {
			Array2CArray(boundsElements, test);
		    }
		    else
		    {
			Array2CArray(testElements, test);
			if (testElements[0] < boundsElements[0])
				boundsElements[0] = testElements[0];
			if (testElements[1] > boundsElements[1])
				boundsElements[1] = testElements[1];
			if (testElements[2] < boundsElements[2])
				boundsElements[2] = testElements[2];
			if (testElements[3] > boundsElements[3])
				boundsElements[3] = testElements[3];
		    }
		}
		else
		{
		    /*It doesn't have a bounds*/
		    if (firstTime)
		    {
			/*Try to use my bounds*/
			if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
				&& DIMS(myBounds)[0] == 4)
			{
			    Array2CArray(boundsElements, myBounds);
			}
			else
			{
			    doubleNoBounds = true;
			}
		    }
		}
		firstTime = false;
	    }
	}

	scrollBar = GetVar(object, VSCROLL);
	if (scrollBar);
	{
	    test = BoundsInvalid(scrollBar, changeCount);
	    if (test)
	    {
		/*Hey, the scroll bar needs redrawing*/
		if (IsRealArray(test))
		{
		    /*It has a bounds*/
		    if (firstTime)
		    {
			Array2CArray(boundsElements, test);
		    }
		    else
		    {
			Array2CArray(testElements, test);
			if (testElements[0] < boundsElements[0])
				boundsElements[0] = testElements[0];
			if (testElements[1] > boundsElements[1])
				boundsElements[1] = testElements[1];
			if (testElements[2] < boundsElements[2])
				boundsElements[2] = testElements[2];
			if (testElements[3] > boundsElements[3])
				boundsElements[3] = testElements[3];
		    }
		}
		else
		{
		    /*It doesn't have a bounds*/
		    if (firstTime)
		    {
			/*Try to use my bounds*/
			if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
				&& DIMS(myBounds)[0] == 4)
			{
			    Array2CArray(boundsElements, myBounds);
			}
			else
			{
			    doubleNoBounds = true;
			}
		    }
		}
		firstTime = false;
	    }
	}

	readout = GetVar(object, READOUT);
	if (readout)
	{
	    test = BoundsInvalid(readout, changeCount);
	    if (test)
	    {
		/*Hey, the scroll bar needs redrawing*/
		if (IsRealArray(test))
		{
		    /*It has a bounds*/
		    if (firstTime)
		    {
			Array2CArray(boundsElements, test);
		    }
		    else
		    {
			Array2CArray(testElements, test);
			if (testElements[0] < boundsElements[0])
				boundsElements[0] = testElements[0];
			if (testElements[1] > boundsElements[1])
				boundsElements[1] = testElements[1];
			if (testElements[2] < boundsElements[2])
				boundsElements[2] = testElements[2];
			if (testElements[3] > boundsElements[3])
				boundsElements[3] = testElements[3];
		    }
		}
		else
		{
		    /*It doesn't have a bounds*/
		    if (firstTime)
		    {
			/*Try to use my bounds*/
			if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
				&& DIMS(myBounds)[0] == 4)
			{
			    Array2CArray(boundsElements, myBounds);
			}
			else
			{
			    doubleNoBounds = true;
			}
		    }
		}
		firstTime = false;
	    }
	}

	readout = GetVar(object, READOUT2);
	if (readout)
	{
	    test = BoundsInvalid(readout, changeCount);
	    if (test)
	    {
		/*Hey, the scroll bar needs redrawing*/
		if (IsRealArray(test))
		{
		    /*It has a bounds*/
		    if (firstTime)
		    {
			Array2CArray(boundsElements, test);
		    }
		    else
		    {
			Array2CArray(testElements, test);
			if (testElements[0] < boundsElements[0])
				boundsElements[0] = testElements[0];
			if (testElements[1] > boundsElements[1])
				boundsElements[1] = testElements[1];
			if (testElements[2] < boundsElements[2])
				boundsElements[2] = testElements[2];
			if (testElements[3] > boundsElements[3])
				boundsElements[3] = testElements[3];
		    }
		}
		else
		{
		    /*It doesn't have a bounds*/
		    if (firstTime)
		    {
			/*Try to use my bounds*/
			if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
				&& DIMS(myBounds)[0] == 4)
			{
			    Array2CArray(boundsElements, myBounds);
			}
			else
			{
			    doubleNoBounds = true;
			}
		    }
		}
		firstTime = false;
	    }
	}

	button = GetVar(object, DENSERBUTTON);
	if (button)
	{
	    test = BoundsInvalid(button, changeCount);
	    if (test)
	    {
		/*Hey, the scroll bar needs redrawing*/
		if (IsRealArray(test))
		{
		    /*It has a bounds*/
		    if (firstTime)
		    {
			Array2CArray(boundsElements, test);
		    }
		    else
		    {
			Array2CArray(testElements, test);
			if (testElements[0] < boundsElements[0])
				boundsElements[0] = testElements[0];
			if (testElements[1] > boundsElements[1])
				boundsElements[1] = testElements[1];
			if (testElements[2] < boundsElements[2])
				boundsElements[2] = testElements[2];
			if (testElements[3] > boundsElements[3])
				boundsElements[3] = testElements[3];
		    }
		}
		else
		{
		    /*It doesn't have a bounds*/
		    if (firstTime)
		    {
			/*Try to use my bounds*/
			if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
				&& DIMS(myBounds)[0] == 4)
			{
			    Array2CArray(boundsElements, myBounds);
			}
			else
			{
			    doubleNoBounds = true;
			}
		    }
		}
		firstTime = false;
	    }
	}

	button = GetVar(object, SPARSERBUTTON);
	if (button)
	{
	    test = BoundsInvalid(button, changeCount);
	    if (test)
	    {
		/*Hey, the scroll bar needs redrawing*/
		if (IsRealArray(test))
		{
		    /*It has a bounds*/
		    if (firstTime)
		    {
			Array2CArray(boundsElements, test);
		    }
		    else
		    {
			Array2CArray(testElements, test);
			if (testElements[0] < boundsElements[0])
				boundsElements[0] = testElements[0];
			if (testElements[1] > boundsElements[1])
				boundsElements[1] = testElements[1];
			if (testElements[2] < boundsElements[2])
				boundsElements[2] = testElements[2];
			if (testElements[3] > boundsElements[3])
				boundsElements[3] = testElements[3];
		    }
		}
		else
		{
		    /*It doesn't have a bounds*/
		    if (firstTime)
		    {
			/*Try to use my bounds*/
			if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
				&& DIMS(myBounds)[0] == 4)
			{
			    Array2CArray(boundsElements, myBounds);
			}
			else
			{
			    doubleNoBounds = true;
			}
		    }
		}
		firstTime = false;
	    }
	}

    MakeVar(object, REPOBJ);
    repObj = GetVar(object, REPOBJ);
    if (repObj)
    {
    MakeVar(repObj, TRACKS);
    tracks = GetVar(repObj, TRACKS);
    if (tracks)
    {
	/*Draw the contents of the tracks*/
	int downOffset, y, height, lowestTrack, shownHeight;
	long k;
	ObjPtr var, *elements;

	if (scrollBar)
	{
	    downOffset = (long) GetReal(GetValue(scrollBar));
	}
	else
	{
	    downOffset = 0;
	}

	elements = ELEMENTS(tracks);

	/*Search for a place to begin*/
	y = -downOffset;

	for (k = 0; k < DIMS(tracks)[0]; ++k)
	{
	    MakeVar(elements[k], TRACKHEIGHT);
	    var = GetVar(elements[k], TRACKHEIGHT);
	    if (var)
	    {
		height = GetInt(var);
		if ((y - height) < 0) break;
		y -= height;
	    }
	}

	if (k < DIMS(tracks)[0])
	{
	    /*There's something to check*/
	    FuncTyp method;

	    lowestTrack = k;

	    shownHeight = myBoundsElements[3] - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 
		  (myBoundsElements[2] + TC_GAP + BARWIDTH) - 2;

	    for (k = lowestTrack; (k < DIMS(tracks)[0]) && (y > -shownHeight); ++k)
	    {
		MakeVar(elements[k], TRACKHEIGHT);
		var = GetVar(elements[k], TRACKHEIGHT);
		if (var)
		{
		    height = GetInt(var);
		    MakeVar(elements[k], APPEARANCE);
		    if (GetVarChangeCount(elements[k], APPEARANCE) > changeCount)
		    {
			real tl, tr, tb, tt;

			tl = myBoundsElements[0] + 1;
			tr = myBoundsElements[1] - 1;
			tb = myBoundsElements[3] - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 + y - height,
			tt = myBoundsElements[3] - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1 + y;
			if (firstTime)
			{
			    boundsElements[0] = tl;
			    boundsElements[1] = tr;
			    boundsElements[2] = tb;
			    boundsElements[3] = tt;
			    firstTime = false;
			}
			else
			{
			    if (tl < boundsElements[0])
				boundsElements[0] = tl;
			    if (tr > boundsElements[1])
				boundsElements[1] = tr;
			    if (tb < boundsElements[2])
				boundsElements[2] = tb;
			    if (tt > boundsElements[3])
				boundsElements[3] = tt;
			}
		    }

		    y -= height;
		}
	    }
	}
    }
    }
 
	/*Now return the bounds*/
	if (firstTime != true)
	{
	    if (doubleNoBounds)
	    {
		return ObjTrue;
	    }
	    else
	    {
		ObjPtr retVal;
		retVal = NewRealArray(1, 4L);
		CArray2Array(retVal, boundsElements);
		return retVal;
	    }
	}

    return NULLOBJ;
}

#ifdef PROTO
static void UpdateTrackReadouts(ObjPtr control)
#else
static void UpdateTrackReadouts(control)
ObjPtr control;
#endif
/*Updates the readouts in a track control*/
{
    ObjPtr readout1, readout2, clock, format, var;
    char timeStr[256];
    char frameStr[256];
    FuncTyp method;
    real frameWidth, time;

    readout1 = GetObjectVar("UpdateTrackReadouts", control, READOUT);
    if (!readout1)
    {
	return;
    }

    readout2 = GetVar(control, READOUT2);

    var = GetVar(control, VALUE);
    if (var)
    {
	time = GetReal(var);

	MakeVar(control, TIMEFORMAT);
	format = GetVar(control, TIMEFORMAT);
	if (format)
	{
	    PrintTime(timeStr, time, GetInt(format));
	}
	else
	{
	    sprintf(timeStr, "%g", time);
	}

	MakeVar(control, FRAMERATE);
	var = GetVar(control, FRAMERATE);
	if (var)
	{
	    frameWidth = 1.0 / GetReal(var);
	    sprintf(frameStr, "%g", floor(time / frameWidth + 0.5));
	}
	else
	{
	    strcpy(frameStr, "");
	}
    }
    else
    {
	strcpy(timeStr, "");
	strcpy(frameStr, "");
    }
    InhibitLogging(true);

    if (readout1)
    {
	method = GetMethod(readout1, CHANGEDVALUE);
	SetMethod(readout1, CHANGEDVALUE, (FuncTyp) 0);
	SetTextBox(readout1, timeStr);
	SetMethod(readout1, CHANGEDVALUE, method);
    }

    if (readout2)
    {
	method = GetMethod(readout2, CHANGEDVALUE);
	SetMethod(readout2, CHANGEDVALUE, (FuncTyp) 0);
	SetTextBox(readout2, frameStr);
	SetMethod(readout2, CHANGEDVALUE, method);
    }

    InhibitLogging(false);
}


static ObjPtr KeyDownTrackControl(object, key, flags)
ObjPtr object;
int key;
long flags;
/*Does a keydown in a track control*/
{
    ObjPtr readout;
    if (key == 0)
    {
	/*Do nothing with timeout*/
	return ObjTrue;
    }

    if (AmICurrent(object) &&
	(key == FK_LEFT_ARROW || key == FK_RIGHT_ARROW ||
	 key == FK_UP_ARROW || key == FK_DOWN_ARROW))
    {
	/*It's a keypress here*/
	ObjPtr var;
	real value, frameWidth;

	MakeVar(object, FRAMERATE);
	var = GetVar(object, FRAMERATE);
	if (!var)
	{
	    return ObjTrue;
	}
	frameWidth = 1.0 / GetReal(var);

	var = GetRealVar("KeyDownTrackControl", object, VALUE);
	if (!var)
	{
	    return ObjTrue;
	}
	value = GetReal(var);

#ifdef INTERACTIVE
	interactiveMoving = false;
#endif
	switch (key)
	{
	    case FK_LEFT_ARROW:
		SetValue(object, NewReal((floor(value / frameWidth) - 1.0) * frameWidth));
		break;		
	    case FK_RIGHT_ARROW:
		SetValue(object, NewReal((floor(value / frameWidth) + 1.0) * frameWidth));
		break;
	}
	AutoScroll(object);
    }

    readout = GetVar(object, READOUT);
    if (readout || AmICurrent(readout))
    {
	return KeyDownObject(readout, key, flags);
    }

    readout = GetVar(object, READOUT2);
    if (readout || AmICurrent(readout))
    {
	return KeyDownObject(readout, key, flags);
    }
    return ObjFalse;
}

static ObjPtr PressTrackControl(object, x, y, flags)
ObjPtr object;
int x, y;
long flags;
/*Does a press in a track control beginning at x and y.  Returns
  true iff the press really was in the control.*/
{
#ifdef INTERACTIVE
    int left, right, bottom, top;
    ObjPtr scrollbar, textBox, button;
    int leftWidth;
    ObjPtr var;

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

    /*Kludge to make it not flash*/
    SetVar(object, OPAQUE, ObjTrue);

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

    /*Now the readouts*/
    textBox = GetVar(object, READOUT);
    if (textBox)
    {
	if (IsTrue(PressObject(textBox, x, y, flags)))
	{
	    return ObjTrue;
	}
    }

    textBox = GetVar(object, READOUT2);
    if (textBox)
    {
	if (IsTrue(PressObject(textBox, x, y, flags)))
	{
	    return ObjTrue;
	}
    }

    /*Now the buttons*/
    button = GetVar(object, DENSERBUTTON);
    if (button)
    {
	if (IsTrue(PressObject(button, x, y, flags)))
	{
	    return ObjTrue;
	}
    }

    /*Now the buttons*/
    button = GetVar(object, SPARSERBUTTON);
    if (button)
    {
	if (IsTrue(PressObject(button, x, y, flags)))
	{
	    return ObjTrue;
	}
    }

    var = GetIntVar("DrawTrackControl", object, LEFTSIDEWIDTH);
    if (var)
    {
	leftWidth = GetInt(var);
    }
    else
    {
	leftWidth = 100;
    }

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	/*Hey!  It really was a click in the track control*/
	ObjPtr tracks;
	ObjPtr vScroll;
	long downOffset;
	ObjPtr repObj;

	if (TOOL(flags) == T_ROTATE)
	{
	    flags |= F_EXTEND;
	}

	if (x >= left + leftWidth + TC_GAP && x <= left + leftWidth + TC_GAP + BARWIDTH &&
	    y >= bottom && y <= bottom + BARWIDTH)
	{
	    /*It's a press in the shape adjust control*/
	    int newX, newY;
	    int baseX;
	    int newLeftWidth, oldLeftWidth;
	    Bool insideP;

	    SetVar(object, OPAQUE, ObjFalse);

	    baseX = x;
	    OverDraw(true);

	    oldLeftWidth = leftWidth;
	    insideP = true;

	    EraseAll();
#ifdef SCROLLINMIDDLE
	    FrameUIRect(left, left + leftWidth, bottom + TC_GAP + BARWIDTH,
		top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP, UIRED);
#else
	    FrameUIRect(left + BARWIDTH + TC_GAP, left + BARWIDTH + TC_GAP + leftWidth, bottom + TC_GAP + BARWIDTH,
		top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP, UIRED);
#endif

	    FrameUIRect(left + leftWidth + 2 * TC_GAP + BARWIDTH,
		right,
		bottom + TC_GAP + BARWIDTH,
		top, UIRED);

	    while (Mouse(&newX, &newY))
	    {
		if (newX != x || newY != y)
		{
		    x = newX;
		    y = newY;

		    /*Mouse has moved.  First see about a transition*/
		    if (insideP)
		    {
			if (newX < left || newX > right ||
			    newY < bottom || newY > top)
			{
			    /*Transition to outside*/
			    insideP = false;
			    EraseAll();
			    leftWidth = oldLeftWidth;

#ifdef SCROLLINMIDDLE
			    FrameUIRect(left, left + leftWidth, bottom + TC_GAP + BARWIDTH,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP, UIRED);
#else
			    FrameUIRect(left + BARWIDTH + TC_GAP, left + BARWIDTH + TC_GAP + leftWidth, bottom + TC_GAP + BARWIDTH,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP, UIRED);
#endif

			    FrameUIRect(left + leftWidth + 2 * TC_GAP + BARWIDTH,
				right,
				bottom + TC_GAP + BARWIDTH,
				top, UIRED);
			    
			    continue;
			}
		    }
		    else
		    {
			if (newX < left || newX > right ||
			    newY < bottom || newY > top)
			{
			    /*Still outside*/
			    continue;
			}
			else
			{
			    insideP = true;
			}
		    }

		    /*Make a new left width*/
		    newLeftWidth = oldLeftWidth + newX - baseX;

		    if (newLeftWidth < MINNAMESPACE) newLeftWidth = MINNAMESPACE;
		    if (newLeftWidth > right - left - MINNOTNAMESPACE) newLeftWidth = right - left - MINNOTNAMESPACE;

		    if (newLeftWidth != leftWidth)
		    {
			leftWidth = newLeftWidth;
			EraseAll();
#ifdef SCROLLINMIDDLE
			    FrameUIRect(left, left + leftWidth, bottom + TC_GAP + BARWIDTH,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP, UIRED);
#else
			    FrameUIRect(left + BARWIDTH + TC_GAP, left + BARWIDTH + TC_GAP + leftWidth, bottom + TC_GAP + BARWIDTH,
				top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP, UIRED);
#endif

			FrameUIRect(left + leftWidth + 2 * TC_GAP + BARWIDTH,
				right,
				bottom + TC_GAP + BARWIDTH,
				top, UIRED);
		    }
		}
	    }

	    EraseAll();
	    OverDraw(false);

	    if (leftWidth != oldLeftWidth)
	    {
		/*Have to set it*/
		int l, r, b, t;

		SetVar(object, LEFTSIDEWIDTH, NewInt(leftWidth));

		var = GetVar(object, READOUT);
		if (var)
		{
		    Get2DIntBounds(var, &l, &r, &b, &t);
		    r += leftWidth - oldLeftWidth;
		    Set2DIntBounds(var, l, r, b, t);
		}

		var = GetVar(object, READOUT2);
		if (var)
		{
		    Get2DIntBounds(var, &l, &r, &b, &t);
		    r += leftWidth - oldLeftWidth;
		    Set2DIntBounds(var, l, r, b, t);
		}

		/*Adjust the buttons*/
		var = GetVar(object, SPARSERBUTTON);
		if (var)
		{
		    Get2DIntBounds(var, &l, &r, &b, &t);
		    r += leftWidth - oldLeftWidth;
		    l += leftWidth - oldLeftWidth;
		    Set2DIntBounds(var, l, r, b, t);
		}

		var = GetVar(object, DENSERBUTTON);
		if (var)
		{
		    Get2DIntBounds(var, &l, &r, &b, &t);
		    r += leftWidth - oldLeftWidth;
		    l += leftWidth - oldLeftWidth;
		    Set2DIntBounds(var, l, r, b, t);
		}

#ifdef SCROLLINMIDDLE
		var = GetVar(object, VSCROLL);
		if (var)
		{
		    Get2DIntBounds(var, &l, &r, &b, &t);
		    l += leftWidth - oldLeftWidth;
		    r += leftWidth - oldLeftWidth;
		    Set2DIntBounds(var, l, r, b, t);
		}
#endif

		var = GetVar(object, HSCROLL);
		if (var)
		{
		    Get2DIntBounds(var, &l, &r, &b, &t);
		    l += leftWidth - oldLeftWidth;
		    Set2DIntBounds(var, l, r, b, t);
		}

		ImInvalid(object);
		RecalcScroll(object);
	    }
	}

	MakeMeCurrent(object);

	vScroll = GetVar(object, VSCROLL);
	if (vScroll)
	{
	    downOffset = (long) GetReal(GetValue(vScroll));
	}
	else
	{
	    downOffset = 0;
	}

	MakeVar(object, REPOBJ);
	repObj = GetVar(object, REPOBJ);
	if (!repObj) return ObjTrue;

	MakeVar(repObj, TRACKS);
	tracks = GetVar(repObj, TRACKS);

	if (tracks)
	{
#ifdef SCROLLINMIDDLE
	    if (x >= left + 1 && x <= left + leftWidth - 1 &&
		y >= bottom + TC_GAP + BARWIDTH + 1 &&
		y <= top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1)
#else
	    if (x >= left + BARWIDTH + TC_GAP + 1 &&
		x <= left + BARWIDTH + TC_GAP + leftWidth - 1 &&
		y >= bottom + TC_GAP + BARWIDTH + 1 &&
		y <= top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 1)
#endif
	    {
		/*It's a press in the names*/
		int ty, height, k;
		ObjPtr *elements;

		elements = ELEMENTS(tracks);

		/*Search for a place to begin*/
		ty = -downOffset;
		y -= top - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 2;
		for (k = 0; k < DIMS(tracks)[0]; ++k)
		{
		    MakeVar(elements[k], TRACKHEIGHT);
		    var = GetVar(elements[k], TRACKHEIGHT);
		    if (var)
		    {
			height = GetInt(var);
			if ((ty - height) < y &&
			    (ty >= y)) break;
			ty -= height;
		    }
		}

		if (k < DIMS(tracks)[0])
		{
		    /*Found a click*/
		    FuncTyp method;

		    method = GetMethod(elements[k], PRESSNAME);
		    if (method)
		    {
#ifdef SCROLLINMIDDLE
			(*method)(elements[k], x - 1, y - ty, flags);
#else
			(*method)(elements[k], x - 1 - BARWIDTH - TC_GAP, y - ty, flags);
#endif
		    }
		}
		else
		{
		    if (!(flags & F_EXTEND))
		    {
			DeselectAll();
		    }
		}
	    }
	}

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

	if(GetVar(object, VALUE) &&
	   x >= left + leftWidth + 2 * TC_GAP + BARWIDTH &&
	   x <= right &&
	   y >= top - TC_TIMEHEIGHT - TC_CURHEIGHT &&
	   y <= top - TC_TIMEHEIGHT)
	{
	    /*It's a click in the current time slider*/
	    ObjPtr var;
	    real curTime, oldTime, lastTime;
	    real loValue, hiValue;
	    int timePixel;
	    int xOffset;
	    Bool inp;			/*True iff in*/
	    int newX, newY;		/*New x and y*/
	    real frameWidth;		/*Frame width*/
	    int leftSide, rightSide;	/*Sides for scroll*/

	    SaveForUndo(object);

	    MakeVar(object, FRAMERATE);
	    var = GetRealVar("PressTrackControl", object, FRAMERATE);
	    if (var)
	    {
		frameWidth = 1.0 / GetReal(var);
	    }
	    else
	    {
		frameWidth = 30.0;
	    }

	    var = GetVar(object, VALUE);
	    if (var)
	    {
		curTime = GetReal(var);
	    }
	    else
	    {
		curTime = 0.0;
	    }

	    var = GetVar(object, LOVALUE);
	    if (var)
	    {
		loValue = GetReal(var);
	    }
	    else
	    {
		loValue = 0.0;
	    }

	    var = GetVar(object, HIVALUE);
	    if (var)
	    {
		hiValue = GetReal(var);
	    }
	    else
	    {
		hiValue = 60.0;
	    }

	    timePixel = TimeToPixel(object, curTime);

	    oldTime = curTime;
	    lastTime = curTime;

	    if (x >= timePixel - TCCURWIDTH / 2 && x <= timePixel + TCCURWIDTH / 2)
	    {
		/*It's a click on the thumb.  Set offset*/
		xOffset = timePixel - x;
	    }
	    else
	    {
		/*No offset*/
		xOffset = 0;
	    }

	    /*Start off inside*/
	    inp = true;
	    SetVar(object, HIGHLIGHTED, ObjTrue);

	    /*Get bounds for scrolling*/
	    leftSide = left + leftWidth + 2 * TC_GAP + BARWIDTH + 1 + TCSCROLLBORDER;
	    rightSide =  right - 1 - TCSCROLLBORDER;
	    if (x < leftSide) leftSide = x;
	    if (x > rightSide) rightSide = x;
	    var = NewRealArray(1, 2L);
	    ((real *) ELEMENTS(var))[0] = leftSide;
	    ((real *) ELEMENTS(var))[1] = rightSide;
	    SetVar(object, SCROLLBOUNDS, var);

	    /*Make current x and y bogus*/
	    x = -12345; y = -12345;
	    while (Mouse(&newX, &newY))
	    {
		if (newX != x)
		{
		    x = newX;
		    y = newY;

		    /*Check to see if it's outside*/
		    if (y < top - TC_TIMEHEIGHT - TC_CURHEIGHT - SLOP ||
	   		y > top - TC_TIMEHEIGHT + SLOP)
		    {
			/*Yes, it's outside*/
			if (inp)
			{
			    /*Transition from in to out*/
			    lastTime = oldTime;
			    SetVar(object, VALUE, NewReal(oldTime));
			    UpdateTrackReadouts(object);
			    SetVar(object, HIGHLIGHTED, ObjFalse);
			    inp = false;

			    if (AutoScroll(object))
			    {
				x = -12345;
			    }

			    DrawMe(object);
			}
		    }
		    else
		    {
			/*No, it's inside*/
			if (!inp)
			{
			    /*Transition from out to in*/
			    inp = true;
			    SetVar(object, HIGHLIGHTED, ObjTrue);
			}
			curTime = PixelToTime(object, x + xOffset);
			if (curTime < loValue - frameWidth * 0.5)
			{
			    curTime = loValue;
			}
			if (curTime > hiValue - frameWidth * 0.5)
			{
			    curTime = hiValue - frameWidth;
			}
			curTime = floor(curTime / frameWidth + 0.5) * frameWidth;
			if (curTime != lastTime)
			{
			    SetVar(object, VALUE, NewReal(curTime));
			    UpdateTrackReadouts(object);

			    if (AutoScroll(object))
			    {
				x = -12345;
			    }

			    DrawMe(object);
			    lastTime = curTime;
			}
		    }
		}
	    }
	    SetVar(object, SCROLLBOUNDS, NULLOBJ);

	    if (inp)
	    {
		SetVar(object, HIGHLIGHTED, ObjFalse);
		if (logging)
		{
		    LogControl(object);
		}
		ChangedValue(object);
	    }
	    ImInvalid(object);
	}
#endif

	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

static ObjPtr RecalcTrackControlScroll(control)
ObjPtr control;
/*Recalculates the scroll of the track control*/
{
    ObjPtr repObj, tracks, scrollBar, var;
    long totalHeight, shownHeight, k;
    ObjPtr *elements;
    int l, r, b, t;
    real sliderDown;
    real loValue, hiValue;
    real portionShown;
    int pixWidth;
    int leftWidth;

    Get2DIntBounds(control, &l, &r, &b, &t);

    repObj = GetObjectVar("RecalcTrackControlScroll", control, REPOBJ);
    if (!repObj) return ObjFalse;

    var = GetIntVar("RecalcTrackControlScroll", control, LEFTSIDEWIDTH);
    if (var)
    {
	leftWidth = GetInt(var);
    }
    else
    {
	leftWidth = 100;
    }

    /*Do vertical scroll*/

    /*Calculate the total height*/
    totalHeight = 0;
    tracks = GetVar(repObj, TRACKS);
    if (tracks)
    {
	elements = ELEMENTS(tracks);
	for (k = 0; k < DIMS(tracks)[0]; ++k)
	{
	    MakeVar(elements[k], TRACKHEIGHT);
	    var = GetVar(elements[k], TRACKHEIGHT);
	    if (var && IsInt(var))
	    {
		totalHeight += GetInt(var);
	    }
	}
    }

    /*Slop at the bottom*/
    totalHeight += TC_CELLHEIGHT;

    /*Calculate the numbers for the vertical scroll bar*/
    shownHeight = t - TC_CURHEIGHT - TC_TIMEHEIGHT - TC_GAP - 
		  (b + TC_GAP + BARWIDTH) - 2;
    sliderDown = shownHeight - totalHeight;
    if (sliderDown > 0.0) sliderDown = 0.0;
    portionShown = (real) shownHeight;

    /*Set the v scroll*/
    scrollBar = GetVar(control, VSCROLL);
    if (scrollBar)
    {
	SetSliderRange(scrollBar, 0.0, (real) sliderDown, TC_CELLHEIGHT);
	SetPortionShown(scrollBar, portionShown);
    }

    /*Do horizontal scroll*/
    var = GetRealVar("RecalcTrackControlScroll", control, LOVALUE);
    if (var)
    {
	loValue = GetReal(var);
    }
    else
    {
	loValue = 0.0;
    }

    var = GetRealVar("RecalcTrackControlScroll", control, HIVALUE);
    if (var)
    {
	hiValue = GetReal(var);
    }
    else
    {
	hiValue = 60.0;
    }

    pixWidth = (r - 1) - (l + leftWidth + 2 * TC_GAP + BARWIDTH + 1);

    var = GetRealVar("RecalcTrackControlScroll", control, TIMEPERPIXEL);
    if (var)
    {
	portionShown = GetReal(var) * pixWidth;
    }
    else
    {
	portionShown = hiValue - loValue;
    }

    scrollBar = GetVar(control, HSCROLL);
    if (scrollBar)
    {
	real min, max;

	min = loValue + portionShown * (0.5 - TC_MARGIN);
	max = hiValue - portionShown * (0.5 - TC_MARGIN);
	if (min > max)
	{
	    min = (min + max) * 0.5;
	    max = min;
	}
	SetSliderRange(scrollBar, min, max, portionShown / 10.0);
	SetPortionShown(scrollBar, portionShown);
    }

    return ObjTrue;
}

static ObjPtr AutoScrollTrackControl(control)
ObjPtr control;
/*Auto scrolls a time control*/
{
    ObjPtr var;
    int leftWidth;
    int leftSide, rightSide;

    var = GetIntVar("PixelToTime", control, LEFTSIDEWIDTH);
    if (var)
    {
	leftWidth = GetInt(var);
    }
    else
    {
	leftWidth = 100;
    }

    var = GetVar(control, VALUE);
    if (var)
    {
	int left, right, bottom, top, timePix;
	ObjPtr slider;
	real value, testVal, midVal, lo, hi;

	value = GetReal(var);

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

	slider = GetObjectVar("AutoScrollTimeControl", control, HSCROLL);
	if (!slider)
	{
	    return ObjFalse;
	}
	
	GetSliderRange(slider, &lo, &hi);

	var = GetVar(control, SCROLLBOUNDS);
	if (var)
	{
	    leftSide = ((real *) ELEMENTS(var))[0];
	    rightSide = ((real *) ELEMENTS(var))[1];
	}
	else
	{
	    leftSide = left + leftWidth + 2 * TC_GAP + BARWIDTH + 1 + TCSCROLLBORDER;
	    rightSide =  right - 1 - TCSCROLLBORDER;
	}

	testVal = PixelToTime(control, leftSide);
	if (value < testVal)
	{
	    /*It's off to the left*/
	    SetSliderValue(slider, GetSliderValue(slider) + value - testVal);
	    return ObjTrue;
	}

	testVal = PixelToTime(control, rightSide);
	if (value > testVal)
	{
	    /*It's off to the right*/
	    SetSliderValue(slider, GetSliderValue(slider) + value - testVal);
	    return ObjTrue;
	}

	return ObjFalse;
    }
    else
    {
	return ObjFalse;
    }
}

static ObjPtr MakeTrackControlAppearance(control)
ObjPtr control;
/*Makes the appearance of a track control*/
{
    RecalcScroll(control);
    SetVar(control, APPEARANCE, ObjTrue);
    return ObjTrue;
}

void InitTrackControls()
/*Initializes the track controls*/
{
    pictureButtonClass = NewObject(buttonClass, 0L);
    AddToReferenceList(pictureButtonClass);
    SetMethod(pictureButtonClass, DRAW, DrawPictureButton);

    densityButtonClass = NewObject(pictureButtonClass, 0L);
    AddToReferenceList(densityButtonClass);
    SetMethod(densityButtonClass, DRAWCONTENTS, DrawDensityButtonContents);

    trackControlClass = NewObject(controlClass, 0L);
    AddToReferenceList(trackControlClass);
    SetVar(trackControlClass, LEFTSIDEWIDTH, NewInt(110));
    SetMethod(trackControlClass, DRAW, DrawTrackControl);
    SetMethod(trackControlClass, PRESS, PressTrackControl);
    SetMethod(trackControlClass, BOUNDSINVALID, TrackControlBoundsInvalid);
    SetMethod(trackControlClass, RESHAPE, ReshapeTrackControl);
    DeclareIndirectDependency(trackControlClass, APPEARANCE, REPOBJ, TRACKS);
    SetMethod(trackControlClass, APPEARANCE, MakeTrackControlAppearance);
    SetMethod(trackControlClass, RECALCSCROLL, RecalcTrackControlScroll);
    SetMethod(trackControlClass, AUTOSCROLL, AutoScrollTrackControl);
    SetMethod(trackControlClass, KEYDOWN, KeyDownTrackControl);
    SetVar(trackControlClass, OPAQUE, ObjTrue);

    AddSnapVar(trackControlClass, TIMEPERPIXEL);
}

void KillTrackControls()
/*Kills the track controls*/
{
    RemoveFromReferenceList(trackControlClass);
    RemoveFromReferenceList(densityButtonClass);
    RemoveFromReferenceList(pictureButtonClass);
}
