/*ScianScripts.c
  Stuff for scripts in scian
  Eric Pepke
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianArrays.h"
#include "ScianIDs.h"
#include "ScianEvents.h"
#include "ScianRecorders.h"
#include "ScianColors.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianDialogs.h"
#include "ScianVisWindows.h"
#include "ScianScripts.h"
#include "ScianSliders.h"
#include "ScianTimers.h"
#include "ScianMethods.h"
#include "ScianLists.h"
#include "ScianErrors.h"
#include "ScianGarbageMan.h"
#include "ScianSpaces.h"
#include "ScianIcons.h"
#include "ScianSockets.h"
#include "ScianObjFunctions.h"
#include "ScianHelp.h"
#include "ScianStyle.h"
#include "ScianNames.h"

#define SCRIPTALLOCBATCH	200	/*Batch to allocate script*/
Bool runningScript = false;		/*True iff running script*/
Bool settingUp = false;			/*True iff setting up*/
WinInfoPtr scriptWindow = 0;		/*The current window of the script*/
extern ObjPtr perspecControlClass;	/*Class of perspecive controls*/
long framesLeft = 0;			/*Frames left to record*/
Bool abortScript = false;		/*Abort script*/
FILE *curScript = 0;			/*Current script being read, if any*/
char *scriptLine = 0;			/*Current line of the script*/
int nScriptCharsAllocated = 0;		/*Number of script characters allocated*/
Bool lineAccepted = true;		/*True iff last script line was accepted*/

/*IDs for running tasks*/
#define RT_NONE		0		/*No running task*/
#define RT_RECORDING	1		/*Recording some time*/
int runningTask = RT_NONE;		/*Current running task*/

#define MAXSTACKDEPTH	100		/*Maximum depth of block stack*/

/*Block in a stack*/
typedef struct
    {
	int command;			/*The command*/
	ObjPtr object;			/*The (optional) object this is about*/
    } Block;

Block commandStack[MAXSTACKDEPTH];	/*Stack of commands*/
int nextStackElement = 0;		/*Next element on the stack*/

typedef struct				/*Element of file logging stack*/
    {
	FILE *logFile;			/*File pointer for logging*/
	int logInhibit;			/*Counter to inhibit logging*/
	WinInfoPtr logWindow;		/*Current window set in log*/
    } LogElement;

#define MAXLOGFILES	50		/*Maximum number of log files open*/

LogElement logStack[MAXLOGFILES];	/*Stack of log files*/
int nextLogElement = 0;			/*Next element on the stack*/

#define KWBEGIN		0	/*Begin a block*/
#define KWEND		1	/*End current modal command*/
#define KWRECORDER	2	/*(set) recorder*/
#define KWDESELECT	3	/*Deselect an icon*/
#define KWSELECT	4	/*Select an icon*/
#define KWSHELL		5	/*Shell escape*/
#define KWALIGNMENT	6	/*(Set) alignment*/
#define KWLOCATION	7	/*(Set) location*/
#define KWVIDEOSCREEN	8	/*Resize current window to video screen*/
#define KWFULLSCREEN	9	/*Resize current window to full screen*/
#define KWROTATE	10	/*Rotate space*/
#define KWSHOW		11	/*Show something*/
#define KWPANEL		12	/*(Show/Hide) the panel of icons*/
#define KWRECORDING	13	/*(Begin/End) recording*/
#define KWSNAP		14	/*Snap a single frame*/
#define KWWINDOW	15	/*Select a new window*/
#define KWSET		16	/*Set something*/
#define KWCONTROLS	17	/*(Show) controls for a vis object*/
#define KWPUSHWINDOW	18	/*Push a window behind all others*/
#define KWQUIT		19	/*Quit*/
#define KWSHEAR		20	/*Shear the matrix*/
#define KWFPS		21	/*(Set) fps*/
#define KWEYEPOSN	22	/*Eye position*/
#define KWROLL		23	/*Roll of eyeball*/
#define KWPITCH		24	/*Pitch of eyeball*/
#define KWYAW		25	/*Yaw of eyeball*/
#define KWRECTANGLE	26	/*Rectangle*/
#define KWHIDE		27	/*Hide the frame of a window*/
#define KWEXIT		28	/*Exit on next loop*/
#define KWVALUE		29	/*(Set) value of a something*/
#define KWFONT		30	/*(Set) the font of something*/
#define KWSIZE		31	/*(Set) the size of something's font*/
#define KWCLOSE		32	/*Closes a window*/
#define KWENDPOINTS	33	/*(Set) endpoints*/
#define KWDROP		34	/*Drops whatever is in the drag buffer*/
#define KWBOUNDS	35	/*(Set) the bounds of an object*/
#define KWTILE		36	/*Tile to a certain width and height*/
#define KWANNOTATION	37	/*Add annotation*/
#define KWFRAME		38	/*(Show/Hide) the frame of a window*/
#define KWLOCATE	39	/*Locate a window*/
#define KWDELETE	40	/*Delete stuff*/
#define KWSCRSAVE	41	/*Save the screen*/
#define KWRECORD	42	/*Record some number of seconds*/
#define KWSCREEN	43	/*(Set) screen*/
#define KWCOLOR		44	/*(Set) color*/
#define KWPREFERENCES	45	/*(Show) preferences*/
#define KWTIME		46	/*Time*/
#define KWOFF		47	/*(Turn) off*/
#define KWTURN		48	/*Turn (on/off)*/
#define KWON		49	/*(Turn) on*/
#define KWLINE		50	/*Make a new line*/
#define KWFUNCTIONBOX	51	/*(Set) Function box*/
#define KWFILESWINDOW	52	/*(Show) FilesWindow*/
#define KWSELECTALL	53	/*Selectall*/
#define KWDESELECTALL	54	/*Deselectall*/
#define KWHELP		55	/*(Show) help*/
#define KWFILEREADERS   56	/*(Show) fileReaders*/
#define KWDATASETS	57	/*(Show) datasets*/
#define KWROTATION	58	/*(Set) rotation*/
#define KWVERSION	59	/*Version*/
#define KWFRONT		60	/*(Show) front (panel controls)*/
#define KWBACK		61	/*(Show) back (panel controls)*/
#define KWSETUP		62	/*(Begin/End) setup*/
#define KWSAVE		63	/*Save something*/
#define KWTIMEREADOUT	64	/*Make a time readout*/
#define KWSPACE		65	/*(Show) space (controls)*/
#define KWEFFECT	66	/*Color special effect*/
#define KWKEEP		67	/*Keep changes to palette*/
#define KWREVERT	68	/*Revert to last saved palette*/
#define KWMODEL		69	/*Color model*/
#define KWSNAPSHOT	70	/*(Begin) Snapshot*/
#define KWOBSERVER	71	/*(Begin Snapshot) Observer*/
#define KWVARIABLE	72	/*(set) Variable <value>*/
#define KWOBJECT	73	/*(Begin Snapshot) */Object
#define NKEYWORDS	74	/*Number of keywords*/


