/*ScianTimers.c
  Timers in scian
  Eric Pepke
  September 9, 1990
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianArrays.h"
#include "ScianLists.h"
#include "ScianErrors.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianIDs.h"
#include "ScianTimers.h"
#include "ScianScripts.h"
#include "ScianDialogs.h"
#include "ScianSliders.h"
#include "ScianSpaces.h"
#include "ScianDraw.h"
#include "ScianStyle.h"
#include "ScianColors.h"
#include "ScianTextBoxes.h"
#include "ScianMethods.h"
#include "ScianDepend.h"
#include "ScianPictures.h"
#include "ScianEvents.h"
#include "ScianControls.h"

extern ObjPtr curSpace;			/*The current space*/
ObjPtr timedObjClass;			/*Time dependant object*/
ObjPtr timeControlClass;		/*Class for time controls*/
double clockStarted = 0.0;		/*Time at which clock was started*/
double clockStopped = 0.0;		/*Time clock was stopped*/
double videoClock = 0.0;		/*Video clock*/
Bool clockOn = true;			/*True iff clock is on*/
long startTime;				/*Starting time of machine*/

#define SCROLLTIME			/*Autoscroll the time control*/

static real dummy = 1.0;

#define CEDGE	3

AlarmRec alarms[NALARMS];		/*The alarms*/

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

/*States for time reader machine*/
#define TS_BEFORESIGN	1		/*Before plus or minus sign*/
#define TS_INWHOLE	2		/*Whole part*/
#define TS_INFRAC	3		/*Fractional part*/
#define TS_BEFOREEXP	4		/*Before pluse or minus exponent*/
#define TS_INEXP	6		/*In exponent*/
#define TS_DONE		7

void TinyDelay()
/*Does a tiny little delay*/
{
    int k;
    for (k = 0; k < 10; ++k)
	dummy = dummy * dummy;
}

#ifdef PROTO
int ParseTime(real *t, int *f, char s[])
#else
int ParseTime(t, f, s)
real *t;
int *f;
char s[];
#endif
/*Parses a time in s.  Puts the time format in f and the time in t.
  Returns >0 if it worked.  Returns <=0 if there was an error*/
{
    double time;
    int format;
    double curNumber;
    double sign = 1.0;
    double fracDiv;
    double expSign;
    double curExp = 0.0;
    int k;
    int state = TS_BEFORESIGN;

    format = 0;

    time = 0.0;
    curNumber = 0.0;

    for (k = 0; s[k] && (state != TS_DONE); ++k)
    {
	if (!isspace(s[k]))
	{
	    switch(state)
	    {
		case TS_BEFORESIGN:	/*Before number sign*/
		    if (s[k] == '-')
		    {
			sign = -1.0;
			break;
		    }
		    else if (s[k] == '+')
		    {
			state = TS_INWHOLE;
			break;
		    }
		    else
		    {
			state = TS_INWHOLE;
			/*Fall through*/
		    }
		case TS_INWHOLE:		/*In the whole part of the number*/
		    format |= TF_SECONDS;
		    if (s[k] == ':')
		    {
			/*Next is minutes or seconds.  Save and continue*/
			format |= format << 1;
			time += curNumber;
			time *= 60.0;
			curNumber = 0.0;
		    }
		    else if (s[k] >= '0' && s[k] <= '9')
		    {
			curNumber *= 10.0;
			curNumber += s[k] - '0';
		    }
		    else if (s[k] == '.')
		    {
			fracDiv = 1.0;
			state = TS_INFRAC;
			format |= TF_SUBSECONDS;
		    }
		    else if (s[k] == 'E' || s[k] == 'e' ||
			     s[k] == 'F' || s[k] == 'f' ||
			     s[k] == 'G' || s[k] == 'g')
		    {
			expSign = 1.0;
			curExp = 0.0;
			state = TS_BEFOREEXP;
		    }
		    else
		    {
			/*Error*/
			return -k;
		    }
		    break;
		case TS_INFRAC:			/*In the fractional part*/
		    format |= TF_SUBSECONDS;
		    if (s[k] >= '0' && s[k] <= '9')
		    {
			fracDiv *= 0.1;
			curNumber += fracDiv * (double) (s[k] - '0');
		    }
		    else if (s[k] == 'E' || s[k] == 'e' ||
			     s[k] == 'F' || s[k] == 'f' ||
			     s[k] == 'G' || s[k] == 'g')
		    {
			expSign = 1.0;
			curExp = 0.0;
			state = TS_BEFOREEXP;
		    }
		    else
		    {
			/*Error*/
			return -k;
		    }
		    break;
		case TS_BEFOREEXP:
		    format |= TF_SUBSECONDS;
		    if (s[k] == '-')
		    {
			expSign = -1.0;
			format = TS_INEXP;
			break;
		    }
		    else if (s[k] == '+')
		    {
			format = TS_INEXP;
			break;
		    }
		    /*Fall through*/
		case TS_INEXP:
		    if (s[k] >= '0' && s[k] <= '9')
		    {
			curExp *= 10.0;
			curExp += (double) (s[k] - '0');
		    }
		    else return -k;
	    }
	}
    }

    /*Finish up filling in the time*/
    if (curExp != 0.0)
    {
	curNumber *= pow(10.0, curExp * expSign);
	curExp = 0.0;
    }
    time += curNumber * sign;
    *t = time;
    *f = format;

    return k;
}

ObjPtr NewTimedObject(timeSteps, timeData)
ObjPtr timeSteps, timeData;
/*Returns a new timed object made up of timeSteps and timeData pointing to 
  repObj*/
{
    ObjPtr timedObj, stepsArray, dataArray;
    real *stepElements;
    ObjPtr *dataElements;
    ThingListPtr stepsRunner, dataRunner;
    long nSteps, k;

    nSteps = ListCount(timeSteps);
    stepsArray = NewArray(AT_REAL, 1, &nSteps);
    dataArray = NewArray(AT_OBJECT, 1, &nSteps);
    if (!stepsArray || !dataArray)
    {
	return NULLOBJ;
    }

    stepsRunner = LISTOF(timeSteps);
    dataRunner = LISTOF(timeData);
    stepElements = ELEMENTS(stepsArray);
    dataElements = ELEMENTS(dataArray);
    k = 0;
    while (stepsRunner)
    {
	if (!dataRunner)
	{
	    ReportError("NewTimedObject", "Not enough data for timesteps");
	}
	else
	{
	    stepElements[k] = GetReal(stepsRunner -> thing);
	    dataElements[k] = dataRunner -> thing;
	}
	stepsRunner = stepsRunner -> next;
	dataRunner = dataRunner -> next;
	++k;
    }

    timedObj = NewObject(timedObjClass, 0);
    SetVar(timedObj, TIMESTEPS, stepsArray);
    SetVar(timedObj, TIMEDATA, dataArray);
    return timedObj;
}