char *keywords[NKEYWORDS] =
	{
	    "begin",
	    "end",
	    "recorder",
	    "deselect",
	    "select",
	    "shell",
	    "alignment",
	    "location",
	    "videoscreen",
	    "fullscreen",
	    "rotate",
	    "show",
	    "panel",
	    "recording",
	    "snap",
	    "window",
	    "set",
	    "controls",
	    "pushwindow",
	    "quit",
	    "shear",
	    "fps",
	    "eyeposn",
	    "roll",
	    "pitch",
	    "yaw",
	    "rectangle",
	    "hide",
	    "exit",
	    "value",
	    "font",
	    "size",
	    "close",
	    "endpoints",
	    "drop",
	    "bounds",
	    "tile",
	    "annotation",
	    "frame",
	    "locate",
	    "delete",
	    "scrsave",
	    "record",
	    "screen",
	    "color",
	    "preferences",
	    "time",
	    "off",
	    "turn",
	    "on",
	    "line",
	    "functionbox",
	    "fileswindow",
	    "selectall",
	    "deselectall",
	    "help",
	    "filereaders",
	    "datasets",
	    "rotation",
	    "version",
	    "front",
	    "back",
	    "setup",
	    "save",
	    "timereadout",
	    "space",
	    "effect",
	    "keep",
	    "revert",
	    "model",
	    "snapshot",
	    "observer",
	    "variable",
	    "object"
	};

char *wholeLine;		/*Pointer to the whole line for error*/
char *begToken, *endToken;	/*Beginning and ending of current token*/

#ifdef PROTO
Bool BeginCommand(char *);
Bool EndCommand(char *);
#else
Bool BeginCommand();
Bool EndCommand();
#endif

FILE *OpenLogFile(name)
char *name;
/*Opens a log file named name in the current directory.  OpenLogFile and
  CloseLogFile may be nested.  If !name, it's stdout*/
{
    struct timeval date;

    if (name)
    {
	logStack[nextLogElement] . logFile = fopen(name, "w");
    }
    else
    {
	logStack[nextLogElement] . logFile = stdout;
    }

    if (logStack[nextLogElement] . logFile)
    {
	logStack[nextLogElement] . logInhibit = 0;
	logStack[nextLogElement] . logWindow = 0;
    }
    else
    {
	WinInfoPtr alertWindow;
	char msg[300];
	getcwd(tempStr, TEMPSTRSIZE);

	sprintf(msg, "File %s cannot be saved in directory %s.",
		name, tempStr);
	alertWindow = AlertUser(UIERRORALERT, (WinInfoPtr) NULLOBJ,
			msg, (FuncTyp) 0, 1, "Oh Well");
	SetVar((ObjPtr) alertWindow, HELPSTRING,
		NewString("A file cannot be saved in the specified directory.  \
This is probably because the permissions on the directory are not set up to allow \
you to write to it.  Check the permissions on the directory and try again."));
	return 0;
    }

    /*Write out a version number as first line in file*/
    fprintf(logStack[nextLogElement] . logFile, "%s\n", SCIANVERSION);

    /*And a time stamp*/
    gettimeofday(&date, (struct timezone *)0);

    fprintf(logStack[nextLogElement] . logFile, "time %s\n", ctime(&date.tv_sec));

    ++nextLogElement;
}

void CloseLogFile()
/*Closes a log file opened by OpenLogFile.*/
{
    if (!nextLogElement) return;
    --nextLogElement;
    if (logStack[nextLogElement] . logFile != stdout)
    {
	fclose(logStack[nextLogElement] . logFile);
    }
}

Bool logBeginning = true;	/*True iff it's at the beginning of a log line*/

Bool AnyLogging()
/*Returns true iff any logging is to be done*/
{
    if (!logging)
    {
	return false;
    }

    if (logStack[nextLogElement - 1] . logInhibit)
    {
	return false;
    }
    return true;
}

void Log(s)
char *s;
/*Logs command s to all the open log files*/
{
    int whichLog;
    char *t;

    if (selWinInfo && GetPredicate((ObjPtr) selWinInfo, INHIBITLOGGING))
    {
	return;
    }

    for (whichLog = nextLogElement - 1; whichLog >= 0; --whichLog)
    {
	if (logStack[whichLog] . logInhibit)
	{
	    /*Done; don't do no more.*/
	    break;
	}
        
	if (logBeginning && selWinInfo && logStack[whichLog] . logWindow != selWinInfo)
	{
	    /*Make a window command*/
	    char windowName[256];
	    int k;

	    /*Copy window title*/
	    t = selWinInfo -> winTitle;
	    k = 0;
	    while (*t)
	    {
		if (*t == ' ')
		{
		    windowName[k++] = '_';
		    ++t;
		}
		else
		{
		    if (!isalpha(*t) && ((k == 0) || !isdigit(*t)))
		    {
			windowName[k++] = '\\';
		    }
		    windowName[k++] = *t++;
		}
	    }
	    windowName[k] = 0;

	    fprintf(logStack[whichLog] . logFile, "window %s\n", windowName);
	    logStack[whichLog] . logWindow = selWinInfo;
	}
	fputs(s, logStack[whichLog] . logFile);
    }
	/*Determine if it's in a line*/
	t = s;
	if (*t)
	{
	    while (*t) ++t;
	    --t;
	    if (*t == '\n')
	    {
		logBeginning = true;
	    }
	    else
	    {
		logBeginning = false;
	    }
	}
}

#ifdef PROTO
void InhibitLogging(Bool whether)
#else
void InhibitLogging(whether)
Bool whether;
#endif
/*Inhibits or disinhibits logging based on whether*/
{
    if (nextLogElement)
    {
	if (whether)
	{
	    ++logStack[nextLogElement - 1] . logInhibit;
	}
	else
	{
	    --logStack[nextLogElement - 1] . logInhibit;
	}
    }
}

#ifdef PROTO
void LogVariable(ObjPtr object, NameTyp variable)
#else
void LogVariable(object, variable)
ObjPtr object; NameTyp variable;
#endif
/*Logs a variable*/
{
    if (AnyLogging() && !GetPredicate(object, INHIBITLOGGING))
    {
	char cmd[256];
	char *s;

	sprintf(cmd, "    set variable %s ", GetInternalString(variable));
	s = &(cmd[0]);
	while (*s) ++s;
	PrintScriptObject(s, GetVar(object, variable));
	while (*s) ++s;
	*s++ = '\n';
	*s = 0;
	Log(cmd);
    }
}

void LogControl(object)
ObjPtr object;
{
    if (AnyLogging() && !GetPredicate(object, INHIBITLOGGING))
    {
	char cmd[256];
	char *s;

	sprintf(cmd, "set value ");
	s = &(cmd[0]);
	while (*s) ++s;
	MakeObjectName(s, object);
	while (*s) ++s;
	*s++ = ' ';
	PrintScriptObject(s, GetVar(object, VALUE));
	while (*s) ++s;
	*s++ = '\n';
	*s = 0;
	Log(cmd);
    }
}

void LogObjectName(object)
ObjPtr object;
/*Logs the name of an object*/
{
    char objName[400];
    MakeObjectName(objName, object);
    Log(objName);
}

void ScriptError(e)
char *e;
/*Prints out an error e*/
{
    char *runner;
    fprintf(stderr, "%s\n", wholeLine);

    runner = wholeLine;
    while (runner < begToken)
    {
	fprintf(stderr, *runner == '\t' ? "\t" : " ");
	++runner;
    }
    if (runner < endToken)
    {
	while (runner < endToken)
	{
	    fprintf(stderr, "-");
	    ++runner;
	}
    }
    else
    {
	fprintf(stderr, "^");
    }
    fprintf(stderr, "\n");
    fprintf(stderr, "%s\n", e);
    if (abortScriptP)
    {
	abortScript = true;
    }
}

void SelectNamedWindow(name)
char *name;
/*Selects a window by name name*/
{
    WinInfoPtr namedWindow;
    namedWindow = GetWinFromTitle(name);
    if (!namedWindow)
    {
	ScriptError("Cannot find a unique window by that name.");
	return;
    }
    SelWindow(namedWindow);
    scriptWindow = namedWindow;
}

static ObjPtr testObj;
static WinInfoPtr repWindow;
static ObjPtr globalRep;

void TestWindowOnObject(window)
WinInfoPtr window;
/*Tests to see if window has an object which represents testObj*/
{
    ObjPtr rep;
    if (rep = ObjectWhichRepresents(window, testObj))
    {
	repWindow = window;
	globalRep = rep;
    }
}

void MakeObjectName(dest, object)
char *dest;
ObjPtr object;
/*Makes a long object name from object and puts it into dest*/
{
    register char *s;
    ObjPtr name;
    ObjPtr representative = NULLOBJ;

    if (!IsWindow(object) &&
	((!IsValidWindow(selWinInfo)) ||
	 (!(representative = ObjectWhichRepresents(selWinInfo, object)))))
    {
	/*Must find a window that knows about the object*/
	testObj = object;
	repWindow = (WinInfoPtr) 0;

	ForAllWindows(TestWindowOnObject);

	if (repWindow)
	{
	    representative = globalRep;
	    MakeObjectName(dest, (ObjPtr) repWindow);
	    while (*dest) ++dest;
	}
	else
	{
	    *dest++ = '?';
	}
	*dest++ = ':';
    }

    if (representative)
    {
	object = representative;
    }

    name = GetStringVar("MakeObjectName", object, NAME);
    if (!name)
    {
	*dest = 0;
	return;
    }

    /*Copy name*/
    s = GetString(name);
    if (isdigit(*s))
    {
	*dest++ = '\\';
    }
    while (*s)
    {
	if (*s == ' ')
	{
	    *dest++ = '_';
	    ++s;
	}
	else
	{
	    if (!isalpha(*s) && !isdigit(*s))
	    {
		*dest++ = '\\';
	    }
	    *dest++ = *s++;
	}
    }
    *dest = 0;
}

int ParseKeyword(s, l, n)
char *s;
char *l[];
int n;
/*Parses keyword s in list l with number of elements n.Returns its number or -1 == not found, -2 ==
  ambiguious.*/
{
    int match = -1;		/*Last found match*/
    int i, k;			/*Counters*/

    /*Seek an exact match*/
    for (k = 0; k < n; ++k)
    {
	for (i = 0; s[i]; ++i)
	{
	    if (tolower(s[i]) != tolower(l[k][i]))
	    {
		break;
	    }
	}
	if (!s[i] && !keywords[k][i])
	{
	    /*It's a match!*/
	    return k;
	}
    }

    /*Seek a partial match*/
    for (k = 0; k < n; ++k)
    {
	for (i = 0; s[i]; ++i)
	{
	    if (tolower(s[i]) != tolower(l[k][i]))
	    {
		break;
	    }
	}
	if (!s[i])
	{
	    /*It's a match!*/
	    if (match >= 0)
	    {
		/*Double match*/
		return -2;
	    }
	    match = k;
	}
    }
    return match;
}

#ifdef PROTO
char *ParseArray(char *s, ObjPtr *o)
#else
char *ParseArray(s, o)
char *s;
ObjPtr *o;
#endif
/*
    Parses an array at s
    Returns
	s if the object was syntactically bad or null
	the first character after the object if it was OK.
	Puts in o the object if it was semantically valid
        Leavs o alone if it was not
    Generates a script error if syntactically or semantically invalid.
    Doesn't yet do arrays of higher order than vectors by recursion
*/
{
    char *r;
    r = s;

    SKIPBLANKS(r);
    if (*r != '[')
    {
	ScriptError("This is not a valid array");
	return s;
    }

    ++r;
    SKIPBLANKS(r);

    /*Now, it's either some subvectors or a number*/
    if (*r == '[')
    {
	/*Subvector*/
	ScriptError("Arrays of higher order than vectors are not supported yet");
	return s;
    }
    else
    {
	/*Simple numerical vector.*/
	real *numBuf;		/*Temporary buffer for holding real numbers*/
	long bufSize;		/*Size of temporary buffer*/
	long vecSize;		/*Size of the vector so far*/
	char num[256];		/*Temporary number space*/

	/*Allocate some storage*/
	bufSize = 1000;
	vecSize = 0;
	numBuf = malloc(sizeof(real) * bufSize);
	if (!numBuf)
	{
	    OMErr();
	    return s;
	}

	while (*r != ']')
	{
	    SHIFTNUM(num, r);
	    if (1 != sscanf(num, "%g", numBuf + vecSize))
	    {
		ScriptError("This cannot be read as a number");
		free(numBuf);
		return s;
	    }
	    ++vecSize;
	    if (vecSize > bufSize)
	    {
		bufSize += 1000;
		numBuf = realloc(numBuf, bufSize);
		if (!numBuf)
		{
		    OMErr();
		    return s;
		}
	    }
	    SKIPBLANKS(r);
	}
	++r;

	/*Now make it into a vector*/
	*o = NewRealArray(1, vecSize);
	if (!o)
	{
	    free(numBuf);
	    return s;
	}
	CArray2Array(*o, numBuf);
	free(numBuf);

	return r;
    }
}

#ifdef PROTO
char *ParseObjectArg(char *s, ObjPtr *o)
#else
char *ParseObjectArg(s, o)
char *s;
ObjPtr *o;
#endif
/*
    Parses an object at s
    Returns
	s if the object was syntactically bad or null
	the first character after the object if it was OK.
	Puts in o the object if it was semantically valid
        Leavs o alone if it was not
    Generates a script error if syntactically or semantically invalid.
*/
{
    char arg[256];
    char *r;
    r = s;

    /*Get to the first character*/
    SKIPBLANKS(r);

    /*Based on one-char lookahead, parse the argument*/
    if (*r == '-' || *r == '+' || *r == '.' || isdigit(*r))
    {
	double val;

	/*It's a number*/
	SHIFTNUM(arg, r);
	if (1 == sscanf(arg, "%lg", &val))
	{
	    *o = NewReal((real) val);
	}
	else
	{
	    ScriptError("This number has bad syntax");
	    return s;
	}
    }
    else if (isalpha(*r) || (*r == '\\'))
    {
	/*It's an object name or NULLOBJ*/
	SHIFTKW(arg, r);

	if (0 == strcmp2(arg, "NULLOBJ"))
	{
	    *o = NULLOBJ;
	}
	else
	{
	    ObjPtr list = NULLOBJ;

	    if (*r == ':')
	    {
		char arg2[256];
		WinInfoPtr window;

		/*Compound name*/
		++r;
		SHIFTKW(arg2, r);
		if (strlen(arg2))
		{
		    window = GetWinFromTitle(arg);
		    if (window)
		    {
			list = FindNamedObject((ObjPtr) window, arg2);
		    }
		    else
		    {
			ScriptError("Cannot find a unique window by that name.");
			return s;
		    }
		}
		else
		{
		    ScriptError("An object name is expected after the window name.");
		    return s;
		}
	    }
	    else
	    {
		if (scriptWindow && IsValidWindow(scriptWindow))
		{
		    list = FindNamedObject((ObjPtr) scriptWindow, arg);
		}
		else
		{
		    ScriptError("There is no currently selected window.");
		}
	    }
	    if (!list || !LISTOF(list))
	    {
		ScriptError("There is no object by that name.");
	    }
	    else if (LISTOF(list) -> next)
	    {
		ScriptError("That name is not unique.");
	    }
	    else
	    {
		*o = LISTOF(list) -> thing;
	    }
	}
    }
    else if (*r == '[')
    {
	return ParseArray(r, o);
    }
    else if (*r == '"')
    {
	int k;
	/*It's a string*/
	begToken = r;
	++r;
	k = 0;
	while (*r && *r != '"')
	{
	    if (*r == '\\')
	    {
		++r;
		if (isdigit(*r))
		{
		    int nDigits, value;
		    value = 0;
		    nDigits = 0;
		    do
		    {
			value *= 8;
			value += *r - '0';
			++nDigits;
			++r;
		    } while (isdigit(*r) && nDigits < 3);
		    tempStr[k++] = value;
		}
		else 
		switch (*r)
		{
		    case 'n':
		    case 'N':
			tempStr[k++] = '\n';
			++r;
			break;
		    case 't':
		    case 'T':
			tempStr[k++] = '\t';
			++r;
			break;
		    case 'r':
		    case 'R':
			tempStr[k++] = '\r';
			++r;
			break;
		    default: tempStr[k++] = *r++;
			
		}
	    }
	    else
	    {
		tempStr[k++] = *r++;
	    }
	}
	if (*r == '"') ++r;
	endToken = r;
	tempStr[k] = 0;
	*o = NewString(tempStr);
    }
    else switch(*r)
    {
	case 0:		/*End of string, null object*/
	    *o = NULLOBJ;
	    break;
	default:	/*Who knows?  Error*/
	    ScriptError("This is an unrecognized object");
	    return s;
    }

    return r;
}  