ObjPtr MakeTimeBounds(timedObj)
ObjPtr timedObj;
/*Calculates the time bounds of a timedObj*/
{
    ObjPtr timeSteps;		/*Array of time steps*/
    real *timeElements;
    real tb[2];
    ObjPtr timeBounds;

    MakeVar(timedObj, TIMESTEPS);
    timeSteps = GetVar(timedObj, TIMESTEPS);
    
    if (!timeSteps)
    {
	return ObjFalse;
    }

    timeElements = ELEMENTS(timeSteps);

    /*New method of doing time step*/
    if (DIMS(timeSteps)[0] == 1)
    {
	tb[0] = tb[1] = timeElements[0];
    }
    else
    {
	tb[0] = timeElements[0] +
		0.5 * (timeElements[0] - timeElements[1]);
	tb[1] = timeElements[DIMS(timeSteps)[0] - 1] +
		0.5 * ( timeElements[DIMS(timeSteps)[0] - 1] -
			timeElements[DIMS(timeSteps)[0] - 2]);
    }

    timeBounds = NewRealArray(1, 2L);
    CArray2Array(timeBounds, tb);
    SetVar(timedObj, TIMEBOUNDS, timeBounds);
    return ObjTrue;
}

Bool InsertTimeSlice(timedObj, time, data)
ObjPtr timedObj, time, data;
/*Inserts a time slice of data at time into timedObj*/
{
    ObjPtr timeSteps, timeData;
    long index;
    real *elements;
    real curTime;

    MakeVar(timedObj, TIMESTEPS);
    timeSteps = GetArrayVar("InsertTimeSlice", timedObj, TIMESTEPS);
    timeData = GetVar(timedObj, TIMEDATA);
    
    if (!timeSteps || !timeData)
    {
	return false;
    }

    curTime = GetReal(time);
    index = SearchReal(timeSteps, curTime);
    if (index < 0)
    {
	return false;
    }

    /*See if this is a duplication of an existing time step*/
    elements = ELEMENTS(timeSteps);
    if (index < DIMS(timeSteps)[0] && elements[index] == curTime)
    {
	/*DIKEO change to replace the time step later*/
	return true;
    }
    if (index - 1 >= 0 && elements[index - 1] == curTime)
    {
	return true;
    }

    timeSteps = InsertInArray(timeSteps, time, index);
    timeData = InsertInArray(timeData, data, index);

    SetVar(timedObj, TIMESTEPS, timeSteps);
    SetVar(timedObj, TIMEDATA, timeData);

    if (index == 0 || index >= DIMS(timeSteps)[0])
    {
	MakeVar(timedObj, TIMEBOUNDS);
    }

    return true;
}

double WallClock()
/*Returns the wall clock time*/
{
    struct tms buffer;

    return ((double) (times(&buffer) - startTime)) / HEARTBEAT - clockStarted;
}

double Clock()
/*Returns the clock.  This differs between real-time and single-frame.*/
{
    if (runningScript)
    {
	return videoClock - clockStarted;
    }
    else
    {
	return WallClock();
    }
}

void SetSystemClock(time)
double time;
/*Sets the system clock to time*/
{
    struct tms buffer;
    double supposedTime;

    if (runningScript)
    {
	supposedTime = videoClock;
    }
    else
    {
	supposedTime = ((double) (times(&buffer) - startTime)) / HEARTBEAT;
    }
    clockStarted = time - supposedTime;
}

void ClockOff()
/*Turns the clock off for a moment*/
{
    struct tms buffer;

    if (clockOn)
    {
	clockOn = false;
	if (runningScript)
	{
	    clockStopped = videoClock;
	}
	else
	{
	    clockStopped = ((double) (times(&buffer) - startTime)) / HEARTBEAT;
	}
    }
}

void ClockOn()
/*Turns the clock back on*/
{
    struct tms buffer;

    if (!clockOn)
    {
	clockOn = true;
	if (runningScript)
	{
	    clockStarted += videoClock - clockStopped;
	}
	else
	{
	    clockStarted += ((double) (times(&buffer) - startTime)) / HEARTBEAT - clockStopped;
	}
    }
}

ObjPtr MakeTimedCurData(timedObj)
ObjPtr timedObj;
/*Makes a timed object's timeData*/
{
    ObjPtr timeSteps, timeData;		/*Time steps and data*/
    ObjPtr retVal;			/*Value to return*/
    ObjPtr timeBounds;			/*Time bounds of the current space*/
    ObjPtr time;			/*Current spaceTime*/
    ObjPtr curObjTime;			/*Current time for the object*/
    ObjPtr interp1, interp2;		/*Two arrays to interpolate between*/
    real time1, time2;			/*Times to interpolate*/
    long index;
    ObjPtr element;			/*Elements of the array*/
    Bool interpolateP;

    MakeVar(timedObj, TIMEBOUNDS);
    timeBounds = GetVar(timedObj, TIMEBOUNDS);

    interpolateP = GetPredicate(timedObj, INTERPOLATEP);

    if ((curObjTime = GetVar(timedObj, TIME)) &&
	(GetReal(curObjTime) == spaceTime) &&
	(interpolateP == GetPredicate(timedObj, LASTINTERP)))
    {
	return GetVar(timedObj, CURDATA);
    }

    SetVar(timedObj, LASTINTERP, interpolateP ? ObjTrue : ObjFalse);
    MakeVar(timedObj, TIMESTEPS);
    timeSteps = GetArrayVar("MakeTimedCurData", timedObj, TIMESTEPS);
    timeData = GetVar(timedObj, TIMEDATA);
    
    if (!timeSteps || !timeData)
    {
	return NULLOBJ;
    }


    /*Find the closest lower bound time step*/
    index = SearchReal(timeSteps, spaceTime);

    if (index <= 0)
    {
	retVal = GetObjectElement(timeData, &index);
    }
    else if (index >= DIMS(timeSteps)[0])
    {
	--index;
	retVal = GetObjectElement(timeData, &index);
	++index;
    }
    else
    {
	/*There's something to choose from or interpolate*/
	real weight;
	
	--index;
	interp1 = GetObjectElement(timeData, &index);
	++index;
	interp2 = GetObjectElement(timeData, &index); 
	time1 = ((real *) ELEMENTS(timeSteps))[index - 1];
	time2 = ((real *) ELEMENTS(timeSteps))[index];
	weight = (spaceTime - time1) / (time2 - time1);
	if (interpolateP && IsRealArray(((ObjPtr *) ELEMENTS(timeData))[index - 1]))
	{
	    retVal = InterpArray(interp1, interp2, weight);
	}
	else if (weight > 0.5)
	{
	    retVal = ((ObjPtr *) ELEMENTS(timeData))[index];
	}
	else
	{
	    retVal = ((ObjPtr *) ELEMENTS(timeData))[index - 1];
	}    
    }
    SetVar(timedObj, CURDATA, retVal);
    SetVar(timedObj, TIME, NewReal(spaceTime));
    return retVal;
}