char *PrintScriptString(dest, s)
char *dest, *s;
/*Prints a script string from s into dest*/
{
	*dest++ = '"';
	while (*s)
	{
	    if (*s == '"')
	    {
		*dest++ = '\\';
	    }
	    if (*s == '\n')
	    {
		*dest++ = '\\';
		*dest++ = 'n';
	    }
	    else if (*s == '\t')
	    {
		*dest++ = '\\';
		*dest++ = 't';
	    }
	    else if (*s == '\r')
	    {
		*dest++ = '\\';
		*dest++ = 'r';
	    }
	    else if (*s < ' ')
	    {
		*dest++ = '\\';
		sprintf(tempStr, "%3o", *s);
		*dest++ = tempStr[0];
		*dest++ = tempStr[1];
		*dest++ = tempStr[2];
	    }
	    else
	    {
		*dest++ = *s;
	    }
	    ++s;
	}
	*dest++ = '"';
	*dest = 0;
	return dest;
}

char *PrintScriptObject(s, object)
char *s;
ObjPtr object;
/*Prints object into s as for inclusion into a script.  Returns a pointer
  to the null afterward*/
{
    char *retVal;
    retVal = s;
    if (object == 0)
    {
	sprintf(retVal, "NULLOBJ");
	while (*retVal) ++retVal;
    }
    else if (IsReal(object))
    {
	/*Real number*/
	sprintf(retVal, "%g", (float) GetReal(object));
	while (*retVal) ++retVal;
    }
    else if (IsInt(object))
    {
	sprintf(retVal, "%ld", (long) GetInt(object));
	while (*retVal) ++retVal;
    }
    else if (IsRealArray(object) && RANK(object) == 1)
    {
	/*Vector*/
	int k;
	real *meat;

	*retVal++ = '[';
	meat = ArrayMeat(object);
	for (k = 0; k < DIMS(object)[0]; ++k)
	{
	    sprintf(retVal, "%g", *meat++);
	    while (*retVal) ++retVal;
	    if (k < DIMS(object)[0] - 1) *retVal++ = ' ';
	}
	*retVal++ = ']';
    }
    else if (IsString(object))
    {
	/*String*/
	retVal = PrintScriptString(retVal, GetString(object));
    }
    *retVal = 0;
    return retVal;
}

int FindCommandInStack(command)
int command;
/*Finds the topmost command command in the stack.  Returns it's index or -1
  if not found.*/
{
    int k;
    for (k = nextStackElement - 1; k >= 0; --k)
    {
	if (commandStack[k] . command == command)
	{
	    break;
	}
    }
    return k;
}

Bool BeginCommand(args)
char *args;
/*Begins a begin-end block with command in args.*/
{
    char cmdNum;
    char arg[256];

    if (nextStackElement >= MAXSTACKDEPTH)
    {
	ScriptError("There are too many nested begin-end blocks.");
	return true;
    }

    SHIFTKW(arg, args);

    if (strlen(arg))
    {
	cmdNum = ParseKeyword(arg, keywords, NKEYWORDS);
	if (cmdNum == -1)
	{
	    ScriptError("This command is unknown.");
	    return true;
	}
	else if (cmdNum == -2)
	{
	    ScriptError("This command is ambiguous.");
	    return true;
	}
    }
    else
    {
	/*It's an anonymous block*/
	cmdNum = KWBEGIN;
    }
    commandStack[nextStackElement] . command = cmdNum;
    commandStack[nextStackElement] . object = NULLOBJ;
    switch (cmdNum)
    {
	case KWBEGIN:
	    Log("begin\n");
	    break;
	case KWRECORDING:
	    Log("begin recording\n");
	    if (FindCommandInStack(KWRECORDING) >= 0)
	    {
		ScriptError("The begin recording command cannot be nested.");
		return true;
	    }
	    else
	    {
		real nSeconds;
		
		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%g", &nSeconds))
		{
		    ScriptError("A maximum time in seconds is expected here.");
		    return true;
		}
		if (nSeconds < 0.0) nSeconds = -nSeconds;
		if (ConnectRecorder())
		{
		    int nFrames;
		    nFrames = (int) (nSeconds * 30.0);
		    if (!PrepareToRecord(nFrames))
		    {
			ScriptError("There is a problem recording that many frames.");
			return true;
		    }
		}
		else
		{
		    ScriptError("Could not connect to the recorder.");
		    return true;
		}
	    }
	    break;
	case KWSETUP:
	    Log("begin setup\n");
	    settingUp = true;
	    break;
	case KWSNAPSHOT:
	    /*Beginning of an object snapshot*/
	    {
		ObjPtr value;
		value = ERROBJ;
		SKIPBLANKS(args);
		Log("begin snapshot ");
		Log(args);
		Log("\n");
		args = ParseObjectArg(args, &value);
		if (value != ERROBJ)
		{
		    commandStack[nextStackElement] . object = value;
		}
		else
		{
		    ScriptError("An object for the snapshot is expected here");
		    commandStack[nextStackElement] . object = NULLOBJ;
		}
	    }
	    break;
	default:
	    ScriptError("This command may not start a begin-end block");
	    return true;
    }
    ++nextStackElement;
    return true;
}

Bool EndCommand(args)
char *args;
/*Ends the top command on the stack, or up to command in args*/
{
    char arg[256];
    if (!nextStackElement)
    {
	ScriptError("There are too many ends and not enough begins.");
	return true;
    }

    SHIFTKW(arg, args);
    if (strlen(arg))
    {
	int cmdNum, cmdWhere;
	cmdNum = ParseKeyword(arg, keywords, NKEYWORDS);
	cmdWhere = FindCommandInStack(cmdNum);
	if (cmdWhere < 0)
	{
	    ScriptError("There is no such block in force.");
	    return true;
	}
	else
	{
	    do
	    {
		EndCommand("");
	    } while (commandStack[nextStackElement] . command != cmdNum);
	}
    }
    else
    {
	/*End the top command*/
	--nextStackElement;
	switch(commandStack[nextStackElement] . command)
	{
	    case KWBEGIN:
		Log("end\n");
		break;
	    case KWRECORDING:
		Log("end recording\n");
		StopRecording();
		DisconnectRecorder();
		break;
	    case KWSNAPSHOT:
		Log("end snapshot\n");
		if (commandStack[nextStackElement] . object)
		{
		    ImInvalid(commandStack[nextStackElement] . object);
		}
		break;
	    case KWSETUP:
		Log("end setup\n");
		settingUp = false;
		break;
	}
    }
    return true;
}