Bool WakeMe(object, method, time)
ObjPtr object;
NameTyp method;
double time;
/*Puts in a request to wake an object by sending it a method at a certain
  time.  Returns true iff succeeds.  The method will be passed a double
  giving the lateness of the wakeup call*/
{
    int k;
    for (k = 0; k < NALARMS; ++k)
    {
	if (alarms[k] . object == NULLOBJ)
	{
	    alarms[k] . object = object;
	    alarms[k] . method = method;
	    alarms[k] . when = time;
	    alarms[k] . wallTime = false;
	    alarms[k] . startTime = Clock();
	    AddToReferenceList(object);
	    return true;
	}
    }
    return false;
}

Bool SetTimeout(object, method, time)
ObjPtr object;
NameTyp method;
double time;
/*Puts in a request to wake an object by sending it a method at a certain
  time.  Returns true iff succeeds.  The method will be passed a double
  giving the lateness of the wakeup call*/
{
    int k;
    for (k = 0; k < NALARMS; ++k)
    {
	if (alarms[k] . object == NULLOBJ)
	{
	    alarms[k] . object = object;
	    alarms[k] . method = method;
	    alarms[k] . when = time;
	    alarms[k] . wallTime = true;
	    alarms[k] . startTime = WallClock();
	    AddToReferenceList(object);
	    return true;
	}
    }
    return false;
}

void DoNotDisturb(object, method)
ObjPtr object;
NameTyp method;
/*Removes all wakeup calls for object and method*/
{
    int k;
    for (k = 0; k < NALARMS; ++k)
    {
	if (alarms[k] . object == object &&
	    alarms[k] . method == method)
	{
	    ObjPtr object;
	    object = alarms[k] . object;
	    alarms[k] . object = 0;
	    DeleteThing(object);
	}
    }
}

void IdleTimers()
/*Idles all the timer stuff*/
{
    int k;
    for (k = 0; k < NALARMS; ++k)
    {
	double time;
	
	if (alarms[k] . wallTime)
	{
	    time = WallClock();
	}
	else
	{
	    time = Clock();
	}

	if (alarms[k] . object && time >= alarms[k] . when)
	{
	    ObjPtr object;
	    FuncTyp method;

	    object = alarms[k] . object;
	    alarms[k] . object = 0;
	    method = GetMethod(object, alarms[k] . method);
	    if (method)
	    {
		(*method)(object, time - alarms[k] . startTime);
	    }
	    DeleteThing(object);
	}
    }
}

#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 time control.
  Check this against bounds of thing before drawing*/
{
    ObjPtr var, scrollbar;
    real value, tpp;
    int l, r, b, t, mid;

    Get2DIntBounds(control, &l, &r, &b, &t);
    
    l += TCDSWIDTH + 2 * TCGAP + BARWIDTH;
    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;
    }

    var = GetVar(control, TIMEPERPIXEL);
    if (var)
    {
	tpp = GetReal(var);
    }
    else
    {
	return mid;
    }
    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 time control.
  Check this against bounds of thing before drawing*/
{
    ObjPtr var, scrollbar;
    real value, tpp;
    int l, r, b, t, mid;

    Get2DIntBounds(control, &l, &r, &b, &t);
    
    l += TCDSWIDTH + 2 * TCGAP + BARWIDTH;
    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;
    }

    var = GetVar(control, TIMEPERPIXEL);
    if (var)
    {
	tpp = GetReal(var);
    }
    else
    {
	return value;
    }
    
    return (pixel - mid) * tpp + value;
}

#ifdef PROTO
void PrintTime(char *s, real t, int f)
#else
void PrintTime(s, t, f)
char *s;
real t;
int f;
#endif
/*Prints time t using predefined format f into string s*/
{
    long hours;
    long minutes;
    long seconds;

    if (f & TF_HOURS)
    {
	hours = t / 3600;
	sprintf(s, "%d:", hours);
	while (*s) ++s;

	minutes = t / 60;
 	sprintf(s, "%02d", minutes - hours * 60);
	while (*s) ++s;
	
	if (f & TF_SECONDS)
	{
	    seconds = t;
	    if (f & TF_SUBSECONDS)
	    {
		sprintf(s, ":%02g", t - minutes * 60);
	    }
	    else
	    {
		sprintf(s, ":%02d", seconds - minutes * 60);
	    }
	}
    }
    else if (f & TF_MINUTES)
    {
	minutes = t / 60;
	sprintf(s, "%d:", minutes);
	while (*s) ++s;

	seconds = t;
	if (f & TF_SUBSECONDS)
	{
	    sprintf(s, "%02g", t - minutes * 60);
	}
	else
	{
	    sprintf(s, "%02d", seconds - minutes * 60);
	}
    }
    else
    {
	sprintf(s, "%g", t);
    }
}

void UpdateTimeReadout(control)
ObjPtr control;
/*Updates a time readout in a control*/
{
    ObjPtr readout, clock, format, var;
    char timeStr[256];
    FuncTyp method;

    readout = GetObjectVar("UpdateTimeReadout", control, READOUT);
    if (!readout)
    {
	return;
    }

    clock = GetObjectVar("UpdateTimeReadout", control, REPOBJ);
    if (!clock)
    {
	return;
    }

    var = GetVar(control, VALUE);

    if (var)
    {
	MakeVar(control, TIMEFORMAT);
	format = GetVar(control, TIMEFORMAT);
	if (format)
	{
	    PrintTime(timeStr, GetReal(var), GetInt(format));
	}
	else
	{
	    sprintf(timeStr, "%g", GetReal(var));
	}
    }
    else
    {
	strcpy(timeStr, "");
    }
    InhibitLogging(true);
    method = GetMethod(readout, CHANGEDVALUE);
    SetMethod(readout, CHANGEDVALUE, (FuncTyp) 0);
    SetTextBox(readout, timeStr);
    SetMethod(readout, CHANGEDVALUE, method);
    InhibitLogging(false);
}

ObjPtr DrawTimeControl(object)
ObjPtr object;
/*Draws a time control*/
{
#ifdef GRAPHICS
    ObjPtr hScroll, vScroll, readout, var, timeVar;
    int left, right, bottom, top, timePix;
    real value;
    real downOffset;
    ObjPtr datasets;
    Bool madeDatasets;
    ObjPtr clock;
    long line;

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

    SetupFont(TCFONT, TCFONTSIZE);

    if (MakeVar(object, DATASETS))
    {
	RecalcScroll(object);
    }

    MakeVar(object, TIMESTEPS);

    /*Get the clock*/
    clock = GetObjectVar("DrawTimeControl", object, REPOBJ);
    if (!clock)
    {
	return NULLOBJ;
    }

    /*Get current time*/
    timeVar = GetVar(object, VALUE);
    if (timeVar)
    {
	value = GetReal(timeVar);
    }
    else
    {
	value = 0.0;
    }

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

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

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

    datasets = GetVar(object, DATASETS);

    /*Draw the datasets box*/
    FillUIRect(left + 1, left + TCDSWIDTH - 1, bottom + TCGAP + BARWIDTH + 1, top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP - 1, UIGRAY62);

    /*Draw the contents of the datasets box*/
    if (vScroll && datasets)
    {
	downOffset = GetReal(GetValue(vScroll));
	line = (-downOffset / TCCELLHEIGHT);
	if (line < DIMS(datasets)[0])
	{
	    /*It's worth printing*/
	    int texty;
	    ObjPtr element;
	    SetClipRect(left + 1,
			left + TCDSWIDTH - 1,
			bottom + TCGAP + BARWIDTH + 1,
			top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP - 1);
	    texty = top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP - 1 - (line + 1) * TCCELLHEIGHT + TCTEXTBOFF - downOffset;
	    SetUIColor(UIBLACK);
	    while (line < DIMS(datasets)[0] &&
		   texty > bottom + TCGAP + BARWIDTH + 1 - TCCELLHEIGHT)
	    {
		ObjPtr name;
		element = GetObjectElement(datasets, &line);
		name = GetVar(element,  NAME);
		if (name)
		{
		    DrawAString(LEFTALIGN, left + 1 + TCTEXTLOFF,
			       texty,
			       GetString(name)); 
		}
		else
		{
		    DrawAString(LEFTALIGN, left + 1 + TCTEXTLOFF,
			       texty,
			       "?");
		}
		texty -= TCCELLHEIGHT;
		++line;
	    }
	    RestoreClipRect();
	}
    }
    /*Draw the frame of the datasets box*/
    FrameUIRect(left, left + TCDSWIDTH, bottom + TCGAP + BARWIDTH, top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP, UIBLACK);

    /*Draw the timeline box*/
    FillUIRect(	left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
		right - 1,
		bottom + TCGAP + BARWIDTH + 1,
		top - 1,
		UIGRAY62);

    /*Draw the contents of the timeline box*/
    if (vScroll && datasets)
    {
	ObjPtr var;
	char timeStr[256];
	int format;

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

	/*Draw the time numbers*/
	var = GetVar(object, TIMEPERPIXEL);
	if (var)
	{
	    /*It's valid; draw a whole range of times*/
	    real timePerPixel, timeAtPixel;
	    real displayStep;
	    int displayTics;
	    long tempLong;
	    int curPixel;
	    int timePixel;
	    real curTime;
	    int k;
	    timePerPixel = GetReal(var);

	    /*Get a pixel that's way off the left*/
	    timePixel = left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1 - TCMAXTIMEWIDTH;
	    timeAtPixel = PixelToTime(object, timePixel);

	    /*Get the step and tics*/
	    var = GetRealVar("DrawTimeControl", object, DISPLAYSTEP);
	    if (var)
	    {
		displayStep = GetReal(var);
	    }
	    else
	    {
		displayStep = 1.0;
	    }
	    var = GetIntVar("DrawTimeControl", object, DISPLAYTICS);
	    if (var)
	    {
		displayTics = GetInt(var);
	    }
	    else
	    {
		displayTics = 10;
	    }

	    /*Do the truncate bit*/
	    tempLong = timeAtPixel / displayStep;
	    timeAtPixel = tempLong * displayStep;
	    timePixel = TimeToPixel(object, timeAtPixel);

	    /*Write out the times*/
	    SetClipRect(left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
			right - 1,
			top - TCTIMEHEIGHT + 1,
			top - 1);
	    curPixel = timePixel;
	    curTime = timeAtPixel;
	    SetupFont(TCTIMEFONT, TCTIMEFONTSIZE);
	    SetUIColor(UIBLACK);
	    do
	    {
		if (format)
		{
		    PrintTime(timeStr, curTime, format);
		}
		else
		{
		    sprintf(timeStr, "%g", curTime);
		}
		DrawAString(CENTERALIGN, curPixel, 
			   top - TCTIMEHEIGHT + 1 + TCTIMEBOFF,
			   timeStr);
		curTime += displayStep;
		tempLong = curTime / displayStep + 0.5;
		curTime = tempLong * displayStep;
		curPixel = TimeToPixel(object, curTime);
	    }
	    while (curPixel < right + TCMAXTIMEWIDTH);

	    /*Write out upper tic marks*/
	    curPixel = timePixel;
	    curTime = timeAtPixel;
	    k = 0;
	    SetUIColor(UIBLACK);
	    do
	    {
		DrawUILine(curPixel, top - TCTIMEHEIGHT + (k % displayTics ? TCTIMEBOFF / 2 : TCTIMEBOFF) - 1,
			   curPixel, top - TCTIMEHEIGHT + 1,
			   UIGRAY25);
		++k;
		curTime += displayStep / displayTics;
		curPixel = TimeToPixel(object, curTime);
	    }
	    while (curPixel < right + TCMAXTIMEWIDTH);
	    RestoreClipRect();

	    /*Write out lower tic marks*/
	    SetClipRect(left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
			right - 1,
			bottom + BARWIDTH + TCGAP + 1,
			top - TCTIMEHEIGHT - TCCURHEIGHT - TCGAP - 1);
	    curPixel = timePixel;
	    curTime = timeAtPixel;
	    do
	    {
		DrawUILine(curPixel, top - TCTIMEHEIGHT - TCCURHEIGHT - TCGAP - 1,
			   curPixel, bottom + BARWIDTH + TCGAP + 1,
			   UIGRAY25);
		curTime += displayStep;
		curPixel = TimeToPixel(object, curTime);
	    }
	    while (curPixel < right + TCMAXTIMEWIDTH);
	    RestoreClipRect();
	}
	else if (timeVar)
	{
	    /*Just draw a single time at the current time slider*/
	    real curTime;
	    int timePixel;
	
	    curTime = value;

	    timePixel = TimeToPixel(object, curTime);

	    /*Write out the time*/
	    SetClipRect(left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
			right - 1,
			top - TCTIMEHEIGHT + 1,
			top - 1);
	    SetupFont(TCTIMEFONT, TCTIMEFONTSIZE);
	    SetUIColor(UIBLACK);
	    if (format)
	    {
		PrintTime(timeStr, curTime, format);
	    }
	    else
	    {
		sprintf(timeStr, "%g", curTime);
	    }
	    DrawAString(CENTERALIGN, timePixel, 
		       top - TCTIMEHEIGHT + 1 + TCTIMEBOFF,
		       timeStr);
	    RestoreClipRect();
	}

	/*Draw the time lines*/
	downOffset = GetReal(GetValue(vScroll));
	line = (-downOffset / TCCELLHEIGHT);
	if (line < DIMS(datasets)[0])
	{
	    /*It's worth printing*/
	    int midy;
	    ObjPtr element;
	    SetClipRect(left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
			right - 1,
			bottom + TCGAP + BARWIDTH + 1,
			top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP - 1);
	    midy = top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP - 1 - (line + 1) * TCCELLHEIGHT + TCLINEBOFF - downOffset;
	    SetUIColor(UIBLACK);
	    while (line < DIMS(datasets)[0] &&
		   midy > bottom + TCGAP + BARWIDTH + 1 - TCCELLHEIGHT)
	    {
		/*Print out the timeline of the object*/
		ObjPtr timeSteps;
		element = GetObjectElement(datasets, &line);
		MakeVar(element, TIMESTEPS);
		timeSteps = GetVar(element, TIMESTEPS);
		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;
			    Bool interpolateP;

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

			    interpolateP = GetPredicate(element, INTERPOLATEP);

			    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);
				bgnpolygon();
				coords[0] = pixel;
				coords[1] = midy + TCSAMPLESIZE / 2;
				v2i(coords);
				coords[0] = pixel - TCSAMPLESIZE / 2;
				coords[1] = midy;
				v2i(coords);
				coords[0] = pixel;
				coords[1] = midy - TCSAMPLESIZE / 2;
				v2i(coords);
				coords[0] = pixel + TCSAMPLESIZE / 2;
				coords[1] = midy;
				v2i(coords);
				endpolygon();
			    }
			}
		    }
		}
		else
		{
		    /*Eternal object*/
		    SetLineWidth(2);
		    DrawUILine(left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1, midy,
			       right - 1, midy, TCSAMPLECOLOR);
		    SetLineWidth(1);
		}
		midy -= TCCELLHEIGHT;
		++line;
	    }
	    RestoreClipRect();
	}
    }
    FrameUIRect(left + TCDSWIDTH + 2 * TCGAP + BARWIDTH,
		right,
		bottom + TCGAP + BARWIDTH,
		top, UIBLACK);
    DrawUILine(	left + TCDSWIDTH + 2 * TCGAP + BARWIDTH,
		top - TCTIMEHEIGHT,
		right, top - TCTIMEHEIGHT,
		UIBLACK);
    DrawUILine(	left + TCDSWIDTH + 2 * TCGAP + BARWIDTH,
		top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP,
		right,
		top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP,
		UIBLACK);
    DrawUILine(	left + TCDSWIDTH + 2 * TCGAP + BARWIDTH,
		top - TCCURHEIGHT - TCTIMEHEIGHT - 1,
		right,
		top - TCCURHEIGHT - TCTIMEHEIGHT - 1,
		UIBLACK);
    FillUIRect(	left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
		right - 1,
		top - TCTIMEHEIGHT - CEDGE,
		top - TCTIMEHEIGHT,
		UIBOTTOMEDGE);
    FillUIRect(	left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
		right - 1,
		top - TCTIMEHEIGHT - TCCURHEIGHT + CEDGE + 1,
		top - TCTIMEHEIGHT - CEDGE - 1,
		timeVar ? UIPGREEN : UIGRAY50);
    FillUIRect(left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
		right - 1,
		top - TCTIMEHEIGHT - TCCURHEIGHT,
		top - TCTIMEHEIGHT - TCCURHEIGHT + CEDGE,
		UITOPEDGE);
    if (timeVar == NULLOBJ)
    {
	FillUIGauzeRect(left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1,
			right - 1,
			top - TCTIMEHEIGHT - TCCURHEIGHT,
			top - TCTIMEHEIGHT,
			UIBACKGROUND);
    }

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

    /*Draw "Current time:"*/
    SetUIColor(UIBLACK);
    SetupFont(TCFONT, TCFONTSIZE);
    DrawAString(LEFTALIGN, left, top - TCTIMEHEIGHT + 1 + TCTIMEBOFF, "Current time:");