Bool DoSetCommand(kwn, args)
int kwn;
char *args;
/*Does a set command*/
{
    char arg[256];

    switch(kwn)
    {
	case KWVARIABLE:
	    /*Set a variable within a snapshot*/
	    {
		int element;

		/*DIKEO put in type checking*/
		element = FindCommandInStack(KWSNAPSHOT);
		if (element >= 0)
		{
		    /*Do a setvar, just like it says*/
		    NameTyp internalID;
		    ObjPtr value;

		    SHIFTKW(arg, args);
		    internalID = GetInternalID(arg);
		    
		    value = ERROBJ;
		    args = ParseObjectArg(args, &value);
		    if (value != ERROBJ)
		    {
			if (commandStack[element] . object)
			{
			    SetVar(commandStack[element] . object, internalID, value);
			    LogVariable(commandStack[element] . object, internalID);
			}
		    }
		    else
		    {
			ScriptError("A valid value is expected here");
		    }
		}
		else
		{
		    ScriptError("A set variable command can only occur within a snapshot");
		}
	    }
	    break;
	case KWRECORDER:
	    {
		ObjPtr object;
		if (FindCommandInStack(KWRECORDING) >= 0)
		{
		    ScriptError("You can't set the recorder while recording");
		    break;
		}
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsString(object))
		    {
			if (!SetRecorder(GetString(object)))
			{
			    ScriptError("This recorder does not exist");
			}
		    }
		    else
		    {
			ScriptError("A quoted name of a recorder is expected here");
		    }
		}
	    }
	    break;
	case KWVALUE:
	    /*Set the value of a control*/
	    {
		ObjPtr object, value;
		object = ERROBJ;

		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (object && IsObject(object))
		    {
			value = ERROBJ;
			args = ParseObjectArg(args, &value);
			if (value != ERROBJ)
			{
			    SetValue(object, value);
			}
			else
			{
			    ScriptError("A valid value is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWROTATION:
	    {
		ObjPtr rotation, axis;
		rotation = ERROBJ;

		args = ParseObjectArg(args, &rotation);
		if (rotation != ERROBJ)
		{
		    if (rotation && IsReal(rotation))
		    {
			axis = ERROBJ;
			args = ParseObjectArg(args, &axis);
			if (axis != ERROBJ && axis &&
			    IsRealArray(axis) && RANK(axis) == 1 && DIMS(axis)[0] == 3)
			{
			    real *elements;
			    real degrees;
			    elements = ELEMENTS(axis);
			    degrees = GetReal(rotation);
			    SetRotationMotor(degrees * M_PI / 180.0, elements[0], elements[1], elements[2]);
			}
			else
			{
			    ScriptError("A rotation axis is expected here");
			}
		    }
		    else
		    {
			ScriptError("A rotation speed is expected here");
		    }
		}
	    }
	    break;
	case KWFUNCTIONBOX:
	    {
		ObjPtr object, functionBox;
		object = ERROBJ;

		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			functionBox = ERROBJ;
			args = ParseObjectArg(args, &functionBox);
			if (functionBox != ERROBJ)
			{
			    SetFunctionBox(object, functionBox);
			    InhibitLogging(true);
			    ImposeColorFunction(object);
			    ForAllVisWindows(ImInvalid);
			    InhibitLogging(false);
			}
			else
			{
			    ScriptError("A valid function box is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWCOLOR:
	    {
		ObjPtr object;
		int which, r, g, b;

		object = ERROBJ;

		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			if (4 == sscanf(args, " %d %d %d %d \n", &which, &r, &g, &b))
			{
			    SetColorBarColor(object, which, r, g, b);
			}
			else
			{
			    ScriptError("A color number and components are expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWFONT:
	    {
		ObjPtr object, value;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			SHIFTKW(arg, args);
			SetFont(object, arg);
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWSIZE:
	    {
		ObjPtr object, value;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			int size;
			SHIFTNUM(arg, args);
			if (1 == sscanf(arg, "%d", &size))
			{
			    SetSize(object, size);
			}
			else
			{
			    ScriptError("A font size is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWALIGNMENT:
	    {
		ObjPtr object, value;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			int alignment;
			SHIFTNUM(arg, args);
			if (1 == sscanf(arg, "%d", &alignment))
			{
			    SetAlignment(object, alignment);
			}
			else
			{
			    ScriptError("An integer text alignment is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWLOCATION:
	    {
		ObjPtr object, value;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			real location[3];
			if (3 == sscanf(args, " [%g %g %g]", 
				&(location[0]),
				&(location[1]),
				&(location[2])))
			{
			    ObjPtr var;
			    var = NewRealArray(1, 3L);
			    CArray2Array(var, location);
			    SetVar(object, LOCATION, var);
			    ImInvalid(object);
			    if (logging)
			    {
				char cmd[300];
				char *s;
				sprintf(cmd, "set location ");

				s = &(cmd[0]);
				while (*s) ++s;
				MakeObjectName(s, object);
				while (*s) ++s;
				sprintf(s, " [%g %g %g]\n",
					location[0],
					location[1],
					location[2]);
				Log(cmd);
			    }
			}
			else
			{
			    ScriptError("A vector location is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWBOUNDS:
	    {
		ObjPtr object;
		ObjPtr bounds;
		char *oldArgs;
		object = ERROBJ;
		oldArgs = args;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			bounds = ERROBJ;
			args = ParseObjectArg(args, &bounds);
			if (bounds != ERROBJ)
			{
			    if (IsRealArray(bounds) && RANK(bounds) == 1)
			    {
				ObjPtr testBounds;
				testBounds = GetVar(object, BOUNDS);
				if (!testBounds ||
				    (IsRealArray(testBounds) && 
				     RANK(testBounds) == 1 &&
				     DIMS(testBounds)[0] == DIMS(bounds)[0]))
				{
				    if (logging)
				    {
					char cmd[256];
					sprintf(cmd, "set bounds %s\n", oldArgs);
					Log(cmd);
				    }
			    	    SetVar(object, BOUNDS, bounds);
				    ImInvalid(object);
				}
				else
				{
				    ScriptError("These bounds are of wrong size for the object");
				}
			    }
			    else
			    {
				ScriptError("A vector is expected here");
			    }
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWENDPOINTS:
	    {
		ObjPtr object;
		int x1, y1, x2, y2;
		char *oldArgs;
		object = ERROBJ;
		oldArgs = args;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			if (4 == sscanf(args, " %d %d %d %d", &x1, &y1, &x2, &y2))
			{
			    SetEndpoints(object, x1, y1, x2, y2);
			    ImInvalid(object);
			}
			else
			{
			    ScriptError("Four endpoint coordinates are expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWSCREEN:
	    {
		/*Set the size of the recording screen*/
		int x, y;
		if (2 != sscanf(args, " %d %d", &x, &y))
		{
		    ScriptError("A width and height of the recording screen is expected here");
		    break;
		}
		recScrWidth = x;
		recScrHeight = y;
	    }
	    break;
	case KWFPS:
	    {
		/*Set the frames per second*/
		real fps;
		if (1 != sscanf(args, " %g", &fps))
		{
		    ScriptError("A number of frames per second is expected here");
		    break;
		}
		SetFPS(fps);
	    }
	    break;
	default:
	    ScriptError("This is not a paramater that can be set");
	    break;
    }
    return true;
}


Bool DoSaveCommand(kwn, args)
int kwn;
char *args;
/*Does a save command*/
{
    char arg[256];

    switch(kwn)
    {
	case KWCONTROLS:
	    DoSaveObject();
	    break;
	default:
	    ScriptError("This is not something that can be saved");
	    return;
	    break;
    }
    return true;
}

Bool DoShowHideCommand(showp, kwn, args)
Bool showp;
int kwn;
char *args;
/*Does a show or hide command, showp is true iff it's show*/
{
    char arg[256];

    switch(kwn)
    {
	case KWPANEL:
	    if (showp)
	    {
		DoShowPanel();
	    }
	    else
	    {
		DoHidePanel();
	    }
	    break;
	case KWPREFERENCES:
	    if (showp)
	    {
		DoShowPreferences();
	    }
	    else
	    {
		ScriptError("You can't hide preferences this way.  Close the window instead");
	    }
	    break;
	case KWHELP:
	    if (showp)
	    {
		DoShowHelp();
	    }
	    else
	    {
		ScriptError("You can't hide help this way.  Close the window instead");
	    }
	    break;
	case KWDATASETS:
	    if (showp)
	    {
		PopDatasetsWindow();
	    }
	    else
	    {
		ScriptError("You can't hide the datasets window this way.  Close the window instead");
	    }
	    break;
	case KWFILEREADERS:
	    if (showp)
	    {
		PopFileReadersWindow();
	    }
	    else
	    {
		ScriptError("You can't hide the file readers window this way.  Close the window instead");
	    }
	    break;
	case KWFILESWINDOW:
	    if (showp)
	    {
		DoNewFileWindow();
	    }
	    else
	    {
		ScriptError("You can't hide the file window this way.  Close the window instead");
	    }
	    break;
	case KWFRAME:
	    if (showp)
	    {
		DoShowFrame();
	    }
	    else
	    {
		DoHideFrame();
	    }
	    break;
	case KWFRONT:
	    if (showp)
	    {
		DoShowFrontPanelControls();
	    }
	    else
	    {
		ScriptError("You can't hide the front panel controls this way.  Close the window instead");
	    }
	    break;
	case KWBACK:
	    if (showp)
	    {
		DoShowBackPanelControls();
	    }
	    else
	    {
		ScriptError("You can't hide the back panel controls this way.  Close the window instead");
	    }
	    break;
	case KWSPACE:
	    if (showp)
	    {
		DoShowSpaceControls();
	    }
	    else
	    {
		ScriptError("You can't hide the space controls this way.  Close the window instead");
	    }
	    break;

	default:
	    ScriptError("This keyword is not a settable parameter");
	    break;
    }
    return true;
}

#ifdef PROTO
void ShowScriptControlPanels(Bool whether)
#else
void ShowScriptControlPanels(whether)
Bool whether;
#endif
/*Turns subsequent showing of control panels within a script on or off*/
{
    showControlPanels = whether;
    Log(whether ? "turn show controls on\n" : "turn show controls off");
}

void VersionCommand(args)
char *args;
/*Does a version command*/
{
    char arg[256];
    float scianVersion, scriptVersion;
    SHIFTNUM(arg, args);

    if (1 != sscanf(arg, "%g", &scriptVersion))
    {
	ScriptError("A number is expected here");
	return;
    }
		
    if (1 != sscanf(SCIANVERSION, "Version %g", &scianVersion))
    {
	ReportError("VersionCommand", "Internal error: cannot parse SciAn version");
	return;
    }

    if (scianVersion < scriptVersion)
    {
	ScriptError("The script is newer than this version of SciAn");
	if (abortScriptP)
	{
	    abortScript = true;
	}
    }
    if (scianVersion > scriptVersion)
    {
#if 0
	ScriptError("The script is older than this version of SciAn");
	if (abortScriptP)
	{
	    abortScript = true;
	}
#endif
    }
}

#ifdef PROTO
void SelectCommand(char *args, Bool selectP)
#else
void SelectCommand(args, selectP)
char *args;
Bool selectP;
#endif
/*Does select or deselect depending on selectP*/
{
	ObjPtr object;
	object = ERROBJ;
	args = ParseObjectArg(args, &object);
 	if (object != ERROBJ)
	{
	    if (IsObject(object))
	    {
		Select(object, selectP);
	    }
	    else
	    {
		ScriptError("An object name is expected here");
	    }
	}
}

#ifdef PROTO
void TurnCommand(char *args)
#else
void TurnCommand(args)
char *args;
#endif
/*Does turn show controls on/off.  Normal turn on/off done by obj
  functions.*/
    {
	char arg[256];
	int kwn;
	SHIFTKW(arg, args);
	kwn = ParseKeyword(arg, keywords, NKEYWORDS);
	if (kwn == KWSHOW)
	{
	    SHIFTKW(arg, args);
	    kwn = ParseKeyword(arg, keywords, NKEYWORDS);
	    if (kwn == KWCONTROLS)
	    {
		SHIFTKW(arg, args);
		kwn = ParseKeyword(arg, keywords, NKEYWORDS);
		
		if (kwn == KWON)
		{
		    ShowScriptControlPanels(true);
		}
		else if (kwn == KWOFF)
		{
		    ShowScriptControlPanels(false);
		}
	    }
	    else
	    {
		ScriptError("'Controls' is expected here");
	    }
	}
	else
	{
	    ScriptError("Either 'on' or 'off' is expected here");
	}
    }


Bool DoScriptCommand(kwn, args)
int kwn;
char *args;
/*Does command kwn with args*/
{
    char arg[256];

    switch(kwn)
    {
	case KWVERSION:
	    VersionCommand(args);
	    break;
	case KWTIME:
	    break;
	case KWDELETE:
	    DoDelete();
	    break;
	case KWTURN:
	    /*Turn show controls on/off.  Turn on/off handled by objfunctions*/
	    TurnCommand(args);
	    break;
	case KWSELECT:
	    SelectCommand(args, true);
	    break;
	case KWDESELECT:
	    SelectCommand(args, false);
	    break;
	case KWSELECTALL:
	    DoSelectAllIcons();
	    break;
	case KWDESELECTALL:
	    DeselectAll();
	    break;
	case KWANNOTATION:
	case KWTIMEREADOUT:
	case KWRECTANGLE:
	    {
		char name[256];
		ObjPtr bounds;
		SHIFTKW(name, args);
		if (strlen(name))
		{
		    bounds = ERROBJ;
		    args = ParseObjectArg(args, &bounds);
		    if (bounds != ERROBJ)
		    {
			if (IsRealArray(bounds) && RANK(bounds) == 1 &&
				DIMS(bounds)[0] == 4)
			{
			    switch(kwn)
			    {
				case KWANNOTATION:
				    AddAnnotation(name, ArrayMeat(bounds));
				    break;
				case KWTIMEREADOUT:
				    AddTimeReadout(name, ArrayMeat(bounds));
				    break;
				case KWRECTANGLE:
				    AddRectangle(name, ArrayMeat(bounds));
				    break;
			    }
			}
			else
			{
			    ScriptError("A 4-vector is expected here");
			}
		    }
		}
		else
		{
		    ScriptError("A name is expected here");
		}
	    }
	    break;
	case KWLINE:
	    {
		char name[256];
		int x1, y1, x2, y2;
		SHIFTKW(name, args);
		if (strlen(name))
		{
		    if (4 == sscanf(args, " %d %d %d %d", &x1, &y1, &x2, &y2))
		    {
			AddLine(name, x1, y1, x2, y2);
		    }
		    else
		    {
			ScriptError("Four endpoint coordinates are expected here");
		    }
		}
		else
		{
		    ScriptError("A name is expected here");
		}
	    }
	    break;
	case KWSET:			/*Set something or other*/
	    {
		int k;
		SHIFTKW(arg, args);
		/*Find out what to set*/
		kwn = ParseKeyword(arg, keywords, NKEYWORDS);
		if (kwn == -1)
		{
		    ScriptError("This parameter is unknown.");
		    break;
		}
		else if (kwn == -2)
		{
		    ScriptError("This parameter is ambiguous.");
		    break;
		}
		else
		{
		    return DoSetCommand(kwn, args);
		}
	    }
	    break;
	case KWSAVE:			/*Save something or other*/
	    {
		int k;
		SHIFTKW(arg, args);
		/*Find out what to set*/
		kwn = ParseKeyword(arg, keywords, NKEYWORDS);
		if (kwn == -1)
		{
		    ScriptError("This parameter is unknown.");
		    break;
		}
		else if (kwn == -2)
		{
		    ScriptError("This parameter is ambiguous.");
		    break;
		}
		else
		{
		    return DoSaveCommand(kwn, args);
		}
	    }
	    break;
	case KWSHOW:			/*Show something or other*/
	    {
		int k;
		SHIFTKW(arg, args);
		/*Find out what to set*/
		kwn = ParseKeyword(arg, keywords, NKEYWORDS);
		if (kwn == -1)
		{
		    ScriptError("This parameter is unknown.");
		    break;
		}
		else if (kwn == -2)
		{
		    ScriptError("This parameter is ambiguous.");
		    break;
		}
		else
		{
		    return DoShowHideCommand(true, kwn, args);
		}
	    }
	    break;
	case KWHIDE:			/*Hide something or other*/
	    {
		int k;
		SHIFTKW(arg, args);
		/*Find out what to set*/
		kwn = ParseKeyword(arg, keywords, NKEYWORDS);
		if (kwn == -1)
		{
		    ScriptError("This parameter is unknown.");
		    break;
		}
		else if (kwn == -2)
		{
		    ScriptError("This parameter is ambiguous.");
		    break;
		}
		else
		{
		    return DoShowHideCommand(false, kwn, args);
		}
	    }
	    break;
	case KWDROP:
	    {
		int dropX, dropY;
		
		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%d", &dropX))
		{
		    ScriptError("A number is expected here");
		    break;
		}

		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%d", &dropY))
		{
		    ScriptError("A number is expected here");
		    break;
		}

		if (scriptWindow && dragBuffer)
		{
		    FuncTyp method;
		    /*Drop*/
		    method = GetMethod((ObjPtr) scriptWindow, DROPOBJECTS);
		    if (method)
		    {
			iconXOff = 0;
			iconYOff = 0;
			if (logging)
			{
			    char cmd[256];
			    sprintf(cmd, "drop %d %d\n", dropX, dropY);
			    Log(cmd);
			    InhibitLogging(true);
			}
			(*method)(scriptWindow, dragBuffer, dropX, dropY);
			DeleteThing(dragBuffer);
			dragBuffer = NULLOBJ;
			if (logging)
			{
			    InhibitLogging(false);
			}
		    }
		}
		else
		{
		    ScriptError("There is no selected window");
		}
	    }
	    break;
	case KWVIDEOSCREEN:
	    DoVideoScreen();
	    break;
	case KWFULLSCREEN:
	    DoMaxScreen();
	    break;
	case KWTILE:
	    {
		int width, height;

		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%d", &width))
		{
		    ScriptError("A width is expected here");
		    break;
		}

		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%d", &height))
		{
		    ScriptError("A height is expected here");
		    break;
		}

		Tile(width, height);
	    }
	    break;
	case KWLOCATE:
	    {
		int l, r, b, t;
		if (4 != sscanf(args, " %d %d %d %d", &l, &r, &b, &t))
		{
		    ScriptError("The bounds of the window are expected here.");
		    return true;
		}
		LocateWindow(l, r, b, t);
	    }
	    break;
	case KWEYEPOSN:
	    {
		if (scriptWindow)
		{
		    ObjPtr array, observer /*, observers*/;
		    array = ERROBJ;
    
		    args = ParseObjectArg(args, &array);
		    if (array != ERROBJ)
		    {
			observer = FindObserver(scriptWindow);
			if (!observer) break;

			if (IsArray(array) && RANK(array) == 1 && DIMS(array)[0] == 3)
			{
			    real *p;
			    SetVar(observer, LOCATION, array);
			    ImInvalid(observer);
			    p = ELEMENTS(array);
			    if (logging)
			    {
				char cmd[256];
				sprintf(cmd, "eyeposn [%g %g %g]\n",
				    p[0], p[1], p[2]);
				Log(cmd);
			    }
			}
			else
			{
			    ScriptError("A 3-vector is expected here");
			}
		    }
 		}
	    }
	    break;
	case KWROLL:
	    {
		float value;
		SHIFTKW(arg, args);
		if (strlen(arg) && 1 == sscanf(arg, " %g", &value))
		{
		    if (scriptWindow)
		    {
			ObjPtr observer /*, observers*/;
			observer = FindObserver(scriptWindow);
			if (!observer) break;

			SetVar(observer, ROLL, NewReal(value));
		    }
		}
		else
		{
		    ScriptError("An amount to roll is expected here.");
		}
	    }
	    break;
	case KWPITCH:
	    {
		float value;
		SHIFTKW(arg, args);
		if (strlen(arg) && 1 == sscanf(arg, " %g", &value))
		{
		    if (scriptWindow)
		    {
			ObjPtr observer/*, observers*/;
			observer = FindObserver(scriptWindow);
			if (!observer) break;

			SetVar(observer, PITCH, NewReal(value));
		    }
		}
		else
		{
		    ScriptError("An amount to pitch is expected here.");
		}
	    }
	    break;
	case KWYAW:
	    {
		ScriptError("YAW is no longer supported.");
	    }
	    break;
	case KWROTATE:
	    {
		char axis;
		float amount;

		SHIFTKW(arg, args);
		if (strlen(arg) == 1 && (*arg == 'x' || *arg == 'y' || *arg == 'z'))
		{
		    axis = *arg;	

		    SHIFTNUM(arg, args);
		    if (!strlen(arg) || 1 != sscanf(arg, "%f", &amount))
		    {
			ScriptError("An amount to rotate is expected here.");
			return true;
		    }

		    if (scriptWindow)
		    {
			RotateOrthoWindow(scriptWindow, axis, amount);
		    }
		}
		else
		{
		    ScriptError("Unrecognized keyword.  An axis (x, y, or z) is expected.");
		}
		return true;
	    }
	    break;
	case KWSHEAR:
	    ScriptError("Shearing is no longer supported.");
	    return true;
	    break;
	case KWRECORD:
	    if (FindCommandInStack(KWRECORDING) < 0)
	    {
		ScriptError("This command can only exist within a BEGIN RECORDING block.");
		return true;
	    }
	    else
	    {
		real nSecs;
		if (1 != sscanf(args, " %g", &nSecs))
		{
		    
		    ScriptError("A number of seconds to record is expected here.");
		}
		else
		{
		    runningTask = RT_RECORDING;
		    framesLeft = nSecs * 30 + 0.5;
		}
		return false;
	    }
	case KWBEGIN:
	    return BeginCommand(args);
	case KWEND:
	    return EndCommand(args);
	case KWSNAP:
	    if (FindCommandInStack(KWRECORDING) < 0)
	    {
		ScriptError("This command can only exist within a BEGIN RECORDING block.");
		return true;
	    }
	    if (!SnapOneFrame())
	    {
		ScriptError("Could not snap a single frame.");
	    }
	    ++TrashDayFlag;
	    break;
	case KWWINDOW:
	    SHIFTKW(arg, args);
	    if (!strlen(arg))
	    {
		ScriptError("The name of a window is expected here");
		return true;
	    }
	    SelectNamedWindow(arg);
	    break;
	case KWEXIT:
	case KWQUIT:
	    DoQuit();
	    break;
	case KWCLOSE:
	    CloseWindow(scriptWindow);
	    SelWindow(0);
	    scriptWindow = 0;
	    break;
	case KWPUSHWINDOW:
	    if (scriptWindow)
	    {
		PushWindow(scriptWindow);
	    }
	    else
	    {
		ScriptError("There is no current window");
	    }
	    break;
	case KWSHELL:			/*Shell escape*/
	    SKIPBLANKS(args);
	    if ((getuid() == geteuid()) &&
		(getgid() == getegid()))
	    {
	        system(args);
	    }
	    break;
	case KWSCRSAVE:
	    {
		char fileName[256];
		int l, r, b, t;
		int n;
		n = sscanf(args, " %s %d %d %d %d", fileName, &l, &r, &b, &t);
		if (n == 5)
		{
		    sprintf(tempStr, "scrsave %s %d %d %d %d", fileName, l, r, b, t);
		    if ((getuid() == geteuid()) &&
			(getgid() == getegid()))
		    {
		        system(tempStr);
		    }
		}
		else if (n == 1)
		{
		    sprintf(tempStr, "scrsave %s", fileName);
		    if ((getuid() == geteuid()) &&
			(getgid() == getegid()))
		    {
		        system(tempStr);
		    }
		}
		else
		{
		    ScriptError("A filename and four sides are expected here");
		    return true;
		}
	    }
	    break;
	case KWEFFECT:
	    {
		/*Color effect*/
		int whichFunc;
		SHIFTKW(arg, args);
		whichFunc = ParseKeyword(arg, spfNames, NPALETTEFUNCS);
		if (whichFunc >= 0)
		{
#ifdef MENUSFROM0
		    SimpleFuncFromMenu(whichFunc);
#else
		    SimpleFuncFromMenu(whichFunc + 1);
#endif
		}
		else
		{
		    ScriptError("This special effect is not defined");
		}
	    }
	    break;
	case KWMODEL:
	    {
		/*Color model*/
		int whichFunc;
		SHIFTKW(arg, args);
		whichFunc = ParseKeyword(arg, colorModelNames, NCOLORMODELS);
		if (whichFunc >= 0)
		{
#ifdef MENUSFROM0
		    ColorModelFromMenu(whichFunc);
#else
		    ColorModelFromMenu(whichFunc + 1);
#endif
		}
		else
		{
		    ScriptError("This color model is not defined");
		}
	    }
	    break;
	case KWKEEP:
	    DoKeepPalette();
	    break;
	case KWREVERT:
	    DoRevertPalette();
	    break;
	default:
	    ScriptError("This keyword is not a command");
	    break;
    }
    return true;
}

#ifdef PROTO
void BeginScript(char *name)
#else
void BeginScript(name)
char *name;
#endif
/*Begins a new script opened from file name*/
{
    curScript = fopen(name, "r");
    if (curScript)
    {
	runningScript = true;
	abortScript = false;
    }
    else
    {
	fprintf(stderr, "BeginScript: Cannot open script file %s.\n", name);
    }
}

void EndScript()
/*Ends a script*/
{
    if (curScript)
    {
	while (nextStackElement)
	{
	    EndCommand("");
	}

	/*Make control panels shown*/
	ShowScriptControlPanels(true);
	fclose(curScript);
	curScript = 0;
	runningScript = false;
	MySetCursor(0);
    }
}

Bool ReadScriptLine()
/*Reads and does a script line*/
{
    if (curScript)
    {
	int k, c;

	if (!lineAccepted)
	{
	    lineAccepted = InterpretScriptLine(scriptLine);
	    return true;
	}

	if (abortScript)
	{
	    fprintf(stderr, "Aborting script.\n");
	    EndScript();
	    return false;
	}

	/*Read a script line*/
	k = 0;

	while((c = fgetc(curScript)) != EOF && c != '\n')
	{
	    if (k >= nScriptCharsAllocated)
	    {
		if (nScriptCharsAllocated)
		{
		    nScriptCharsAllocated += SCRIPTALLOCBATCH;
		    scriptLine = realloc(scriptLine, nScriptCharsAllocated + 1);
		}
		else
		{
		    nScriptCharsAllocated = SCRIPTALLOCBATCH;
		    scriptLine = malloc(nScriptCharsAllocated + 1);
		}
	    }
	    scriptLine[k] = c;
	    ++k;
	}
	if (k == 0 && c == EOF)
	{
	    EndScript();
	    return false;
	}
	else if (k != 0)
	{
	    scriptLine[k] = 0;
	    lineAccepted = InterpretScriptLine(scriptLine);
	    return true;
	}
    }
    return true;
}

static Bool ScriptTask()
/*Run the current script task*/
{
    switch(runningTask)
    {
	case RT_RECORDING:
	    if (framesLeft <= 0)
	    {
		runningTask = 0;
		return true;
	    }
	    if (!SnapOneFrame())
	    {
		ScriptError("Could not snap a single frame.");
	    }
	    ++TrashDayFlag;
	    --framesLeft;
	    break;
    }
    return false;
}

Bool InterpretScriptLine(s)
char *s;
/*Interprets a script line in character string s.  Returns true iff the script
  line needs to be thrown away*/
{
    char curKeyword[256];
    int kwn;
    Bool retVal;

    if (runningTask != RT_NONE)
    {
	/*There's a running task*/
	return ScriptTask();
    }

    /*Set up whole line for script stuff*/
    wholeLine = s;

    /*Look at first keyword*/
    SKIPBLANKS(s);
    if (*s == '#')
    {
	if (logging)
	{
	    Log(wholeLine);
	}
	return true;
    }

    if (scriptWindow && IsValidWindow(scriptWindow))
    {
	SelWindow(scriptWindow);
    }
    else
    {
	SelWindow((WinInfoPtr) 0);
    }

    if (ObjFunctionScriptLine(s))
    {
	retVal = true;
    }
    else
    {
	SHIFTKW(curKeyword, s);

	if (!strlen(curKeyword))
	{
	    retVal = true;
	}
	else
	{
	
	    /*Find out what it is*/
	    kwn = ParseKeyword(curKeyword, keywords, NKEYWORDS);
	    if (kwn == -1)
	    {
		ScriptError("This command is unknown.");
		retVal = true;
	    }
	    else if (kwn == -2)
	    {
		ScriptError("This command is ambiguous.");
		retVal = true;
	    }
	    else
	    {
		retVal = DoScriptCommand(kwn, s);
	    }
	}
    }
    return retVal;
}