#endif
    return ObjTrue;
}

static ObjPtr AutoScrollTimeControl(control)
ObjPtr control;
/*Auto scrolls a time control*/
{
    ObjPtr var;

    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);

	testVal = PixelToTime(control, left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1 + TCSCROLLBORDER);
	if (value < testVal)
	{
	    /*It's off to the left*/
	    midVal = PixelToTime(control, (left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + right) / 2);
	    SetSliderValue(slider, MAX(lo, value + midVal - testVal));
	    return ObjTrue;
	}

	testVal = PixelToTime(control, right - 1 - TCSCROLLBORDER);
	if (value > testVal)
	{
	    /*It's off to the right*/
	    midVal = PixelToTime(control, (left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + right) / 2);
	    SetSliderValue(slider, MIN(hi, value + midVal - testVal));
	    return ObjTrue;
	}

	return ObjFalse;
    }
    else
    {
	return ObjFalse;
    }
}

static ObjPtr FindObjectTimeControl(object, name)
ObjPtr object;
char *name;
/*Searches a time control for an object with name*/
{
    ObjPtr retVal = NULLOBJ;
    ObjPtr objName;
    ObjPtr scrollbar, textBox;

    /*First check to see if I am the object*/
    objName = GetVar(object, NAME);
    if (objName && IsString(objName) && ObjectNameMatches(name, GetString(objName)))
    {
	if (!retVal)
	{
	    retVal = NewList();
	}
	PostfixList(retVal, object);
    }

    /*Now check the scroll bars*/
    scrollbar = GetVar(object, HSCROLL);
    if (scrollbar)
    {
	objName = GetVar(scrollbar, NAME);
	if (objName && IsString(objName) && 0 == strcmp2(GetString(objName), name))
	{
	    if (!retVal)
	    {
		retVal = NewList();
	    }
	    PostfixList(retVal, scrollbar);
	}
    }
    scrollbar = GetVar(object, VSCROLL);
    if (scrollbar)
    {
	objName = GetVar(scrollbar, NAME);
	if (objName && IsString(objName) && 0 == strcmp2(GetString(objName), name))
	{
	    if (!retVal)
	    {
		retVal = NewList();
	    }
	    PostfixList(retVal, scrollbar);
	}
    }

    textBox = GetVar(object, READOUT);
    if (textBox)
    {
	objName = GetVar(textBox, NAME);
	if (objName && IsString(objName) && 0 == strcmp2(GetString(objName), name))
	{
	    if (!retVal)
	    {
		retVal = NewList();
	    }
	    PostfixList(retVal, textBox);
	}
    }
    return retVal;
}

static ObjPtr KeyDownTimeControl(object, key, flags)
ObjPtr object;
int key;
long flags;
/*Does a keydown in a time 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;
	ObjPtr timeSteps;
	long index;

	MakeVar(object, TIMESTEPS);
	timeSteps = GetVar(object, TIMESTEPS);
	if (!timeSteps)
	{
	    return ObjTrue;
	}

	var = GetRealVar("KeyDownTimeControl", object, VALUE);
	if (!var)
	{
	    return ObjTrue;
	}
	
	index = SearchReal(timeSteps, GetReal(var));

	if (key == FK_LEFT_ARROW || key == FK_DOWN_ARROW) index -= 2;
	if (index < 0) index = DIMS(timeSteps)[0] - 1;
	if (index >= DIMS(timeSteps)[0]) index = 0;

#ifdef INTERACTIVE
	interactiveMoving = false;
#endif
	SetVar(object, VALUE, NewReal(((real *) ELEMENTS(timeSteps))[index]));
	UpdateTimeReadout(object);
	if (logging)
	{
	    LogControl(object);
	}
	AutoScroll(object);
	ImInvalid(object);
	ChangedValue(object);
    }

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

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

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

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

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

    MakeMeCurrent(object);

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	/*Hey!  It really was a click in the time control*/

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

	if(GetVar(object, VALUE) &&
	   x >= left + TCDSWIDTH + 2 * TCGAP + BARWIDTH &&
	   x <= right &&
	   y >= top - TCTIMEHEIGHT - TCCURHEIGHT &&
	   y <= top - TCTIMEHEIGHT)
	{
	    /*It's a click in the current time control*/
	    ObjPtr var;
	    real curTime, oldTime;
	    int timePixel;
	    int xOffset;
	    Bool inp;			/*True iff in*/
	    int newX, newY;		/*New x and y*/
	    ObjPtr timeSteps;

	    SaveForUndo(object);

	    /*If constrained, get the time steps*/
	    timeSteps = NULLOBJ;
	    if (flags & F_SHIFTDOWN)
	    {
		MakeVar(object, TIMESTEPS);
		timeSteps = GetVar(object, TIMESTEPS);
	    }

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

	    oldTime = 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);

	    /*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 - TCTIMEHEIGHT - TCCURHEIGHT - SLOP ||
	   		y > top - TCTIMEHEIGHT + SLOP)
		    {
			/*Yes, it's outside*/
			if (inp)
			{
			    /*Transition from in to out*/
			    SetVar(object, VALUE, NewReal(oldTime));
			    UpdateTimeReadout(object);
			    SetVar(object, HIGHLIGHTED, ObjFalse);
			    inp = false;
#ifdef SCROLLTIME
			    if (AutoScroll(object))
			    {
				x = -12345;
			    }
#endif			    
			    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 (timeSteps)
			{
			    /*Constrain the time to the closest time step*/
			    long index;			/*Index into timesteps*/
			    real *timeElements;		/*Elements of the timesteps*/

			    timeElements = ELEMENTS(timeSteps);

			    index = SearchReal(timeSteps, curTime);
			    if (index <= 0)
			    {
				curTime = timeElements[0];
			    }
			    else if (index >= DIMS(timeSteps)[0])
			    {
				curTime = timeElements[DIMS(timeSteps)[0] - 1];
			    }
			    else if (timeElements[index] - curTime >
				     curTime - timeElements[index - 1])
			    {
				curTime = timeElements[index - 1];
			    }
			    else
			    {
				curTime = timeElements[index];
			    }
			}
			SetVar(object, VALUE, NewReal(curTime));
			UpdateTimeReadout(object);
#ifdef SCROLLTIME
			if (AutoScroll(object))
			{
			    x = -12345;
			}
#endif			    
			DrawMe(object);
		    }
		}
	    }

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

#if 0
	int xOff, yOff;
	GETSCROLL(object, xOff, yOff);

	SetClipRect(left + FIELDDEPTH, right - FIELDDEPTH, bottom + FIELDDEPTH, top - FIELDDEPTH);
	SetOrigin(left + FIELDDEPTH + xOff, bottom + FIELDDEPTH + yOff);
        x -= left + FIELDDEPTH + xOff;
        y -= bottom + FIELDDEPTH + yOff;
	
	pressContents = GetMethod(object, PRESSCONTENTS);
	if (pressContents)
	{
	    (*pressContents)(object, x, y, flags);
	}

	RestoreOrigin();
	RestoreClipRect();
#endif
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}
#endif

static ObjPtr ChangeTimeControlScroll(scrollbar)
ObjPtr scrollbar;
/*Changes the time control scrollbar*/
{
    ObjPtr repObj;
    repObj = GetVar(scrollbar, REPOBJ);
    if (repObj)
    {
	ImInvalid(repObj);
    }
    return ObjTrue;
}

static ObjPtr MakeTimeControlDatasets(control)
ObjPtr control;
/*Makes the time control's DATASETS*/
{
    SetVar(control, DATASETS, GetVar(GetVar(control, REPOBJ), DATASETS));
    RecalcScroll(control);
    return ObjTrue;
}

static ObjPtr MakeTimeControlTimesteps(control)
ObjPtr control;
/*Makes the time control's timesteps*/
{
    ObjPtr datasets;
    ObjPtr element;
    ObjPtr steps = NULLOBJ;
    long k;

    MakeVar(control, DATASETS);
    datasets = GetVar(control, DATASETS);
    if (datasets)
    {

    for (k = 0; k < DIMS(datasets)[0]; ++k)
    {
	ObjPtr curTimeSteps;
	element = GetObjectElement(datasets, &k);
	MakeVar(element, TIMESTEPS);
	curTimeSteps = GetVar(element, TIMESTEPS);
	if (curTimeSteps)
	{
	    if (steps)
	    {
		steps = MergeRealArrays(steps, curTimeSteps);
	    }
	    else
	    {
		steps = curTimeSteps;
	    }
	}
    }
    }
    SetVar(control, TIMESTEPS, steps);
    return ObjTrue;
}

static ObjPtr MakeTimeControlFormat(control)
ObjPtr control;
/*Makes the time control's TIMEFORMAT*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("MakeTimeControlFormat", control, REPOBJ);
    if (repObj)
    {
	MakeVar(repObj, TIMEFORMAT);
	SetVar(control, TIMEFORMAT, GetVar(repObj, TIMEFORMAT));
	RecalcScroll(control);
    }
}

static ObjPtr RecalcTimeControlScroll(control)
ObjPtr control;
/*Recalcs the time control scrolling and other parameters*/
{
    real value;
    ObjPtr var, scrollbar, repObj, readout;
    int left, right, bottom, top;
    ObjPtr datasets;
    int format;

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

    repObj = GetVar(control, REPOBJ);
    MakeVar(control, DATASETS);
    datasets = GetVar(control, DATASETS);
    MakeVar(control, TIMEFORMAT);
    var = GetVar(control, TIMEFORMAT);
    if (var)
    {
	format = GetInt(var);
    }
    else
    {
	format = TF_SECONDS + TF_SUBSECONDS;
    }

    readout = GetObjectVar("RecalcTimeControlScroll", control, READOUT);
    if (!readout)
    {
	return false;
    }

    /*Set the horizontal scroll bar*/
    scrollbar = GetVar(control, HSCROLL);
    if (scrollbar)
    {
	ObjPtr timeSteps;
	real lo, hi, minTimeStep;
	Bool stepSet = false;
	Bool loHiSet = false;
	Bool someTime = false;

	/*Calculate timeSteps*/
	if (datasets)
	{
	    ObjPtr element;
	    long k;

	    for (k = 0; k < DIMS(datasets)[0]; ++k)
	    {
		ObjPtr timeSteps;

		element = GetObjectElement(datasets, &k);
		MakeVar(element, TIMESTEPS);

		timeSteps = GetVar(element, TIMESTEPS);
		if (timeSteps)
		{
		    ObjPtr deltas;
		    deltas = SortArray(Uniq(RealArrayDeltas(timeSteps)));

		    someTime = true;

		    /*Set the lo and hi*/
		    if (loHiSet)
		    {
			lo = MIN(lo,
				 *((real *) ELEMENTS(timeSteps)));
			hi = MAX(hi,
				 ((real *) ELEMENTS(timeSteps))[DIMS(timeSteps)[0] - 1]); 
		    }
		    else
		    {
			lo = *((real *) ELEMENTS(timeSteps));
			hi = ((real *) ELEMENTS(timeSteps))[DIMS(timeSteps)[0] - 1];
			loHiSet = true;
		    }

		    /*Set the step only if there is one*/
		    if (DIMS(timeSteps)[0] > 1)
		    {
			if (stepSet)
			{
			    minTimeStep = MIN(minTimeStep,
					  *((real *) ELEMENTS(deltas)));
			}
			else
			{
			    minTimeStep = *((real *) ELEMENTS(deltas));
			    stepSet = true;
			}
		    }
		}
	    }
	}
	if (someTime)
	{
	    ActivateTextBox(readout, true);
	}
	else
	{
	    ActivateTextBox(readout, false);
	    /*Kludge to make time go away*/
	    SetVar(control, VALUE, NULLOBJ);
	    SetVar(control, VALUESET, ObjFalse);
	    SetVar(repObj, TIME, NULLOBJ);
	    UpdateTimeReadout(control);
	}
	if (loHiSet)
	{
	    /*Deal with existing value*/
	    var = GetVar(control, VALUE);
	    if (var)
	    {
		if (!GetPredicate(control, VALUESET))
		{
		    /*It hasn't been set yet*/
		    SetVar(control, VALUESET, ObjTrue);
		    SetSliderValue(scrollbar, GetReal(var));
		}
#if 0
		/*There already is a value.  Extend lo and hi*/
		value = GetReal(var);
		lo = MIN(lo, value);
		hi = MAX(hi, value);
#endif
	    }
	    else
	    {
		/*There's a new value*/
		value = lo;
		SetValue(control, NewReal(value));
		SetSliderValue(scrollbar, value);
	    }
	    SetSliderRange(scrollbar, hi, lo, minTimeStep);
	}
	else
	{
	    SetSliderValue(scrollbar, 0.0);
	    SetSliderRange(scrollbar, 0.0, 1.0, 20.0);
	}
	if (stepSet)
	{
	    long deltaLog;
	    int nTics;
	    real timePerPixel, timeShown, trialDisplay;
	    /*There is more than one time sample.  Calculate time per pixel
	      and scrollbar coverage*/
	    timePerPixel = minTimeStep / TCSTEPPIXELS;
	    SetVar(control, TIMEPERPIXEL, NewReal(timePerPixel));
	    timeShown = (right - 1 - (left + TCDSWIDTH + 2 * TCGAP + BARWIDTH + 1))
			* timePerPixel;
	    SetPortionShown(scrollbar, timeShown);

	    /*Also calculate DISPLAYSTEP*/
	    if ((format & TF_HOURS) || (format & TF_MINUTES))
	    {
		/*It's HMS*/
		if (minTimeStep < 10.0)
		{
		    /*Might as well ignore hours and minutes*/
		    deltaLog = ((long) (1000.0 + log10((double) minTimeStep))) - 1000;
		    trialDisplay = pow((double) 10.0, (double) deltaLog);
		    nTics = 10;
		}
		else
		{
		    deltaLog = ((long) (1000.0 + log10((double) minTimeStep) / log10(60.0))) - 1000;
		    trialDisplay = pow((double) 60.0, (double) deltaLog);
		    nTics = 10;

		    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
		    {
			/*Try 5*/
			trialDisplay *= 5.0;
			nTics = 5;
		    }
		    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
		    {
			/*Try 15*/
			trialDisplay *= 3.0;
			nTics = 3;
		    }
		    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
		    {
			/*Try 30*/
			trialDisplay *= 2.0;
			nTics = 2;
		    }
		    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
		    {
			/*Try 60*/
			trialDisplay *= 2.0;
			nTics = 4;
		    }
		    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
		    {
			/*Try 5*/
			trialDisplay *= 5.0;
			nTics = 5;
		    }
		    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
		    {
			/*Try 15*/
			trialDisplay *= 3.0;
			nTics = 3;
		    }
		    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
		    {
			/*Try 30*/
			trialDisplay *= 2.0;
			nTics = 2;
		    }
		    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
		    {
			/*Try 60*/
			trialDisplay *= 2.0;
			nTics = 4;
		    }
		}
	    }
	    else
	    {
		deltaLog = ((long) (1000.0 + log10((double) minTimeStep))) - 1000;
		trialDisplay = pow((double) 10.0, (double) deltaLog);
		nTics = 10;
	    }

	    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
	    {
		/*Try 2*/
		trialDisplay *= 2.0;
		nTics = 2;
	    }
	    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
	    {
		/*Try 5*/
		trialDisplay *= 2.5;
		nTics = 5;
	    }
	    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
	    {
		/*Try 10*/
		trialDisplay *= 2.0;
		nTics = 10;
	    }
	    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
	    {
		/*Try 2*/
		trialDisplay *= 2.0;
		nTics = 2;
	    }
	    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
	    {
		/*Try 5*/
		trialDisplay *= 2.5;
		nTics = 5;
	    }
	    if (trialDisplay / timePerPixel < TCTIMEPIXELS)
	    {
		/*Try 10*/
		trialDisplay *= 2.0;
		nTics = 10;
	    }
	    SetVar(control, DISPLAYSTEP, NewReal(trialDisplay));
	    SetVar(control, DISPLAYTICS, NewInt(nTics));
	}
	else
	{
	    /*No more than one time sample, no need to have any time per pixel*/
	    SetVar(control, TIMEPERPIXEL, NULLOBJ);
	    SetPortionShown(scrollbar, 1.0);
	}
    }

    /*Set the vertical scroll bar*/
    scrollbar = GetVar(control, VSCROLL);
    if (scrollbar)
    {
	if (datasets)
	{
	    long dsSize, boxSize;
	    dsSize = TCCELLHEIGHT * DIMS(datasets)[0];
	    boxSize = top - TCCURHEIGHT - TCTIMEHEIGHT - TCGAP - 1 - (bottom + TCGAP + BARWIDTH + 1);
	    SetSliderRange(scrollbar, 0.0, -(dsSize - boxSize), TCCELLHEIGHT);
	    SetPortionShown(scrollbar, (real) dsSize);
	}
	else
	{
	    SetSliderRange(scrollbar, 0.0, -1.0, 20.0);
	    SetPortionShown(scrollbar, 1.0);
	}
    }

    return ObjTrue;
}

static ObjPtr SetTimeControlValue(control, value)
ObjPtr control;
ObjPtr value;
/*Sets the value of a time control*/
{
    if (IsReal(value))
    {
	SetVar(control, VALUE, value);
    }
    else if (IsInt(value))
    {
	SetVar(control, VALUE, NewReal(GetInt(value)));
    }
    else
    {
	return ObjFalse;
    }
    UpdateTimeReadout(control);
    if (logging)
    {
	LogControl(control);
    }
    ImInvalid(control);
    ChangedValue(control);
    return ObjTrue;
}

static ObjPtr ChangeTimeReadout(readout)
ObjPtr readout;
/*CHANGEDVALUE for a time control readout*/
{
    ObjPtr value;
    char *s;
    real t;
    int f;
    int position;
    ObjPtr parent;

    value = GetValue(readout);
    if (!value)
    {
	return ObjFalse;
    }
    s = GetString(value);

    parent = GetVar(readout, PARENT);
    if (!parent)
    {
	return ObjFalse;
    }
    
    position = ParseTime(&t, &f, s);
    if (position > 0)
    {
	FuncTyp method;
	/*It's OK*/
	method = GetMethod(readout, CHANGEDVALUE);
	SetMethod(readout, CHANGEDVALUE, 0);
	InhibitLogging(true);
	SetValue(parent, NewReal(t));
	AutoScroll(parent);
	InhibitLogging(false);
	SetMethod(readout, CHANGEDVALUE, method);
    }
    else
    {
	DoUniqueTask(DoNumberError);
    }
    return ObjTrue;
}

ObjPtr TimeControlBoundsInvalid(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

  Contents are assumed to be shifted in their space
*/
{
    ObjPtr contents;
    ObjPtr myBounds;
    real boundsElements[4];
    real testElements[4];
    ObjPtr test;
    real myBoundsElements[4];
    ObjPtr scrollBar, readout;
    Bool firstTime = true;
    Bool doubleNoBounds = false;

    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;
	    }
	}

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

    return NULLOBJ;
}

ObjPtr NewTimeControl(l, r, b, t, name)
int l, r, b, t;
char *name;
/*Makes a new time control within l, r, b, t with name name*/
{
    ObjPtr retVal, scrollbar, readout;
    retVal = NewObject(timeControlClass, 0);

    if (!retVal)
    {
	return;
    }
    Set2DIntBounds(retVal, l, r, b, t);
    SetVar(retVal, NAME, NewString(name));
    
    /*Create the scroll bars*/
    scrollbar = NewScrollbar(l + TCDSWIDTH + TCGAP, l + TCDSWIDTH + TCGAP + BARWIDTH,
			     b + BARWIDTH + TCGAP,
			     t - TCTIMEHEIGHT - TCCURHEIGHT - TCGAP,
			     "Dataset Scroll");
    SetVar(scrollbar, PARENT, retVal);
    SetVar(scrollbar, REPOBJ, retVal);
    SetMethod(scrollbar, CHANGEDVALUE, ChangeTimeControlScroll);
    SetVar(retVal, VSCROLL, scrollbar);

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

    /*Create the readout*/
    readout = NewTextBox(l, l + TCDSWIDTH + TCGAP + BARWIDTH,
	t - TCTIMEHEIGHT - TCCURHEIGHT, t - TCTIMEHEIGHT,
	EDITABLE + WITH_PIT + ONE_LINE, "Time Readout", "0.0");
    SetMethod(readout, CHANGEDVALUE, ChangeTimeReadout);
    SetVar(readout, PARENT, retVal);
    SetTextAlign(readout, RIGHTALIGN);
    SetVar(retVal, READOUT, readout);

    return retVal;
}

void InitTimers()
/*Initializes the timers system*/
{
    int k;
    struct tms buffer;

    startTime = times(&buffer);

    timedObjClass = NewObject(NULLOBJ, 0);
    DeclareDependency(timedObjClass, CURDATA, TIME);
    DeclareDependency(timedObjClass, CURDATA, TIMESTEPS);
    DeclareDependency(timedObjClass, CURDATA, TIMEDATA);
    SetVar(timedObjClass, CLASSID, NewInt(CLASS_TIMEDOBJ));
    SetMethod(timedObjClass, CURDATA, MakeTimedCurData);
    DeclareDependency(timedObjClass, TIMEBOUNDS, TIMESTEPS);    
    DeclareDependency(timedObjClass, TIMEBOUNDS, TIMEDATA);    
    SetMethod(timedObjClass, TIMEBOUNDS, MakeTimeBounds);

    AddToReferenceList(timedObjClass);

    timeControlClass = NewObject(controlClass, 0);
    SetMethod(timeControlClass, DRAW, DrawTimeControl);
    SetMethod(timeControlClass, FINDOBJECT, FindObjectTimeControl);
#ifdef INTERACTIVE
    SetMethod(timeControlClass, PRESS, PressTimeControl);
#endif
    SetMethod(timeControlClass, BOUNDSINVALID, TimeControlBoundsInvalid);
    SetMethod(timeControlClass, KEYDOWN, KeyDownTimeControl);
    SetMethod(timeControlClass, RECALCSCROLL, RecalcTimeControlScroll);
    SetMethod(timeControlClass, SETVAL, SetTimeControlValue);
    SetMethod(timeControlClass, AUTOSCROLL, AutoScrollTimeControl);
    SetVar(timeControlClass, TYPESTRING, NewString("time control"));
    SetVar(timeControlClass, HELPSTRING,
	NewString("This control shows the current time in a clock.  On the \
left is a box containing the names all the datasets in all the visualization objects that this \
clock controls.  On the right is the time line of each dataset, lined up with \
the dataset name.  A horizontal blue line means that the dataset is eternal and \
is defined for all time.  Blue diamonds indicate time steps in time-dependent \
data.  Between the diamonds, dashed black lines indicate that the nearest \
timestep to the current time will be displayed.  Solid black lines indicate that the data will be \
interpolated between time steps.  You can change whether a dataset will be \
interpolated using the Show Info button \
in the Datasets window.\n\
\n\
At the top left is a text box that shows the current time.  At the top right is \
a slider connected to a cursor that shows the current time as well as its relation \
to the datasets controlled by the clock.  You can change the current time either by \
editing the text or moving the indicator.  Hold down the Shift key while moving \
the indicator to constrain to actual time steps.  If none of the datasets are time-dependent, \
both of these will show as gray."));
    DeclareIndirectDependency(timeControlClass, DATASETS, REPOBJ, DATASETS);
    SetMethod(timeControlClass, DATASETS, MakeTimeControlDatasets);
    DeclareIndirectDependency(timeControlClass, TIMEFORMAT, REPOBJ, TIMEFORMAT);
    SetMethod(timeControlClass, TIMEFORMAT, MakeTimeControlFormat);
    DeclareDependency(timeControlClass, TIMESTEPS, DATASETS);
    SetMethod(timeControlClass, TIMESTEPS, MakeTimeControlTimesteps);
    AddToReferenceList(timeControlClass);

    for (k = 0; k < NALARMS; ++k)
    {
	alarms[k] . object = 0;
    }
}

void KillTimers()
/*Kills the timers system*/
{
    int k;
    
    DeleteThing(timeControlClass);
    DeleteThing(timedObjClass);
    for (k = 0; k < NALARMS; ++k)
    {
	if (alarms[k] . object)
	{
	    ObjPtr object;
	    object = alarms[k] . object;
	    alarms[k] . object = 0;
	    DeleteThing(object);
	}
    }
}
