/*ScianDatasets.c
  Eric Pepke
  August 17, 1990
  Stuff for Datasets
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianLists.h"
#include "ScianIcons.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianVisWindows.h"
#include "ScianButtons.h"
#include "ScianTextBoxes.h"
#include "ScianControls.h"
#include "ScianDatasets.h"
#include "ScianErrors.h"
#include "ScianColors.h"
#include "ScianDialogs.h"
#include "ScianVisObjects.h"
#include "ScianStyle.h"
#include "ScianSpaces.h"
#include "ScianIDs.h"
#include "ScianArrays.h"
#include "ScianMethods.h"
#include "ScianTimers.h"
#include "ScianDepend.h"
#include "ScianFiles.h"
#include "ScianFileSystem.h"
#include "ScianObjFunctions.h"
#include "ScianSockets.h"
#include "ScianEvents.h"
#include "ScianSciences.h"
#include "ScianGeometry.h"

#define VISMENU
#define SHOWCTLMENU

ObjPtr geometryClass;			/*Class for geometry object*/
ObjPtr datasetClass, data3DScalar, data2DScalar, data1DVector, data3DUnstructSurface;
ObjPtr iconDataset;
ObjPtr icon1DVector, icon2DVector, icon3DVector, icon4DVector;
ObjPtr icon1DScalar, icon2DScalar, icon3DScalar, icon4DScalar;
ObjPtr iconGeometry;

Bool timedDatasets = true;		/*True iff reading timed datasets*/
ObjPtr dataFormClass;
ObjPtr allDatasets = NULLOBJ;		/*All datasets*/
static ObjPtr dsLocArray = NULLOBJ;	/*Location of icon for dataset*/

Bool onePalette = false;
ObjPtr commonPalette;

DatasetBuffer curFields[MAXNCURFIELDS];

#ifdef PROTO
ObjPtr NewGeometryDataset(char *name)
#else
ObjPtr NewGeometryDataset(name)
char *name;
#endif
{
    ObjPtr retVal;
    retVal = NewObject(datasetClass, 0L);
    SetVar(retVal, DEFAULTICON, iconGeometry);
    SetVar(retVal, NAME, NewString(name));

    return retVal;
}

#ifdef PROTO
ObjPtr NewDataset(char *name, int rank, long *dims, int nComponents)
#else
ObjPtr NewDataset(name, rank, dims, nComponents)
char *name;
int rank;
long *dims;
int nComponents;
#endif
/*Makes a new structured dataset
  name		is the name of the dataset
  rank		is the rank of the dataset
  dims		is a pointer to the dimensions
  nComponents	is the number of components, 0 for scalar
*/
{
    ObjPtr retVal;
    ObjPtr dimensions;
    real *elements;
    ObjPtr data;
    int k;

    /*Make the dataset*/
    retVal = NewObject(datasetClass, 0L);

    /*Make its dimensions*/
    if (rank)
    {
	dimensions = NewRealArray(1, (long) rank);
	elements = ELEMENTS(dimensions);
	for (k = 0; k < rank; ++k)
	{
	    elements[k] = (real) dims[k];
	}
	SetVar(retVal, DIMENSIONS, dimensions);
    }

    /*Set its NCOMPONENTS*/
    if (nComponents)
    {
	SetVar(retVal, NCOMPONENTS, NewInt(nComponents));
    }

    /*Set its NAME*/
    SetVar(retVal, NAME, NewString(name));

    /*Set its DATA*/
    if (nComponents)
    {
	ObjPtr *objectElements;
	long dummy;

	/*Vector data*/
	dummy = nComponents;
	data = NewArray(AT_OBJECT, 1, &dummy);

	objectElements = (ObjPtr *) ELEMENTS(data);

	for (k = 0; k < nComponents; ++k)
	{
	    objectElements[k] = NewArray(AT_REAL, rank, dims);
	}
    }
    else
    {
	/*Scalar data*/
	data = NewArray(AT_REAL, rank, dims);
    }
    SetVar(retVal, DATA, data);

    /*Set its DEFAULTICON*/
    if (nComponents)
    {
	/*Vector*/
	switch (rank)
	{
	    case 0:	/*Really should have a value, here*/
	    case 1:     SetVar(retVal, DEFAULTICON, icon1DVector); break;
	    case 2:     SetVar(retVal, DEFAULTICON, icon2DVector); break;
	    case 3:     SetVar(retVal, DEFAULTICON, icon3DVector); break;
	    default:    SetVar(retVal, DEFAULTICON, icon4DVector); break;
	}
    }
    else
    {
	/*Scalar*/
	switch (rank)
	{
	    case 0:	/*Really should have a value, here*/
	    case 1:     SetVar(retVal, DEFAULTICON, icon1DScalar); break;
	    case 2:     SetVar(retVal, DEFAULTICON, icon2DScalar); break;
	    case 3:     SetVar(retVal, DEFAULTICON, icon3DScalar); break;
	    default:    SetVar(retVal, DEFAULTICON, icon4DScalar); break;
	}
    }

    return retVal;
}

#ifdef PROTO
ObjPtr NewSeparableDataForm(char *name, int rank, long *dims, real *scales[])
#else
ObjPtr NewSeparableDataForm(name, rank, dims, scales)
char *name;
int rank;
long *dims;
real *scales[];
#endif
/*Makes a new rectilinear data form with separable axes
  name		is the name of the dataset
  rank		is the rank of the dataset
  dims		is a pointer to the dimensions
  scales	is an array, rank rank, containing elements of size
		dims[k] giving scale along that axis
*/
{
    ObjPtr retVal;
    ObjPtr dimensions;
    ObjPtr bounds;
    real *elements;
    ObjPtr *objElements;
    ObjPtr data, axes;
    int k;
    long temp;

    /*Create the data form*/
    retVal = NewObject(dataFormClass, 0L);

    /*Make its dimensions*/
    dimensions = NewRealArray(1, (long) rank);
    elements = ELEMENTS(dimensions);
    for (k = 0; k < rank; ++k)
    {
	elements[k] = (real) dims[k];
    }
    SetVar(retVal, DIMENSIONS, dimensions);

    /*Set its NAME*/
    SetVar(retVal, NAME, NewString(name));

    /*Make its BOUNDS*/
    bounds = NewRealArray(1, 2L * (long) rank);
    if (!bounds)
    {
	return NULLOBJ;
    }
    elements = ELEMENTS(bounds);
    for (k = 0; k < rank; ++k)
    {
	int i;
	real min, max;

	min = max = scales[k][0];
	for (i = 1; i < dims[k]; ++i)
	{
	    if (scales[k][i] < min) min = scales[k][i];
	    if (scales[k][i] > max) max = scales[k][i];
	}
	elements[k * 2] = min;
	elements[k * 2 + 1] = max;
    }
    SetVar(retVal, BOUNDS, bounds);

    /*Now make a dataset which specifies the field*/
    data = NewObject(datasetClass, 0);
    if (!data)
    {
	return NULLOBJ;
    }

    /*Set its NAME*/
    SetVar(data, NAME, NewString(name));

    /*Set its NCOMPONENTS*/
    SetVar(data, NCOMPONENTS, NewInt(rank));

    /*Make an array of axes for the data*/
    temp = rank;
    axes = NewArray(AT_OBJECT, 1, &temp);
    if (!axes)
    {
	return NULLOBJ;
    }
    SetVar(data, DATA, axes);

    /*Fill the axes*/
    objElements = ELEMENTS(axes);
    for (k = 0; k < rank; ++k)
    {
	ObjPtr indices, scaleArray;
	long i;

	/*Copy over the scale*/
	scaleArray = NewRealArray(1, dims[k]);
	elements = ELEMENTS(scaleArray);
	objElements[k] = scaleArray;

	for (i = 0; i < dims[k]; ++i)
	{
	    elements[i] = scales[k][i];
	}
	
	/*Give it an INDICES array*/
	indices = NewRealArray(1, 1L);
	*(real *)ELEMENTS(indices) = k;
	SetVar(objElements[k], INDICES, indices);
    }
    SetVar(retVal, DATA, data);

    return retVal;
}

#ifdef PROTO
ObjPtr NewUnstructuredDataForm(char *name, int rank, long *dims, real *bounds, ObjPtr dataset)
#else
ObjPtr NewUnstructuredDataForm(name, rank, dims, bounds, dataset)
char *name;
int rank;
long *dims;
real *bounds;
ObjPtr dataset;
#endif
/*Makes a new unstructured data form
  name		is the name of the data form
  rank		is the rank of the dataset
		0 = just nodes
		1 = + links
		2 = + faces
		3 = + cells, etc.
  dims		is a pointer to the dimensions
		This must be one greater than rank
		0 = number of nodes, 1 = number of links, etc.
  bounds	is the bounds of the dataform, rank # components of dataform
  dataset	is the dataset that is used for the data form
*/
{
    ObjPtr retVal;
    ObjPtr dimensions;
    ObjPtr boundsArray;
    real *elements;
    ObjPtr *objElements;
    ObjPtr data, axes;
    ObjPtr var;
    int nComponents;
    int k;
    long temp;

    /*Create the data form*/
    retVal = NewObject(dataFormClass, 0L);
    SetVar(retVal, UNSTRUCTURED, ObjTrue);

    /*Make its dimensions*/
    dimensions = NewRealArray(1, (long) rank + 1);
    elements = ELEMENTS(dimensions);
    for (k = 0; k < rank + 1; ++k)
    {
	elements[k] = (real) dims[k];
    }
    SetVar(retVal, DIMENSIONS, dimensions);

    /*Set its NAME*/
    SetVar(retVal, NAME, NewString(name));

    /*Calculate its BOUNDS*/
    var = GetIntVar("NewUnstructuredDataForm", dataset, NCOMPONENTS);
    if (var)
    {
	nComponents = GetInt(var);
    }
    else
    {
	return NULLOBJ;
    }
    SetVar(retVal, NCOMPONENTS, NewInt(nComponents));
    boundsArray = NewRealArray(1, 2L * (long) nComponents);
    if (!boundsArray)
    {
	return NULLOBJ;
    }
    elements = ELEMENTS(boundsArray);
    for (k = 0; k < nComponents; ++k)
    {
	elements[k * 2] = bounds[k * 2];
	elements[k * 2 + 1] = bounds[k * 2 + 1];
    }
    SetVar(retVal, BOUNDS, boundsArray);

    SetVar(retVal, DATA, dataset);

    return retVal;
}

#ifdef PROTO
void AppendPolygonToDataset(ObjPtr dataset, long nVertices, long vertices[])
#else
void AppendPolygonToDataset(dataset, nVertices, vertices)
ObjPtr dataForm;
long nVertices;
long vertices;
#endif
/*Appends an indexed polygon to a data set*/
{
    ObjPtr geometry;

    geometry = GetVar(dataset, DATA);
    if (!geometry)
    {
	geometry = NewGeometry();
	SetVar(dataset, DATA, geometry);
    }
    AppendPolygonToGeometry(geometry, nVertices, vertices);
}

#ifdef PROTO
ObjPtr NewRegularDataForm(char *name, int rank, long *dims, real *bounds)
#else
ObjPtr NewRegularDataForm(name, rank, dims, bounds)
char *name;
int rank;
long *dims;
real *bounds;
#endif
/*Makes a new regulat data form
  name		is the name of the dataset
  rank		is the rank of the dataset
  dims		is a pointer to the dimensions
  bounds	is the bounds of the dataform, rank 1, size 2 * rank
*/
{
    ObjPtr retVal;
    ObjPtr dimensions;
    ObjPtr boundsArray;
    real *elements;
    ObjPtr *objElements;
    ObjPtr data, axes;
    int k;
    long temp;

    /*Create the data form*/
    retVal = NewObject(dataFormClass, 0L);

    /*Make its dimensions*/
    dimensions = NewRealArray(1, (long) rank);
    elements = ELEMENTS(dimensions);
    for (k = 0; k < rank; ++k)
    {
	elements[k] = (real) dims[k];
    }
    SetVar(retVal, DIMENSIONS, dimensions);

    /*Set its NAME*/
    SetVar(retVal, NAME, NewString(name));

    /*Make its BOUNDS*/
    boundsArray = NewRealArray(1, 2L * (long) rank);
    if (!boundsArray)
    {
	return NULLOBJ;
    }
    elements = ELEMENTS(boundsArray);
    for (k = 0; k < rank; ++k)
    {
	elements[k * 2] = bounds[k * 2];
	elements[k * 2 + 1] = bounds[k * 2 + 1];
    }
    SetVar(retVal, BOUNDS, boundsArray);

    return retVal;
}

#ifdef PROTO
ObjPtr NewCurvilinearDataForm(char *name, int rank, long *dims, real *bounds, ObjPtr dataset)
#else
ObjPtr NewCurvilinearDataForm(name, rank, dims, bounds, dataset)
char *name;
int rank;
long *dims;
real *bounds;
ObjPtr dataset;
#endif
/*Makes a new curvilinear data
  name		is the name of the data form
  rank		is the rank of the data form
  dims		is a pointer to the dimensions
  bounds	is the bounds of the dataform, rank # components of dataform
  dataset	is the dataset that is used for the data form
*/
{
    ObjPtr retVal;
    ObjPtr dimensions;
    ObjPtr boundsArray;
    real *elements;
    ObjPtr *objElements;
    ObjPtr data, axes;
    ObjPtr var;
    int nComponents;
    int k;
    long temp;

    /*Create the data form*/
    retVal = NewObject(dataFormClass, 0L);

    /*Make its dimensions*/
    dimensions = NewRealArray(1, (long) rank);
    elements = ELEMENTS(dimensions);
    for (k = 0; k < rank; ++k)
    {
	elements[k] = (real) dims[k];
    }
    SetVar(retVal, DIMENSIONS, dimensions);

    /*Set its NAME*/
    SetVar(retVal, NAME, NewString(name));

    /*Calculate its BOUNDS*/
    var = GetIntVar("NewCurvilinearDataForm", dataset, NCOMPONENTS);
    if (var)
    {
	nComponents = GetInt(var);
    }
    else
    {
	return NULLOBJ;
    }
    SetVar(retVal, NCOMPONENTS, NewInt(nComponents));
    boundsArray = NewRealArray(1, 2L * (long) nComponents);
    if (!boundsArray)
    {
	return NULLOBJ;
    }
    elements = ELEMENTS(boundsArray);
    for (k = 0; k < nComponents; ++k)
    {
	elements[k * 2] = bounds[k * 2];
	elements[k * 2 + 1] = bounds[k * 2 + 1];
    }
    SetVar(retVal, BOUNDS, boundsArray);

    SetVar(retVal, DATA, dataset);

    return retVal;
}

#ifdef PROTO
void SetDatasetPalette(ObjPtr dataset, ObjPtr palette)
#else
void SetDatasetPalette(dataset, palette)
ObjPtr dataset, palette;
#endif
/*Sets a dataset's palette*/
{
    FieldPaletteName(palette, dataset);
    SetVar(dataset, CPALETTE, palette);
}

#ifdef PROTO
void SetDatasetForm(ObjPtr dataset, ObjPtr dataForm)
#else
void SetDatasetForm(dataset, dataForm)
ObjPtr dataset, dataForm;
/*Sets a dataset's data form*/
#endif
{
    SetVar(dataset, DATAFORM, dataForm);
}

ObjPtr GetLongName(dataset)
ObjPtr dataset;
/*Gets the long name of dataset*/
{
    FuncTyp method;
    method = GetMethod(dataset, GETLONGNAME);
    if (method)
    {
	return (*method)(dataset);
    }
    else
    {
	return NULLOBJ;
    }
}

ObjPtr GetPlainDatasetLongName(dataset)
ObjPtr dataset;
/*Gets the long name of a plain dataset*/
{
    return GetVar(dataset, NAME);
}

#ifdef PROTO
static DeleteBuckets(int whichField)
#else
static DeleteBuckets(whichField)
int whichField;
#endif
/*Deletes the buckets from within a field*/
{
    if (curFields[whichField] . bucketMinMax)
    {
	/*It has buckets, get rid of them*/
	long k;
	long nBuckets;

	/*Figure out how many buckets there are*/
	nBuckets = 1;
	for (k = 0; k < curFields[whichField] . nBucketIndices; ++k)
	{
	    if (curFields[whichField] . bucketDim[k])
	    {
		nBuckets += curFields[whichField] . bucketDim[k];
	    }
	}

	/*Empty the buckets*/
	for (k = 0; k < nBuckets; ++k)
	{
	    if (curFields[whichField] . buckets[k] . indices)
	    {
		free(curFields[whichField] . buckets[k] . indices);
	    }
	    if (curFields[whichField] . buckets[k] . samples)
	    {
		free(curFields[whichField] . buckets[k] . samples);
	    }
	}
	free(curFields[whichField] . buckets);
	curFields[whichField] . buckets = 0;

	/*Get rid of the min and max*/
	free(curFields[whichField] . bucketMinMax);
	curFields[whichField] . bucketMinMax = 0;

	/*Get rid of the dimensions*/
	free(curFields[whichField] . bucketDim);
	curFields[whichField] . bucketDim = 0;
    }
}

static void CleanCurField(k)
int k;
/*Cleans the current field number k*/
{
    DeleteBuckets(k);
    if (curFields[k] . components)
    {
	int i;
	for (i = 0; i < curFields[k] . nComponents; ++i)
	{
	    if (curFields[k] . components[i] . indices)
	    {
		free(curFields[k] . components[i] . indices);
		curFields[k] . components[i] . indices = 0;
	    }
	    if (curFields[k] . components[i] . dimensions)
	    {
		free(curFields[k] . components[i] . dimensions);
		curFields[k] . components[i] . dimensions = 0;
	    }
	    if (curFields[k] . components[i] . steps)
	    {
		free(curFields[k] . components[i] . steps);
		curFields[k] . components[i] . steps = 0;
	    }
	}
	free(curFields[k] . components);
    }
    curFields[k] . min = 0.0;
    curFields[k] . max = 1.0;
    curFields[k] . nComponents = 0;
    curFields[k] . components = (Component *) 0;
}

ObjPtr MakeDatasetCurData(dataset)
ObjPtr dataset;
/*Makes the dataset's CURDATA.  Returns ObjTrue if it had to make it*/
{
    ObjPtr data = NULLOBJ, curData = NULLOBJ;
    ObjPtr retVal;

    retVal = ObjFalse;

    data = GetVar(dataset, DATA);
    if (data)
    {
	curData = GetVar(data, CURDATA);
	if (!curData)
	{
	    /*For old-fashioned objects that don't know about CURDATA*/
	    curData = data;
	}
    }
    else
    {
	curData = data;
    }
    if (!curData)
    {
	ReportError("MakeDatasetCurData", "CURDATA of DATA is missing!");
	retVal = ObjTrue;
	return retVal;
    }
    SetVar(dataset, CURDATA, curData);
    return ObjTrue;
}

ObjPtr MakeDatasetTimesteps(dataset)
ObjPtr dataset;
/*Makes the dataset's TIMESTEPS.  Returns ObjTrue if it had to make it*/
{
    ObjPtr data = NULLOBJ, timesteps = NULLOBJ;

    data = GetVar(dataset, DATA);
    if (data)
    {
	MakeVar(data, TIMESTEPS);
	timesteps = GetVar(data, TIMESTEPS);
    }
    else
    {
	SetVar(dataset, TIMESTEPS, NULLOBJ);
	return ObjTrue;
    }
    SetVar(dataset, TIMESTEPS, timesteps);
    return ObjTrue;
}

#if 0
#ifdef PROTO
real *GetComponentDataPtr(int whichField, int whichComponent)
#else
real *GetComponentDataPtr(whichField, whichComponent)
int whichField;
int whichComponent;
#endif
/*Returns a pointer to the data of component in whichField.
  This is VERY DANGEROUS and should only be called from file
  readers after having done New*Dataset and before the next time
  through the event loop!*/
{
    return curFields[whichField] . components[whichComponent] . data;
}
#endif

#ifdef PROTO
Bool SampleSpatComponent(int dataField, int formField,
	int nResultComp, real resultComp[], int nSpatIndices, 
	real spatIndices[], Bool interpolate)
#else
Bool SampleSpatComponent(dataField, formField, nResultComp, resultComp, nSpatIndices, spatIndices, interpolate)
int dataField, formField;
int nResultComp;
real resultComp[];
int nSpatIndices;
real spatIndices[];
Bool interpolate;
#endif
/*Samples a field dataField defined over formField using nSpatIndices spatIndices and puts
  the result in resultComp components*/
{
    long *topIndices;
    real *rTopIndices;
    long indexBuffer[10];
    real rIndexBuffer[10];
    int nTopIndices;
    int k;

    nTopIndices = curFields[formField] . topDim;
    if (nTopIndices != curFields[dataField] . topDim)
    {
	ReportError("SampleSpatComponent", "Topological dimension mismatch");
	return false;
    }

    if (nResultComp != curFields[dataField] . nComponents)
    {
	ReportError("SampleSpatComponent", "Wrong number of result components");
	return false;
    }

    if (nTopIndices <= 10)
    {
	topIndices = &(indexBuffer[0]);
	rTopIndices = &(rIndexBuffer[0]);
    }
    else
    {
	topIndices = malloc(nTopIndices * sizeof(long));
	rTopIndices = malloc(nTopIndices * sizeof(real));
    }
    SampleToTopIndex(formField, nTopIndices, rTopIndices, nSpatIndices, spatIndices);
    for (k = 0; k < nTopIndices; ++k)
    {
	topIndices[k] = rTopIndices[k] + 0.5;
    }

    for (k = 0; k < nResultComp; ++k)
    {
	if (interpolate)
	{
	    resultComp[k] = InterpolateFieldComponent(dataField, k, rTopIndices);
	}
	else
	{
	    resultComp[k] = SelectFieldComponent(dataField, k, topIndices);
	}
    }

    if (nTopIndices > 10)
    {
	free(topIndices);
	free(rTopIndices);
    }

    return true;
}

#ifdef PROTO
real SampleSpatScalar(int dataField, int formField,
	int nSpatIndices, 
	real spatIndices[], Bool interpolate)
#else
real SampleSpatScalar(dataField, formField, nSpatIndices, spatIndices, interpolate)
int dataField, formField;
int nSpatIndices;
real spatIndices[];
Bool interpolate;
#endif
/*Samples a scalar field dataField defined over formField using nSpatIndices
  spatIndices and returns the result
*/
{
    real retVal;
    long *topIndices;
    real *rTopIndices;
    long indexBuffer[10];
    real rIndexBuffer[10];
    int nTopIndices;
    int k;

    nTopIndices = curFields[formField] . topDim;
    if (nTopIndices != curFields[dataField] . topDim)
    {
	ReportError("SampleSpatScalar", "Topological dimension mismatch");
	return false;
    }

    if (nTopIndices <= 10)
    {
	topIndices = &(indexBuffer[0]);
	rTopIndices = &(rIndexBuffer[0]);
    }
    else
    {
	topIndices = malloc(nTopIndices * sizeof(long));
	rTopIndices = malloc(nTopIndices * sizeof(real));
    }
    SampleToTopIndex(formField, nTopIndices, rTopIndices, nSpatIndices, spatIndices);
    for (k = 0; k < nTopIndices; ++k)
    {
	topIndices[k] = rTopIndices[k] + 0.5;
    }


	if (interpolate)
	{
	    retVal = InterpolateFieldScalar(dataField, rTopIndices);
	}
	else
	{
	    retVal = SelectFieldScalar(dataField, topIndices);
	}

    if (nTopIndices > 10)
    {
	free(topIndices);
	free(rTopIndices);
    }

    return retVal;
}

#ifdef PROTO
void CalcBucketIndices(long indices[], int whichField, int nSamples, real *samples)
#else
void CalcBucketIndices(indices, whichField, nSamples, samples)
long indices[];
int whichField;
int nSamples;
real *samples;
#endif
/*Calculates the bucket indices of samples within whichField.  Indices
  must be an array of size == number of components in whichField*/
{
    long k;
    real div;

    for (k = 0; k < curFields[whichField] . nComponents; ++k)
    {
	if (k >= nSamples)
	{
	    /*Higher dimensionality, don't change offset*/
	    indices[k] = 0;
	}    
	else if (samples[k] < curFields[whichField] . bucketMinMax[k][0])
	{
	    /*Less than min, it's just zero.*/
	    indices[k] = 0;
	}
	else
	{
	    long i;

	    /*Calculate one of the divisions*/
	    div =  (curFields[whichField] . bucketMinMax[k][1] -
			curFields[whichField] . bucketMinMax[k][0]) /
			curFields[whichField] . bucketDim[k];
	    i = (samples[k] - curFields[whichField] . bucketMinMax[k][0]) / div;
	    if (i >= curFields[whichField] . bucketDim[k])
	    {
		i = curFields[whichField] . bucketDim[k] - 1;
	    }
	    indices[k] = i;
	}
    }
}

#ifdef PROTO
long CalcBucketOffset(int whichField, long indices[])
#else
long CalcBucketOffset(whichField, indices)
int whichField;
long indices[]
#endif
/*Calculates the bucket offset from indices within whichField*/
{
    long k;
    real div;
    long offset;

    offset = 0;

    for (k = 0; k < curFields[whichField] . nComponents; ++k)
    {
	if (k > 0)
	{
	    offset *= curFields[whichField] . bucketDim[k - 1];
	}
	offset += indices[k];
    }

    return offset;
}

#ifdef PROTO
static void FillBuckets(int whichField, int whichIndex, int maxNIndices, long *dimensions, long *indices)
#else
static void FillBuckets(whichField, whichIndex, maxNIndices, dimensions, indices)
int whichField;
int whichIndex;
int maxNIndices;
long *dimensions;
long *indices;
#endif
/*Fills the buckets of whichField using a
  powerset technique over indices.  Stuffs them into min and max*/
{
    if (whichIndex >= maxNIndices)
    {
	/*Bottomed out.  Spit out the sample*/
	long offset;
	long i, k;
	real *samples;
	long *bucketIndices;
	register Bucket *bucket;

	/*Calculate the offset*/
	samples = (real *) malloc(curFields[whichField] . nComponents * sizeof(real));
	bucketIndices = (long *) malloc(curFields[whichField] . nComponents * sizeof(long));

	for (k = 0; k < curFields[whichField] . nComponents; ++k)
	{
	    samples[k] = SelectFieldComponent(whichField, k, indices);
	}
	CalcBucketIndices(bucketIndices, whichField,
			curFields[whichField] . nComponents,
			samples);
	offset = CalcBucketOffset(whichField, bucketIndices);

	/*OK, got the offset.  Look at the bucket.*/
	bucket = &(curFields[whichField] . buckets[offset]);

	/*Go to the next slot on the bucket*/
	if (0 == (bucket -> nIndices % ADDBUCKETS))
	{
	    /*Must allocate some new buckets*/
	    if (bucket -> nIndices)
	    {
		bucket -> indices = (long *) realloc(bucket -> indices,
		    (ADDBUCKETS + bucket -> nIndices) *
		    sizeof(long) * maxNIndices);
		bucket -> samples = (real *) realloc(bucket -> samples,
		    (ADDBUCKETS + bucket -> nIndices) *
		    sizeof(real) * curFields[whichField] . nComponents);
	    }
	    else
	    {
		bucket -> indices = (long *) malloc(
		    ADDBUCKETS *
		    sizeof(long) * maxNIndices);
		bucket -> samples = (real *) malloc(
		    ADDBUCKETS *
		    sizeof(real) * curFields[whichField] . nComponents);
	    }
	}

	/*Insert this series of indices, and the sample*/
	i = bucket -> nIndices * maxNIndices;
	for (k = 0; k < maxNIndices; ++k)
	{
	    bucket -> indices[i] = indices[k];
	    ++i;
	}
	i = bucket -> nIndices * curFields[whichField] . nComponents;
	for (k = 0; k < curFields[whichField] . nComponents; ++k)
	{
	    bucket -> samples[i] = samples[k];
	    ++i;
	}

	/*Advance pointer*/
	++(bucket -> nIndices);

	/*No more samples needed*/
	free(bucketIndices);
	free(samples);
    }
    else
    {
	int maxIndex, k;

	maxIndex = dimensions[whichIndex];
	if (maxIndex > 0)
	{
	    /*It's real, do it*/
	    for (k = 0; k < maxIndex; ++k)
	    {
		indices[whichIndex] = k;
		FillBuckets(whichField, whichIndex + 1, maxNIndices, dimensions, indices);
	    }
	}
	else
	{
	    /*Descend nude*/
	    FillBuckets(whichField, whichIndex + 1, maxNIndices, dimensions, indices);
	}
    }
}

#ifdef PROTO
static int MaxComponentIndices(int whichField, int whichComp)
#else
static int MaxComponentIndices(whichField, whichComp)
int whichField;
int whichComp;
#endif
/*Returns the maximum number of indices in whichComp of whichField*/
{
    int k;
    int maxNIndices = -1;

    for (k = 0; k < curFields[whichField] . components[whichComp] . nIndices; ++k)
    {
	if (curFields[whichField] . components[whichComp] . indices[k] > maxNIndices)
	{
	    maxNIndices = curFields[whichField] . components[whichComp] . indices[k];
	}
    }
    return maxNIndices + 1;
}

#ifdef PROTO
static void MakeBuckets(int whichField)
#else
static void MakeBuckets(whichField)
int whichField;
#endif
/*Makes the buckets for whichField*/
{
    TwoReals *minMax;
    int whichComp;
    int k;

    if (!curFields[whichField] . bucketMinMax)
    {
	/*No buckets yet.  Make some*/
	int nTraversalDims;
	long *traversalDims;
	long *traversalIndices;

	long bucketDim;
	long nBucketDims;
	long *bucketDims;

	long k;
	long nElements;
	long nBuckets;
	Bucket *buckets;
	real sample;
	int whichComp, whichDim;

	minMax = (TwoReals *) malloc(curFields[whichField] . nComponents * sizeof(TwoReals));
	curFields[whichField] . bucketMinMax = minMax;

	nTraversalDims = CountTraversalDims(whichField);
	if (!nTraversalDims)
	{
	    return;
	}
	traversalDims = (long *) malloc(nTraversalDims * sizeof(long));
	GetTraversalDims(whichField, traversalDims);
	traversalIndices = (long *) malloc(nTraversalDims * sizeof(long));

	/*Go through all the samples, setting up the minmax*/
	for (whichComp = 0; whichComp < curFields[whichField] . nComponents; ++whichComp)
	{
	    /*Prepare minMax for expansion*/
	    minMax[whichComp][0] = PLUSINF;
	    minMax[whichComp][1] = MINUSINF;

	    /*Zero the indices*/
	    for (k = 0; k < nTraversalDims; ++k)
	    {
		traversalIndices[k] = 0;
	    }

	    do
	    {
		/*Sample the location at a point*/
		sample = SelectFieldComponent(whichField, 0, traversalIndices);

		if (sample != missingData)
		{
		    if (sample < minMax[whichComp][0]) minMax[whichComp][0] = sample;
		    if (sample > minMax[whichComp][1]) minMax[whichComp][1] = sample;
		}

		/*Advance to next point*/
		for (whichDim = 0; whichDim < nTraversalDims; ++whichDim)
		{
		    if (traversalDims[whichDim] > 0)
		    {
			if ((++traversalIndices[whichDim]) >= traversalDims[whichDim])
			{
			    traversalIndices[whichDim] = 0;
			}
			else
			{
			    break;
			}
		    }
		}
	    } while (whichDim < nTraversalDims); /*Break is based on advance*/
	}

	/*Start figuring how to traverse field.*/
	curFields[whichField] . nBucketIndices = nTraversalDims;

	/*Calculate the number of elements total*/
	nElements = 1;
	for (k = 0; k < nTraversalDims; ++k)
	{
	    if (traversalDims[k] >= 0)
	    {
		nElements *= traversalDims[k];
	    }
	}

	/*Now that we have the number of elements, calculate a good # of buckets.*/
	nBuckets = nElements * BUCKETSIZEFACTOR;

	/*If it's too many to be manageable, back it off*/
	if (nBuckets > MAXBUCKETSIZE)
	{
	    nBuckets = MAXBUCKETSIZE;
	}

	/*Find a bucket dimension*/
	bucketDim = (int) pow((double) nBuckets, 1.0 / (double) (curFields[whichField] . nComponents));
	if (bucketDim <= 0) ++bucketDim;

#ifdef DEBUG
	printf("Side dimension = %d\n", bucketDim);
	printf("Target # buckets = %d\n", nBuckets);
#endif
	
	/*Make bucket dimensions*/
	bucketDims = (long *) malloc((curFields[whichField] . nComponents) * sizeof(long));
	curFields[whichField] . bucketDim = bucketDims;
	for (k = 0; k < curFields[whichField] . nComponents; ++k)
	{
	    bucketDims[k] = bucketDim;
	}

	/*Create and zero the actual bucket array*/
	buckets = (Bucket *) malloc(((long) nBuckets) * sizeof(Bucket));
	curFields[whichField] . buckets = buckets;
	for (k = 0; k < nBuckets; ++k)
	{
	    buckets[k] . indices = 0;
	    buckets[k] . samples = 0;
	    buckets[k] . nIndices = 0;
	}

	/*Fill the buckets*/
	for (k = 0; k < nTraversalDims; ++k)
	{
	    traversalIndices[k] = 0;
	}
	FillBuckets(whichField, 0, nTraversalDims, traversalDims, traversalIndices);

#ifdef DEBUG
	/*Print out bucket statistics*/
	{
	    long nFilled, maxFilled;
	    nFilled = 0;
	    maxFilled = 0;
	    for (k = 0; k < nBuckets; ++k)
	    {
		if (buckets[k] . nIndices)
		{
		    ++nFilled;
		    if (buckets[k] . nIndices > maxFilled)
			maxFilled = buckets[k] . nIndices;
		}
	    }
	    printf("%ld out of %ld buckets filled (%lg%%)\n",
		nFilled, nBuckets, ((double) nFilled) / ((double) nBuckets) * 100.0);
	    printf("Maximum bucket size = %ld\n", maxFilled);
	}
#endif

	free(traversalIndices);
	free(traversalDims);
    }
}

#ifdef PROTO
void SBS2(int whichField, long powerIndices[], long centerIndices[],
	  long shell, int whichIndex, long bestIndex[],
	  real *bestGuess, real sample[])
#else
void SBS2(whichField, powerIndices, centerIndices,
	  shell, whichIndex, bestIndex,
	  bestGuess, sample)
int whichField;
long powerIndices[];
long centerIndices[];
long shell;
int whichIndex;
long bestIndex[];
real *bestGuess;
real sample[];
#endif
/*Powerset recursive helper for SearchBucketForSample.  Searches a shell 
  shell wide around centerIndices, comparing each sample with bestGuess,
  returning bestIndex if found.  whichIndex is the current index, used for
  the powerset, done through powerIndices*/
{
    long i, k;
    register int nComponents;

    nComponents = curFields[whichField] . nComponents;

    if (whichIndex >= nComponents)
    {
	real d2;	/*Distance squared*/
	real d;		/*Distance*/
	register Bucket *bucket;
	long offset;

	/*Bottomed out.  Test to see if we're in the right shell*/
	for (k = 0; k < nComponents; ++k)
	{
	    if (ABS(powerIndices[k] - centerIndices[k]) != shell)
	    {
		/*Not on this shell, so don't do anything.*/
		return;
	    }
	}

	/*Calculate the offset*/
	offset = CalcBucketOffset(whichField, powerIndices);

	/*Get the bucket*/
	bucket = &(curFields[whichField] . buckets[offset]);

	/*Search for better match*/
	for (k = 0; k < bucket -> nIndices; ++k)
	{
	    /*Calculate distance*/
	    d2 = 0.0;

	    for (i = 0; i < nComponents; ++i)
	    {
		if (bucket -> samples[k * nComponents + i] == missingData)
		{
		    break;
		}
		d = bucket -> samples[k * nComponents + i] - sample[k];
		d2 += d * d;
	    }

	    if (i >= nComponents)
	    {
		/*It completed a distance*/
		if (d2 < *bestGuess)
		{
		    /*Improved guess, copy index*/
		    *bestGuess = d2;
		    for (i = 0; i < curFields[whichField] . nBucketIndices; ++i)
		    {
			bestIndex[i] =
			    bucket -> indices[k * curFields[whichField] . nBucketIndices + i];
		    }
		}
	    }
	}
    }
    else
    {
	long min, max;

	/*Go down powerset*/
	min = centerIndices[whichIndex] - shell;
	if (min < 0)
	{
	    min = 0;
	}
	max = centerIndices[whichIndex] + shell;
	if (max >= curFields[whichField] . bucketDim[whichIndex])
	{
	    max = curFields[whichField] . bucketDim[whichIndex] - 1;
	}
	for (powerIndices[whichIndex] = min;
	     powerIndices[whichIndex] <= max;
	     ++powerIndices[whichIndex])
	{
	    SBS2(whichField, powerIndices, centerIndices,
		shell, whichIndex + 1, bestIndex,
		bestGuess, sample);
	}
    }
}

#ifdef PROTO
Bool SearchBucketForSample(int whichField, real indices[], 
	long bucketIndices[], real sample[])
#else
Bool SearchBucketForSample(whichField, indices, bucketIndices, sample)
int whichField;
real indices[];
long bucketIndices[];
real sample[];
#endif
/*Searches the buckets of whichField for sample and returns in indices.
  bucketIndices is the first guess and is assumed to be DISPOSABLE.
*/
{
    long *bestIndex;		/*Best index so far*/
    long *powerIndices;		/*Power indices*/
    long k;
    long shell, nShells;
    real bestGuess;		/*Best guess so far*/

    bestIndex = (long *) malloc(sizeof(long) * curFields[whichField] . nBucketIndices);
    powerIndices = (long *) malloc(sizeof(long) * curFields[whichField] . nComponents);

    /*Calculate the maximum number of shells to use*/
    nShells = 0;
    for (k = 0; k < curFields[whichField] . nComponents; ++k)
    {
	if (bucketIndices[k] > nShells)
	{
	    nShells = bucketIndices[k];
	}
	if (curFields[whichField] . bucketDim[k] - bucketIndices[k] > bucketIndices[k])
	{
	    nShells = curFields[whichField] . bucketDim[k] - bucketIndices[k];
	}
    }

    /*Now search for a match in progressively widening shells*/
    bestGuess = plusInf;

    for (shell = 0; shell < nShells; ++shell)
    {
	SBS2(whichField, powerIndices, bucketIndices, shell, 0, bestIndex, &bestGuess, sample);
	if (bestGuess < plusInf)
	{
	    break;
	}
    }

    /*Put the best index into indices*/
    for (k = 0; k < curFields[whichField] . nBucketIndices; ++k)
    {
	indices[k] = bestIndex[k];
    }

    free(powerIndices);
    free(bestIndex);

    return (bestGuess < plusInf) ? true : false;
}


#ifdef PROTO
Bool SampleToTopIndex(int whichField, int nIndices, real indices[], 
		int nComponents, real sample[])
#else
Bool SampleToTopIndex(whichField, nIndices, indices, nComponents, sample)
int whichField;
int nIndices;
real indices[];
int nComponents;
real sample[];
#endif
/*Finds and returns in indices the closest index in whichField to sample.
  The arrays had better be big enough*/
{
    int i, comp, k, index, myIndex;
    int indicesAssigned;
    Bool succeed;
    Bool inHere, otherFree, others;
    long *longIndices;
    long *bucketIndices;
    real retVal;

    /*Find the number of components, should be == size of sample*/
    if (nComponents < curFields[whichField] . nComponents)
    {
	ReportError("SampleToTopIndex", "Not enough components passed");
	return false;
    }

    longIndices = malloc(nIndices * sizeof(long));

    /*Make all indices free*/
    for (k = 0; k < nIndices; ++k)
    {
	indices[k] = -1.0;
	longIndices[k] = -1;
    }

    /*Number of indices which have been assigned, start at 0*/
    indicesAssigned = 0;

    do
    {
	succeed = false;
	/*Search for an index and component such that
	  1) The index is free
	  2) The index appears in the component
	  3) No other component is free*/

	for (i = 0; i < nIndices; ++i)
	{
	    /*See if it's free*/
	    if (longIndices[i] < 0)
	    {
		/*See if it appears in any components with free index*/
		for (comp = 0; comp < curFields[whichField] . nComponents; ++comp)
		{
		    inHere = others = otherFree = false;
		    for (k = 0;
			 k < curFields[whichField] . components[comp] . nIndices;
			 ++k)
		    {
			index = curFields[whichField] . components[comp] . indices[k];
			if (index == i)
			{
			    myIndex = k;
			    inHere = true;
			}
			else if (index >= 0)
			{
			    if (longIndices[index] < 0)
			    {
				otherFree = true;
			    }
			    else
			    {
				others = true;
			    }
			}
		    }

		    if (inHere && !otherFree)
		    {
			/*Found one which is free*/

			if (!others &&
			    0 == (curFields[whichField] . components[comp] . flags & (CF_MONOTONIC | CF_NOTMONOTONIC)))
			{
			    int testo;
			    real next, last;

			    testo = 0;
			    
			    /*Might be monotonic.  Let's check*/
			    longIndices[i] = 0;
			    last = SelectFieldComponent(whichField, comp, longIndices);
			    for (longIndices[i] = 1;
			         longIndices[i] < curFields[whichField] . components[comp] . dimensions[myIndex];
			         ++(longIndices[i]))
			    {
				next = SelectFieldComponent(whichField, comp, longIndices);
				if (next > last)
				{
				    testo |= 1;
				}
				else if (last > next)
				{
				    testo |= 2;
				}
				last = next;
			    }
			    if (testo < 3)
			    {
				curFields[whichField] . components[comp] . flags |= CF_MONOTONIC;
			    }
			    else
			    {
				curFields[whichField] . components[comp] . flags |= CF_NOTMONOTONIC;
			    }
			}

			/*Hey!  Found it!*/

			if (curFields[whichField] . components[comp] . flags & CF_MONOTONIC)
			{
			    long beg, end, mid;
			    real begFit, endFit, midFit, diff1, diff2;

			    beg = 0;
			    end = curFields[whichField] . components[comp] . dimensions[myIndex] - 1;
			    longIndices[i] = beg;
			    begFit = SelectFieldComponent(whichField, comp, longIndices);
			    longIndices[i] = end;
			    endFit = SelectFieldComponent(whichField, comp, longIndices);

			    if (begFit < endFit)
			    {
				/*Positive monotonic*/
				if (sample[comp] <= begFit)
				{
				    longIndices[i] = beg;
				    indices[i] = beg;
				}
				else if (sample[comp] >= endFit)
				{
				    longIndices[i] = end;
				    indices[i] = end;
				}
				else
				{
				    while (beg + 1 < end)
				    {
					mid = (beg + end) / 2;
					if (mid <= beg) mid = beg + 1;
					else if (mid >= end) mid = end - 1;
					longIndices[i] = mid;
					midFit = SelectFieldComponent(whichField, comp, longIndices);
					if (midFit > sample[comp])
					{
					    endFit = midFit;
					    end = mid;
					}
					else
					{
					    begFit = midFit;
					    beg = mid;
					}
				    }
				    diff1 = begFit - sample[comp];
				    diff1 = ABS(diff1);
				    diff2 = endFit - sample[comp];
				    diff2 = ABS(diff2);
				    longIndices[i] = (diff1 > diff2) ? end : beg;
				    indices[i] = ((beg * diff2) + (end * diff1)) / (diff1 + diff2);
				}
				succeed = true;
				++indicesAssigned;
			    }
			    else
			    {
				/*Negative monotonic*/
				if (sample[comp] >= begFit)
				{
				    longIndices[i] = beg;
				    indices[i] = beg;
				}
				else if (sample[comp] <= endFit)
				{
				    longIndices[i] = end;
				    indices[i] = end;
				}
				else
				{
				    while (beg + 1 < end)
				    {
					mid = (beg + end) / 2;
					if (mid >= beg) mid = beg + 1;
					else if (mid <= end) mid = end - 1;
					longIndices[i] = mid;
					midFit = SelectFieldComponent(whichField, comp, longIndices);
					if (midFit < sample[comp])
					{
					    endFit = midFit;
					    end = mid;
					}
					else
					{
					    begFit = midFit;
					    beg = mid;
					}
				    }
				    diff1 = begFit - sample[comp];
				    diff1 = ABS(diff1);
				    diff2 = endFit - sample[comp];
				    diff2 = ABS(diff2);
				    longIndices[i] = (diff1 > diff2) ? end : beg;
				    indices[i] = ((beg * diff2) + (end * diff1)) / (diff1 + diff2);
				}
				succeed = true;
				++indicesAssigned;
			    }
			}
			else
			{
			    /*It's not monotonic.  Do a linear search*/
			    long bestFit;
			    real bestDist;
			    real diff;

			    succeed = true;
			    ++indicesAssigned;

			    longIndices[i] = 0;
			    bestFit = 0;
			    diff = SelectFieldComponent(whichField, comp, longIndices) -
				sample[comp];
			    bestDist = ABS(diff);

			    for (longIndices[i] = 1;
				 longIndices[i] < curFields[whichField] . components[comp] . dimensions[myIndex];
				 ++(longIndices[i]))
			    {
				diff = SelectFieldComponent(whichField, comp, longIndices) -
				    sample[comp];
				diff = ABS(diff);
				if (diff < bestDist)
				{
				    bestDist = diff;
				    bestFit = longIndices[i];
				}
			    }
			    longIndices[i] = bestFit;
			    if (longIndices[i] <= 0)
			    {
				real sCenter, sPlus;
				/*At left, see if it's between 0 and 1*/

				sCenter = SelectFieldComponent(whichField, comp, longIndices);
				++longIndices[i];
				sPlus = SelectFieldComponent(whichField, comp, longIndices);
				--longIndices[i];
				if (sCenter <= sample[comp] <= sPlus)
				{
				    indices[i] =
					(sPlus - sample[comp]) * longIndices[i] +
					(sample[comp] - sCenter) * (longIndices[i] + 1);
				}
				else if (sPlus <= sample[comp] <= sCenter)
				{
				    indices[i] =
					(sCenter - sample[comp]) * longIndices[i] +
					(sample[comp] - sPlus) * (longIndices[i] + 1);
				}
				else
				{
				    indices[i] = longIndices[i];
				}
			    }
			    else if (longIndices[i] >= curFields[whichField] . components[comp] . dimensions[myIndex])
			    {
				real sCenter, sMinus;
				/*At right, see if it's between right and right - 1*/

				sCenter = SelectFieldComponent(whichField, comp, longIndices);
				--longIndices[i];
				sMinus = SelectFieldComponent(whichField, comp, longIndices);
				++longIndices[i];
				if (sCenter <= sample[comp] <= sMinus)
				{
				    indices[i] =
					(sMinus - sample[comp]) * longIndices[i] +
					(sample[comp] - sCenter) * (longIndices[i] - 1);
				}
				else if (sMinus <= sample[comp] <= sCenter)
				{
				    indices[i] =
					(sCenter - sample[comp]) * longIndices[i] +
					(sample[comp] - sMinus) * (longIndices[i] - 1);
				}
				else
				{
				    indices[i] = longIndices[i];
				}
			    }
			    else
			    {
				real sCenter, sMinus, sPlus;
				/*It's in the center*/
				sCenter = SelectFieldComponent(whichField, comp, longIndices);
				--longIndices[i];
				sMinus = SelectFieldComponent(whichField, comp, longIndices);
				++longIndices[i];
				++longIndices[i];
				sPlus = SelectFieldComponent(whichField, comp, longIndices);
				--longIndices[i];
				if (sCenter <= sample[comp] <= sPlus)
				{
				    indices[i] =
					(sPlus - sample[comp]) * longIndices[i] +
					(sample[comp] - sCenter) * (longIndices[i] + 1);
				}
				else if (sPlus <= sample[comp] <= sCenter)
				{
				    indices[i] =
					(sCenter - sample[comp]) * longIndices[i] +
					(sample[comp] - sPlus) * (longIndices[i] + 1);
				}
				else if (sCenter <= sample[comp] <= sMinus)
				{
				    indices[i] =
					(sMinus - sample[comp]) * longIndices[i] +
					(sample[comp] - sCenter) * (longIndices[i] - 1);
				}
				else if (sMinus <= sample[comp] <= sCenter)
				{
				    indices[i] =
					(sCenter - sample[comp]) * longIndices[i] +
					(sample[comp] - sMinus) * (longIndices[i] - 1);
				}
				else
				{
				    indices[i] = longIndices[i];
				}
			    }
			}
		    }
		}
	    }
	}
    } while (succeed && indicesAssigned < nIndices);

    free(longIndices);

    if (indicesAssigned >= nIndices)
    {
	/*Success*/
	return true;
    }

    /*Fell through, so have to use bucket approach*/
    MakeBuckets(whichField);

    /*Calculate initial guess*/
    bucketIndices = (long *) malloc(sizeof(long) * curFields[whichField] . nComponents);

    CalcBucketIndices(bucketIndices, whichField,
			curFields[whichField] . nComponents,
			sample);

    retVal = SearchBucketForSample(whichField, indices, bucketIndices, sample);
    free(bucketIndices);
    return retVal;
}

#ifdef PROTO
real SelectFieldComponent(int whichField, int whichComponent, long indices[])
#else
real SelectFieldComponent(whichField, whichComponent, indices)
int whichField;
int whichComponent;
long indices[];
#endif
/*Selects a field component given indices into it.  There had better be enough
  indices*/
{
    register int k;
    register long offset = 0;		/*Offset into the data*/
    register Component *component;	/*The current component*/

    /*Get the current component*/
    component = &(curFields[whichField] . components[whichComponent]);

    /*Calculate the offset*/
    for (k = 0; k < component -> nIndices; ++k)
    {
	register int whichIndex;

	whichIndex = component -> indices[k];

	if (whichIndex >= 0)
	{
	    offset += component -> steps[k] * indices[whichIndex];
	}
    }
    return component -> dataCompressed ?
		COMPTOREAL(whichField, component -> data . comp[offset]):
		component -> data . unComp[offset];
}

#ifdef PROTO
real SelectFieldScalar(int whichField, long indices[])
#else
real SelectFieldScalar(whichField, indices)
int whichField;
long indices[];
#endif
/*Selects a scalar value from whichField using indices*/
{
    if (curFields[whichField] . nComponents < 2)
    {
	return SelectFieldComponent(whichField, 0, indices);
    }
    else
    {
	real squareSum, d;
	int whichComponent;

	squareSum = 0.0;
	for (whichComponent = 0;
		whichComponent < curFields[whichField] . nComponents;
		++whichComponent)
	{
	    d = SelectFieldComponent(whichField, whichComponent, indices);

	    if (d == missingData)
	    {
		return missingData;
	    }
	    squareSum += SQUARE(d);
	}
	return rsqrt(squareSum);
    }
}

#ifdef PROTO
real InterpolateFieldComponent(int whichField, int whichComponent, real indices[])
#else
real InterpolateFieldComponent(whichField, whichComponent, indices)
int whichField;
int whichComponent;
real indices[];
#endif
/*Interpolates a component at a set of real topological indices*/
{
    register int i, k;
    register long test;
    register Component *component;	/*The current component*/
    real retVal;
    register int whichIndex;
    register real index;

    component = &(curFields[whichField] . components[whichComponent]);

    for (k = 0; k < component -> nIndices; ++k)
    {
	whichIndex = component -> indices[k];

	if (whichIndex >= 0)
	{
	    index = indices[whichIndex];

	    test = index;
	    if (index != (real) test)
	    {
		/*It's non integral*/

		if (index < -0.5 ||
		    index > ((real) component -> dimensions[whichIndex]) - 0.5)
		{
		    /*It's way outside*/
		    return missingData;
		}
		else
		{
		    /*It's between two vertices*/
		    long i1, i2;
		    real v1, v2;
		    real *tempIndices;
		    int copyIndex;

		    i1 = test;
		    i2 = i1 + 1;

		    tempIndices = (real *) malloc(MaxComponentIndices(whichField, whichComponent) * sizeof(real));

		    /*Copy all the indices*/
		    for (i = 0; i < component -> nIndices; ++i)
		    {
			copyIndex = component -> indices[i];
			if (copyIndex >= 0)
			{
			    tempIndices[copyIndex] = indices[copyIndex];
			}
		    }

		    /*Now get the two values for the two indices*/
		    tempIndices[whichIndex] = i1;
		    v1 = InterpolateFieldComponent(whichField, whichComponent, tempIndices);
		    tempIndices[whichIndex] = i2;
		    v2 = InterpolateFieldComponent(whichField, whichComponent, tempIndices);

		    free(tempIndices);

		    /*Now return an interpolation*/
		    if (v1 == missingData)
		    {
			if (v2 == missingData)
			{
			    return missingData;
			}
			else
			{
			    if (i2 - index <= index - i1)
			    {
				return v2;
			    }
			    else
			    {
				return missingData;
			    }
			}
		    }
		    else if (v2 == missingData)
		    {
			if (index - i1 <= i2 - index)
			{
			    return v1;
			}
			else
			{
			    return missingData;
			}
		    }
		    else
		    {
			return (index - i1) * v2 + (i2 - index) * v1;
		    }
		}
	    }
	}
    }

    /*If we fell through here, it must be smack dab on a vertex.  Just get it*/
    {
	long *tempIndices;

	tempIndices = (long *) malloc(MaxComponentIndices(whichField, whichComponent) * sizeof(long));
	for (k = 0; k < component -> nIndices; ++k)
	{
	    whichIndex = component -> indices[k];
	    if (whichIndex >= 0)
	    {
	        tempIndices[whichIndex] = indices[whichIndex];
	    }
	}
	retVal = SelectFieldComponent(whichField, whichComponent, tempIndices);
	free(tempIndices);
	return retVal;
    }
}

#ifdef PROTO
real InterpolateFieldScalar(int whichField, real indices[])
#else
real InterpolateFieldScalar(whichField, indices)
int whichField;
real indices[];
#endif
/*Interpolates between scalar values*/
{
    if (curFields[whichField] . nComponents < 2)
    {
	return InterpolateFieldComponent(whichField, 0, indices);
    }
    else
    {
	real squareSum;
	int k;
	real d;

	squareSum = 0.0;
	for (k = 0; k < curFields[whichField] . nComponents; ++k)
	{
	    d = InterpolateFieldComponent(whichField, k, indices);
	    if (d == missingData) return missingData;
	    squareSum += SQUARE(d);
	}

	return rsqrt(squareSum);
    }
}

long GetComponentOffset(whichField, whichComponent, indices)
int whichField;
int whichComponent;
long indices[];
/*Get the offset into a field component given indices into it.  There had better be enough
  indices*/
{
    register int k;
    register long offset = 0;		/*Offset into the data*/
    register Component *component;	/*The current component*/

    /*Get the current component*/
    if (whichComponent < 0 || 
	whichComponent > curFields[whichField] . nComponents)
    {
	return 0;
    }
    component = &(curFields[whichField] . components[whichComponent]);

    /*Calculate the offset*/
    for (k = 0; k < component -> nIndices; ++k)
    {
	register int whichIndex;
	register long index;
	
	whichIndex = component -> indices[k];

	if (whichIndex >= 0)
	{
	    index = indices[whichIndex];
	    if (index < 0 || index > component -> dimensions[k])
	    {
		return 0;
	    }
	}

	offset += component -> steps[k] * index;
    }
    return offset;
}


#ifdef PROTO
void PutFieldComponent(int whichField, int whichComponent, long indices[], real data)
#else
void PutFieldComponent(whichField, whichComponent, indices, data)
int whichField;
int whichComponent;
long indices[];
real data;
#endif
/*Puts data into a field component given indices into it.  There had better be enough
  indices*/
{
    register int k;
    register long offset = 0;		/*Offset into the data*/
    register Component *component;	/*The current component*/

    /*Get the current component*/
    if (whichComponent < 0 || 
	whichComponent > curFields[whichField] . nComponents)
    {
	return;
    }
    component = &(curFields[whichField] . components[whichComponent]);

    /*Calculate the offset*/
    for (k = 0; k < component -> nIndices; ++k)
    {
	register int whichIndex;
	register long index;
	
	whichIndex = component -> indices[k];

	if (whichIndex >= 0)
	{
	    index = indices[whichIndex];
	    if (index < 0 || index > component -> dimensions[k])
	    {
		return;
	    }

	    offset += component -> steps[k] * index;
	}
    }
    if (component -> dataCompressed)
    {
	component -> data . comp[offset] = REALTOCOMP(whichField, data);
    }
    else
    {
	component -> data . unComp [offset] = data;
    }
}

void StuffScalarIJSquare(dest, whichField, indices)
real dest[2][2];
int whichField;
long indices[];
/*Stuffs the square in the ij plane with its lower left corner defined by
  indices into dest.  Takes data from the specified field, which can be 
  scalar, in which case the scalar value is used, or vector, in which case
  the absolute value is used.  

  There had better be enough indices.  This does not check
  validity of the indices*/
{
    int k;
    register long offset;		/*Offset into the data*/
    register long iStep = 0, jStep = 0;	/*Step in the i and j directions*/
    register Component *component;	/*The current component*/

    if (curFields[whichField] . nComponents < 2)
    {
	/*Get the current component*/
	component = &(curFields[whichField] . components[0]);

	offset = 0;
	/*Calculate the offset*/
	for (k = 0; k < component -> nIndices; ++k)
	{
	    register int whichIndex;
	    register long index;
	    
	    whichIndex = component -> indices[k];
	    
	    if (whichIndex >= 0)
	    {
		index = indices[whichIndex];
		if (whichIndex == 0)
		{
		    /*I component*/
		    iStep = component -> steps[k];
		    offset += iStep * index;
		}
		else if (whichIndex == 1)
		{
		    /*J component*/
		    jStep = component -> steps[k];
		    offset += jStep * index;
		}
		else
		{
		    offset += component -> steps[k] * index;
		}
	    }
	}
	if (component -> dataCompressed)
	{
	    dest[0][0] = COMPTOREAL(whichField, component -> data . comp[offset]);
	    dest[1][0] = COMPTOREAL(whichField, component -> data . comp[offset + iStep]);
	    dest[0][1] = COMPTOREAL(whichField, component -> data . comp[offset + jStep]);
	    dest[1][1] = COMPTOREAL(whichField, component -> data . comp[offset + iStep + jStep]);
	}
	else
	{
	    dest[0][0] = component -> data . unComp[offset];
	    dest[1][0] = component -> data . unComp[offset + iStep];
	    dest[0][1] = component -> data . unComp[offset + jStep];
	    dest[1][1] = component -> data . unComp[offset + iStep + jStep];
	}
    }
    else
    {
	int whichComponent;
	real squareSum[2][2];
	Bool missing[2][2];
	real d;

	squareSum[0][0] = 0.0;
	squareSum[0][1] = 0.0;
	squareSum[1][0] = 0.0;
	squareSum[1][1] = 0.0;

	missing[0][0] = false;
	missing[0][1] = false;
	missing[1][0] = false;
	missing[1][1] = false;

	for (whichComponent = 0;
		whichComponent < curFields[whichField] . nComponents;
		++whichComponent)
	{
	    /*Get the current component*/
	    component = &(curFields[whichField] . components[whichComponent]);

	    /*Calculate the offset*/
	    offset = 0;
	    iStep = 0;
	    jStep = 0;
	    for (k = 0; k < component -> nIndices; ++k)
	    {
		register int whichIndex;
		register long index;
	    
		whichIndex = component -> indices[k];

		if (whichIndex >= 0)
		{
		    index = indices[whichIndex];
		    if (whichIndex == 0)
		    {
			/*I component*/
			iStep = component -> steps[k];
			offset += iStep * index;
		    }
		    else if (whichIndex == 1)
		    {
			/*J component*/
			jStep = component -> steps[k];
			offset += jStep * index;
		    }
		    else
		    {
			offset += component -> steps[k] * index;
		    }
		}
	    }

	    if (component -> dataCompressed)
	    {
		d = COMPTOREAL(whichField, component -> data . comp[offset]);
		if (d == missingData)
		{
		    missing[0][0] = true;
		}
		else
		{
		    squareSum[0][0] += SQUARE(d);
		}
		
		d = COMPTOREAL(whichField, component -> data . comp[offset + iStep]);
		if (d == missingData)
		{
		    missing[1][0] = true;
		}
		else
		{
		    squareSum[1][0] += SQUARE(d);
		}
		
		d = COMPTOREAL(whichField, component -> data . comp[offset + jStep]);
		if (d == missingData)
		{
		    missing[0][1] = true;
		}
		else
		{
		    squareSum[0][1] += SQUARE(d);
		}
		
		d = COMPTOREAL(whichField, component -> data . comp[offset + iStep + jStep]);
		if (d == missingData)
		{
		    missing[1][1] = true;
		}
		else
		{
		    squareSum[1][1] += SQUARE(d);
		}
	    }
	    else
	    {
		d = component -> data . unComp[offset];
		if (d == missingData)
		{
		    missing[0][0] = true;
		}
		else
		{
		    squareSum[0][0] += SQUARE(d);
		}
		
		d = component -> data . unComp[offset + iStep];
		if (d == missingData)
		{
		    missing[1][0] = true;
		}
		else
		{
		    squareSum[1][0] += SQUARE(d);
		}
		
		d = component -> data . unComp[offset + jStep];
		if (d == missingData)
		{
		    missing[0][1] = true;
		}
		else
		{
		    squareSum[0][1] += SQUARE(d);
		}
		
		d = component -> data . unComp[offset + iStep + jStep];
		if (d == missingData)
		{
		    missing[1][1] = true;
		}
		else
		{
		    squareSum[1][1] += SQUARE(d);
		}
	    }
	}
	dest[0][0] = (missing[0][0]) ? (missingData) : rsqrt(squareSum[0][0]);
	dest[1][0] = (missing[1][0]) ? (missingData) : rsqrt(squareSum[1][0]);
	dest[0][1] = (missing[0][1]) ? (missingData) : rsqrt(squareSum[0][1]);
	dest[1][1] = (missing[1][1]) ? (missingData) : rsqrt(squareSum[1][1]);
   }
}

void StuffIJSquare(dest, whichField, whichComponent, indices)
real dest[2][2];
int whichField;
int whichComponent;
long indices[];
/*Stuffs the square in the ij plane with its lower left corner defined by
  indices into dest.  Takes data from the specified field, which had better
  have component.  There had better be enough indices.  This does not check
  validity of the indices*/
{
    int k;
    register long offset = 0;		/*Offset into the data*/
    register long iStep = 0, jStep = 0;	/*Step in the i and j directions*/
    register Component *component;	/*The current component*/
    register real *data;

    /*Get the current component*/
    component = &(curFields[whichField] . components[whichComponent]);

    /*Calculate the offset*/
    for (k = 0; k < component -> nIndices; ++k)
    {
	register int whichIndex;
	register long index;
	
	whichIndex = component -> indices[k];

	if (whichIndex >= 0)
	{
	    index = indices[whichIndex];
	    if (whichIndex == 0)
	    {
		/*I component*/
		iStep = component -> steps[k];
		offset += iStep * index;
	    }
	    else if (whichIndex == 1)
	    {
		/*J component*/
		jStep = component -> steps[k];
		offset += jStep * index;
	    }
	    else
	    {
		offset += component -> steps[k] * index;
	    }
	}
    }
	if (component -> dataCompressed)
	{
	    dest[0][0] = COMPTOREAL(whichField, component -> data . comp[offset]);
	    dest[1][0] = COMPTOREAL(whichField, component -> data . comp[offset + iStep]);
	    dest[0][1] = COMPTOREAL(whichField, component -> data . comp[offset + jStep]);
	    dest[1][1] = COMPTOREAL(whichField, component -> data . comp[offset + iStep + jStep]);
	}
	else
	{
	    dest[0][0] = component -> data . unComp[offset];
	    dest[1][0] = component -> data . unComp[offset + iStep];
	    dest[0][1] = component -> data . unComp[offset + jStep];
	    dest[1][1] = component -> data . unComp[offset + iStep + jStep];
	}
}

Bool SetCurField(whichField, field)
int whichField;
ObjPtr field;
/*Sets current field whichField to field.  Returns true iff it did*/
{
    FuncTyp method;

    /*Clean the field before setting it*/
    CleanCurField(whichField);

    curFields[whichField] . topDim = GetTopDim(field);
    curFields[whichField] . fieldObj = field;

    method = GetMethodSurely("SetCurField", field, REGISTERFIELD);
    if (method)
    {
	ObjPtr result;
	result = (*method)(field, whichField);
	if (IsTrue(result))
	{
	    /*See if it's appropriate to set the idiot flag*/
	    long whichComponent;
	    register Component *component;
	    int k;

	    curFields[whichField] . idiotFlag = true;

	    for (whichComponent = 0;
		 whichComponent < curFields[whichField] . nComponents;
		 ++whichComponent)
	    {
		component = &(curFields[whichField] . components[whichComponent]);
		if (curFields[whichField] . topDim != 1)
		{
		    curFields[whichField] . idiotFlag = false;
		    break;
		}

		if (curFields[whichField] . components[whichComponent] . dataCompressed)
		{
		    curFields[whichField] . idiotFlag = false;
		    break;
		}

		if (curFields[whichField] . topDim !=
		    component -> nIndices)
		{
		    curFields[whichField] . idiotFlag = false;
		    break;
		}

		for (k = component -> nIndices - 1; k >= 0; --k)
		{
		    if (component -> indices[k] != k)
		    {
			curFields[whichField] . idiotFlag = false;
			break;
		    }
		    if (component -> steps[k] !=
			((k == component -> nIndices - 1) ?
			    1 :
			    component -> dimensions[k + 1] * component -> steps[k + 1]))
		    {
			curFields[whichField] . idiotFlag = false;
			break;
		    }
		}
	    }

	    return true;
	}
	else
	{
	    return false;
	}
    }
    else
    {
	return false;
    }
}

Bool SetCurForm(whichField, field)
int whichField;
ObjPtr field;
/*Sets current field whichField to the data form of field*/
{
    FuncTyp method;

    CleanCurField(whichField);

    curFields[whichField] . topDim = GetTopDim(field);
    curFields[whichField] . fieldObj = GetVar(field, DATAFORM);

    method = GetMethodSurely("SetCurField", field, REGISTERFORM);
    if (method)
    {
	ObjPtr result;
	result = (*method)(field, whichField);
	if (IsTrue(result))
	{
	    /*See if it's appropriate to set the idiot flag*/
	    long whichComponent;
	    register Component *component;
	    int k;

	    curFields[whichField] . idiotFlag = true;

	    for (whichComponent = 0;
		 whichComponent < curFields[whichField] . nComponents;
		 ++whichComponent)
	    {
		component = &(curFields[whichField] . components[whichComponent]);
		if (curFields[whichField] . topDim !=
		    component -> nIndices)
		{
		    curFields[whichField] . idiotFlag = false;
		    break;
		}

		if (curFields[whichField] . topDim != 1)
		{
		    curFields[whichField] . idiotFlag = false;
		    break;
		}

		for (k = component -> nIndices - 1; k >= 0; --k)
		{
		    if (component -> indices[k] != k)
		    {
			curFields[whichField] . idiotFlag = false;
			break;
		    }
		    if (component -> steps[k] !=
			((k == component -> nIndices - 1) ?
			    1 :
			    component -> dimensions[k + 1] * component -> steps[k + 1]))
		    {
			curFields[whichField] . idiotFlag = false;
			break;
		    }
		}
	    }

	    return true;
	}
	else
	{
	    return false;
	}
     }
    return false;
}

ObjPtr RegisterDatasetForm(dataset, whichField)
int whichField;
ObjPtr dataset;
/*Registers the form of a dataset in whichField*/
{
    ObjPtr dataForm;

    dataForm = GetVar(dataset, DATAFORM);
    if (dataForm)
    {
	return SetCurField(whichField, dataForm) ? ObjTrue : ObjFalse;
    }
    ReportError("RegisterDatasetForm", "No DATAFORM");
    return ObjFalse;
}

Bool RegisterComponent(whichField, whichComponent, field)
int whichField, whichComponent;
ObjPtr field;
/*Registers field as whichComponent of whichField*/
{
    FuncTyp method;

    if (whichComponent < 0 || whichComponent >= curFields[whichField] . nComponents)
    {
	ReportError("RegisterComponent", "Component number out of range");
	return false;
    }

    method = GetMethodSurely("RegisterComponent", field, REGISTERCOMP);
    if (method)
    {
	ObjPtr result;
	result = (*method)(field, whichField, whichComponent);
	return IsTrue(result) ? true : false;
    }
    return false;
}

static ObjPtr globalDataset;		/*Global dataser for ForAllVisWindows*/
static void ChangeWindowForDataset(window)
WinInfoPtr window;
/*Changes a given window upon the knowledge that the dataset has changed*/
{
    ObjPtr space;
    space = FindSpace(window);
    if (space)
    {
	ObjPtr clock;
	ImInvalid((ObjPtr) window);
	clock = GetVar(space, CLOCK);
	if (clock)
	{
	    WinInfoPtr dialogExists;
	    dialogExists = DialogExists((WinInfoPtr) clock, NewString("Clock"));
	    if (dialogExists)
	    {
		ObjPtr timeControl;
		timeControl = GetVar((ObjPtr) dialogExists, TIMECONTROL);
		if (timeControl)
		{
		    RecalcScroll(timeControl);
		}
		ImInvalid((ObjPtr) dialogExists);
	    }
	}
    }
}

void DatasetChanged(dataset)
ObjPtr dataset;
/*Alerts the system that a dataset has changed*/
{
    globalDataset = dataset;
    ForAllVisWindows(ChangeWindowForDataset);
}

static ObjPtr ChangeDatasetInterpolate(object)
ObjPtr object;
/*Changes, according to the value of object, whether a dataset is interpolated*/
{
    ObjPtr repObj, value, dataObj;

    repObj = GetObjectVar("ChangeDatasetInterpolate", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(object);

    SetVar(repObj, INTERPOLATEP, IsTrue(value) ? ObjTrue : ObjFalse);

    dataObj = GetVar(repObj, DATA);
    if (dataObj)
    {
	SetVar(dataObj, INTERPOLATEP, IsTrue(value) ? ObjTrue : ObjFalse);
    }

    DatasetChanged(repObj);
    return ObjTrue;
}

static ObjPtr HideDatasetWindow(window)
ObjPtr window;
/*Hide a dataset window, just return OK*/
{
    return ObjTrue;
}

static void NotPaletteAlert()
{
    WinInfoPtr errWindow;
    errWindow = AlertUser(UIERRORALERT, (WinInfoPtr) 0, "Only palettes can be dropped here.", 0, 0, "");
    SetVar((ObjPtr) errWindow, HELPSTRING,
	NewString("You have tried to drop an object which is not a palette into the palette \
box of a dataset.  Try again using a real palette."));
}

static ObjPtr DropInPaletteCorral(corral, object, x, y)
ObjPtr corral, object;
int x, y;
/*Drops an icon in a palette corral*/
{
    ObjPtr dataset;
    ObjPtr palette;
    ObjPtr icon;
    ObjPtr name;
    ObjPtr defaultIcon;
    ObjPtr contents;
    ObjPtr button;

    /*Find the visualization object*/
    dataset = GetObjectVar("DropInPaletteCorral", corral, REPOBJ);
    if (!dataset)
    {
	return ObjFalse;
    }

    /*Get the field object*/
    palette = GetVar(object, REPOBJ);
    if (!palette)
    {
	return ObjFalse;
    }
    if (!IsPalette(palette))
    {
	DoTask(NotPaletteAlert);
	return ObjFalse;
    }

    /*Create an icon for it*/
    name = GetStringVar("DropInPaletteCorral", palette, NAME);
    if (!name)
    {
	return ObjFalse;
    }

    defaultIcon = GetVar(palette, DEFAULTICON);
    if (defaultIcon)
    {
	ObjPtr locArray;
	real loc[2];
	icon = NewObject(defaultIcon, 0);
	SetVar(icon, NAME, name);
	loc[0] = x;
	loc[1] = y;
	locArray = NewRealArray(1, 2L);
	CArray2Array(locArray, loc);
	SetVar(icon, ICONLOC, locArray);
    }
    else
    {
	icon = NewIcon(x, y, ICONQUESTION, GetString(name));
    }
    
    /*Make the icon point to the palette*/
    SetVar(icon, REPOBJ, palette);

    /*Make it the only icon in the corral*/
    contents = NewList();
    PrefixList(contents, icon);
    SetVar(corral, CONTENTS, contents);
    SetVar(contents, PARENT, corral);
    SetVar(icon, CORRAL, corral);

    SetVar(icon, PARENT, corral);
    RecalcScroll(corral);

    /*Make this object the colored object*/
    SetVar(dataset, CPALETTE, palette);

    ImInvalid(corral);
    DatasetChanged(dataset);

    return ObjTrue;
}

static ObjPtr AddDatasetControls(object, contents)
ObjPtr object, contents;
/*Adds controls for a dataset*/
{
    ObjPtr var;
    ObjPtr corral;
    long info;
    ObjPtr textBox, checkBox, icon, palette, name, button;
    int left, right, bottom, top;
    char *s;

    /*Add the info description*/
    left = MINORBORDER;
    right = CWINWIDTH - 2 * CORRALBORDER - CWINCORRALWIDTH - MINORBORDER;
    top = CWINHEIGHT - MINORBORDER;
    bottom = MINORBORDER;

    info = GetDatasetInfo(object);

    s = tempStr;
    sprintf(s, "Data: ");
    while (*s) ++s;
    if (info & DS_TIMEDEPENDENT)
    {
	sprintf(s, "Time-dependent ");
    }
    else
    {
	sprintf(s, "Static ");
    }

    while (*s) ++s;
    if (info &  (DS_HASGEOMETRY | DS_HASNEWGEOMETRY))
    {
	sprintf(s, "geometric data\n");
    }
    else
    {
	long nBytes;
	long k;
	long nTraversalDims;
	long *traversalDims;

	SetCurField(FIELD1, object);
	if (info & DS_VECTOR)
	{
	    long nComponents;
	    nComponents = GetNComponents(FIELD1);
	    sprintf(s, "%d-vector %s\n", nComponents,
		info & DS_HASFORM ? "field" : "data");
	}
	else
	{
	    sprintf(s, "scalar %s\n", info & DS_HASFORM ? "field" : "data");
	}
	nTraversalDims = CountTraversalDims(FIELD1);
	if (nTraversalDims > 0)
	{
	    while (*s) ++s;
	    traversalDims = (long *) malloc(sizeof(long) * nTraversalDims);
	    GetTraversalDims(FIELD1, traversalDims);
	    nBytes = 1;
	    for (k = 0; k < nTraversalDims; ++k)
	    {
		nBytes *= traversalDims[k];
	    }
	    nBytes *= sizeof(float);
	    sprintf(s, "\tMemory: %ld bytes per time step\n", nBytes);
	}
	else
	{
	    while (*s) ++s;
	    sprintf(s, "\tSingle numeric value\n");
	}
	    while (*s) ++s;
	    sprintf(s, "\tRange: [%g, %g]\n", curFields[FIELD1] . min, curFields[FIELD1] . max);
    }
    while (*s) ++s;
    sprintf(s, "\nGrid: ");
    while (*s) ++s;
    if (info & DS_HASFORM)
    {
	char *t;
	long topDim;
	ObjPtr formObj;

	sprintf(s, "%d-dimensional %s grid\n", 
	    topDim = GetTopDim(object),
	    info & DS_UNSTRUCTURED ? "unstructured" : "structured");

	if (0 == (info & DS_UNSTRUCTURED))
	{
	    ObjPtr dimsArray;
	    int k;
	    real *meat;
	    t = s;
	    while (*t) ++t;

	    MakeVar(object, DATAFORM);
	    formObj = GetVar(object, DATAFORM);

	    dimsArray = GetFixedArrayVar("AddDatasetControls", formObj, DIMENSIONS, 1, topDim);
	    if (dimsArray)
	    {
		meat = ArrayMeat(dimsArray);
		for (k = 0; k < topDim; ++k)
		{
		    sprintf(t, "%g", meat[k]);
		    while (*t) ++t;
		    if (k < topDim - 1)
		    {
			sprintf(t, " by ");
			while (*t) ++t;
		    }
		}
		sprintf(t, "\n");
	    }
	}
    }
    else
    {
	sprintf(s, "none");
    }
    while (*s) ++s;
    textBox = NewTextBox(left, right, 
		     bottom, top,
		     0, "Info Text", tempStr);
    PrefixList(contents, textBox);
    SetVar(textBox, PARENT, contents);
    SetVar(textBox, TEXTFONT, NewString(DCFONT));
    SetVar(textBox, TEXTSIZE, NewInt(DCFONTSIZE));
    top = bottom - MAJORBORDER;

    /*Add the interpolate check box*/
    if (info & DS_TIMEDEPENDENT)
    {
	bottom = top - CHECKBOXHEIGHT;
	/*Create the Display Light check box*/
	checkBox = NewCheckBox(left, right, bottom, top,
			       "Interpolate in time",
			       GetPredicate(object, INTERPOLATEP));
	SetVar(checkBox, HELPSTRING,
	    NewString("If this box is checked, the field will automatically be \
interpolated in time.  If it is not checked, the time step nearest the current \
time will always be used.\n"));
	PrefixList(contents, checkBox);
	SetVar(checkBox, PARENT, contents);
	SetVar(checkBox, REPOBJ, object);
	SetMethod(checkBox, CHANGEDVALUE, ChangeDatasetInterpolate);
    }

    /*Add in the colors corral*/
    bottom = MAJORBORDER + BUTTONHEIGHT + MINORBORDER + TEXTBOXHEIGHT + TEXTBOXSEP;
    top = bottom + ONECORRALHEIGHT;
    right = left + ONECORRALWIDTH;
    corral = NewIconCorral(NULLOBJ,
		       left, right, bottom, top,
		       0);
    SetVar(corral, SINGLECORRAL, ObjTrue);
    SetVar(corral, TOPDOWN, ObjTrue);
    SetVar(corral, NAME, NewString("Color Palette"));
    PrefixList(contents, corral);
    SetVar(corral, HELPSTRING,
    NewString("This corral shows the color palette of the data in the dataset.  This \
may be another dataset.  Right now, you cannot drag any icons \
into the corral."));
    SetVar(corral, PARENT, contents);
    SetVar(corral, REPOBJ, object);
    SetMethod(corral, DROPINCONTENTS, DropInPaletteCorral);

    /*Create the source text box*/
    textBox = NewTextBox(left, right, 
			    bottom - TEXTBOXSEP - TEXTBOXHEIGHT, bottom - TEXTBOXSEP,
			    0, "Colors Text", "Color Palette");
    PrefixList(contents, textBox);
    SetVar(textBox, PARENT, contents);
    SetTextAlign(textBox, CENTERALIGN);

    /*Make a palette icon*/
    MakeVar(object, CPALETTE);
    palette = GetVar(object, CPALETTE);
    if (palette)
    {
	MakeVar(palette, NAME);
	name = GetVar(palette, NAME);
	icon = GetVar(palette, DEFAULTICON);
	if (icon)
	{
	    icon = NewObject(icon, 0);
	    SetVar(icon, NAME, name);
	}
	else
	{
	    icon = NewIcon(0, 0, ICONQUESTION, GetString(name));
	}
	SetVar(icon, ICONLOC, NULLOBJ);
	SetVar(icon, REPOBJ, palette);
	SetVar(icon, CORRAL, corral);
	DropIconInCorral(corral, icon);
    }

    /*Make a show info button*/
    button = NewFunctionButton((WinInfoPtr) GetVar(contents, OWNERWINDOW),
	    left, right, 
	    MINORBORDER, MINORBORDER + BUTTONHEIGHT, OF_SHOW_CONTROLS); 
    if (button)
    {
	SetVar(button, PARENT, contents);
	SetVar(button, STICKINESS, NewInt(STICKYBOTTOM + FLOATINGRIGHT + STICKYLEFT));
	PrefixList(contents, button);
    }

    return ObjTrue;
}

#ifdef PROTO
static int AddControlButton(ObjPtr object, WinInfoPtr controlWindow, ObjPtr controlField, ObjPtr panel, int top)
#else
static int AddControlButton(object, controlWindow, controlField, panel, top)
ObjPtr object;
WinInfoPtr controlWindow;
ObjPtr controlField;
ObjPtr panel;
int top;
#endif
/*Adds a number of control buttons from object into contents in window starting at
  top.  Returns a new top.*/
{
    ObjPtr curObj;
    ObjPtr icon;
    ObjPtr contents;
    Bool firstTime = true;

    contents = GetVar(controlField, CONTENTS);

    /*Fill the control field up with buttons*/
    MakeVar(object, CONTROLICON);

    curObj = object;
    icon = NULLOBJ;
    while (curObj)
    {
	if (!icon)
	{
	    icon = Get1Var(curObj, CONTROLICON);
	}
	if (icon)
	{
	    ObjPtr button;
	    ObjPtr panelContents;
	    FuncTyp method;
	    int whichIcon;
	    char *name;
	    ObjPtr var;

	    /*Give the button a chance to add controls*/
	    method = Get1Method(curObj, ADDCONTROLS);
	    if (method)
	    {
		var = GetIntVar("ShowVisControls", icon, WHICHICON);
		if (var)
		{
		    whichIcon = GetInt(var);
		}
		else
		{
		    whichIcon = ICONQUESTION;
		}

		var = GetStringVar("ShowVisControls", icon, NAME);
		if (var)
		{
		    name = GetString(var);
		}
		else
		{
		    name = "Unknown";
		}

		button = NewIconLabeledButton(0, CWINCCWIDTH, top - CWINICONBUTHEIGHT, top,
			whichIcon, UIYELLOW, name, BS_PITTED);
		SetMethod(button, ICONEXTRADRAW, GetMethod(icon, ICONEXTRADRAW));

		SetMethod(button, CHANGEDVALUE, ChangeControlPanelButton);

		/*Make a new panel contents just for this button*/
		panelContents = NewList();
		PrefixList(panelContents, controlField);
		SetVar(panelContents, PARENT, panel);
		SetVar(button, PANELCONTENTS, panelContents);
		SetVar(button, PANEL, panel);
		SetVar(panelContents, OWNERWINDOW, (ObjPtr) controlWindow);

		icon = NULLOBJ;
		(*method)(object, panelContents);
		PostfixList(contents, button);
		SetVar(button, PARENT, controlField);
		top -= CWINICONBUTHEIGHT + MINORBORDER;

		if (firstTime)
		{
		    ObjPtr mainDataset, notes;


		    /*Do the notes*/
		    notes = GetVar(object, NOTES);
		    if (notes)
		    {
			ObjPtr infoField;
			ThingListPtr runner;
		
			runner = LISTOF(notes);
			while (runner)
			{
			    int l, r, b, t;
			    char objName[200];
			    ObjPtr fieldContents;

			    name = GetString(runner -> thing);
			    runner = runner -> next;
		
			    if (runner)
			    {
				/*Make the info*/
				ObjPtr textBox;

				button = NewIconLabeledButton(0, CWINCCWIDTH, top - CWINICONBUTHEIGHT, top,
					    ICONNOTE, UIYELLOW, name, BS_PITTED);
		    
				SetMethod(button, CHANGEDVALUE, ChangeControlPanelButton);
		    
				/*Make a new panel contents just for this button*/
				panelContents = NewList();
				PrefixList(panelContents, controlField);
				SetVar(panelContents, PARENT, panel);
				SetVar(button, PANELCONTENTS, panelContents);
				SetVar(button, PARENT, panelContents);
				SetVar(button, PANEL, panel);
				SetVar(panelContents, OWNERWINDOW, (ObjPtr) controlWindow);
	
				strcpy(objName, name);
				strcat(objName, " info field");
				infoField = NewControlField(MINORBORDER, 
					    CWINWIDTH - CORRALBORDER - CWINCORRALWIDTH - MINORBORDER,
					    MINORBORDER, CWINHEIGHT - MINORBORDER,
					    objName, OBJECTSFROMTOP | BARRIGHT);	
				PrefixList(panelContents, infoField);
				SetVar(infoField, PARENT, panel);
				SetVar(infoField, BACKGROUND, NewInt(UIGRAY75));
				SetVar(infoField, BORDERTYPE, NewInt(1));
				fieldContents = GetVar(infoField, CONTENTS);
    
				PostfixList(contents, button);
				SetVar(button, PARENT, controlField);
				top -= CWINICONBUTHEIGHT + MINORBORDER;
    
				/*Now make the text box inside the window*/
				Get2DIntBounds(infoField, &l, &r, &b, &t);
				strcpy(objName, name);
				strcat(objName, " info text");
				textBox = NewTextBox(MINORBORDER, r - l - 2 * MINORBORDER,
					     -MINORBORDER - 100, /*Will change later*/
					     -MINORBORDER,
					     0, objName, GetString(runner -> thing));
				Set2DIntBounds(textBox,
					    MINORBORDER, r - l - 2 * MINORBORDER,
					    -MINORBORDER - TextHeight(textBox),
					    -MINORBORDER);
				PrefixList(fieldContents, textBox);
				SetVar(textBox, PARENT, infoField);
				RecalcScroll(infoField);

				runner = runner -> next;
			    }
			}
		    }

		    firstTime = false;
		    mainDataset = GetVar(object, MAINDATASET);
		    if (mainDataset)
		    {
			top = AddControlButton(mainDataset, controlWindow, controlField, panel, top);
		    }
		}
	    }
	}

	curObj = ClassOf(curObj);
    }

    return top;
}

static ObjPtr ShowDatasetControls(object, windowName)
ObjPtr object;
char *windowName;
/*Makes a new control window to control dataset object*/
{
    WinInfoPtr controlWindow;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr controlField;
    ObjPtr contents;
    int left, right, bottom, top, width;
    WinInfoPtr dialogExists;
    ObjPtr icon;

    dialogExists = DialogExists((WinInfoPtr) object, NewString("Controls"));
    controlWindow = GetDialog((WinInfoPtr) object, NewString("Controls"), windowName, 
	CWINWIDTH, CWINHEIGHT, CWINWIDTH, CWINHEIGHT, WINFIXEDSIZE + WINDBUF);
    
    if (!dialogExists)
    {
	SetVar((ObjPtr) controlWindow, REPOBJ, object);

	/*Add in help string*/
	SetVar((ObjPtr) controlWindow, HELPSTRING,
	    NewString("This window shows controls for a dataset object.  \
At the right is an icon corral showing a series of icons.  Each icon represents a \
set of attributes of the visualization object.  On the left are the controls for \
the selected set of attributes.  \
Use Help In Context and click on the various controls to find out what they do.  \
Click on a different icon to choose a different set of attributes."));

	/*Add in a panel*/
	panel = NewPanel(greyPanelClass, 0, CWINWIDTH, 0, CWINHEIGHT);
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);

	contents = GetVar(panel, CONTENTS);

	/*Add in a control field*/
	controlField = NewControlField(CWINWIDTH - CORRALBORDER - CWINCORRALWIDTH,
			       CWINWIDTH - CORRALBORDER,
			       CORRALBORDER,
			       CWINHEIGHT - CORRALBORDER,
				"Dataset Object Attributes", OBJECTSFROMTOP | BARRIGHT);
	SetVar(controlField, HELPSTRING,
		NewString("This icon button group shows sets of attributes of the dataset \
object.  The left side of the panel shows controls for the \
attribute given by the selected icon button.  To show another set of \
attributes, press another button."));
	SetVar(controlField, PARENT, panel);
	PrefixList(contents, controlField);

	contents = GetVar(controlField, CONTENTS);

	top = -MAJORBORDER;
	AddControlButton(object, controlWindow, controlField, panel, top);

	/*Adjust the scroll bars*/
	RecalcScroll(controlField);

	if (LISTOF(contents))
	{
	    SetValue(LISTOF(contents) -> thing, NewInt(1));
	    SetVar(controlField, BUTTON, LISTOF(contents) -> thing);
	}
    }

    return (ObjPtr) controlWindow;
}

ObjPtr FindDatasetByName(name)
ObjPtr name;
/*Finds a dataset by its name, returns it or nothing*/
{
    ThingListPtr runner;
    runner = LISTOF(allDatasets);
    while (runner)
    {
	if (Eql(GetVar(runner -> thing, NAME), name))
	{
	    return runner -> thing;
	}
	runner = runner -> next;
    }
    return NULLOBJ;
}

#ifdef PROTO
Bool InsertDatasetTimeStep(ObjPtr masterDataset, ObjPtr timeSlice, real time)
#else
Bool InsertDatasetTimeStep(masterDataset, timeSlice, time)
ObjPtr masterDataset, timeSlice;
real time;
#endif
/*Inserts a dataset time step into a master dataset*/
{
    /*Add on to an existing dataset*/
    InsertTimeSlice(GetVar(masterDataset, DATA), NewReal(time), GetVar(timeSlice, DATA));

    /*Register the dataset as changed*/
    DatasetChanged(masterDataset);
    return true;
}

#ifdef PROTO
Bool RegisterTimeDataset(ObjPtr dataset, real time)
#else
Bool RegisterTimeDataset(dataset, time)
ObjPtr dataset;
real time;
#endif
/*Registers a timeslice dataset, creating a new one or appending on to an old one
  based on time*/
{
    ObjPtr var;
    ObjPtr priorDataset;

    /*Find out if there already exists a time object like this*/
    var = GetVar(dataset, NAME);
    priorDataset = FindDatasetByName(var);
    if (priorDataset)
    {
	InsertDatasetTimeStep(priorDataset, dataset, time);
	/*Make the prior dataset the dataset in use for the icon test*/
	dataset = priorDataset;
    }
    else
    {
	ObjPtr timeSteps, timeData;
	/*Make a new dataset*/

	timeSteps = NewList();
	timeData = NewList();
	PrefixList(timeSteps, NewReal(time));
	PrefixList(timeData, GetVar(dataset, DATA));

	SetVar(dataset, DATA, NewTimedObject(timeSteps, timeData));
	RegisterNewDataset(dataset);
    }
    return true;
}

#ifdef PROTO
void SetDatasetTimeFormat(ObjPtr dataset, int format)
#else
void SetDatasetTimeFormat(dataset, format)
ObjPtr dataset;
int format;
#endif
/*Sets a dataset's time format*/
{
    SetVar(dataset, TIMEFORMAT, NewInt(format));
}

#ifdef PROTO
Bool RegisterNewDataset(ObjPtr dataset)
#else
Bool RegisterNewDataset(dataset)
ObjPtr dataset;
#endif
/*Registers a brand new dataset, doesn't look for name or anything*/
{
    ObjPtr corral, contents;
    ThingListPtr runner;
    WinInfoPtr datasetsWindow;
    int k;
    char *name;
    ObjPtr icon;
    ObjPtr defaultIcon;
    ObjPtr var;

    datasetsWindow = DatasetsWindow();
    PopWindow(datasetsWindow);

    IdleAllWindows();

    LongOperation();

    /*Tell dataset about its filename and directory*/
    if (curFileName)
    {
	SetVar(dataset, FILENAME, NewString(curFileName));
    }
    getcwd(tempStr, TEMPSTRSIZE);
    SetVar(dataset, DIRECTORY, NewString(tempStr));


    corral = GetObjectVar("RegisterNewDataset", (ObjPtr) datasetsWindow, CORRAL);
    var = GetStringVar("RegisterNewDataset", dataset, NAME);
    if (!var) return false;
    name = GetString(var);

    k = 1;
    while (FindDatasetByName(var))
    {
	++k;
	sprintf(tempStr, "%s (%d)", name, k);
	var = NewString(tempStr); 
    }
    if (k > 1)
    {
	name = GetString(var);
	SetVar(dataset, NAME, var);
    }

    /*Search for an icon*/
    contents = GetListVar("RegisterNewDataset", corral, CONTENTS);
    if (!contents) return false;
    runner = LISTOF(contents);
    while (runner)
    {
	ObjPtr repObj;
	repObj = GetVar(runner -> thing, REPOBJ);
	if (repObj == dataset)
	{
	    break;
	}
	runner = runner -> next;
    }

    if (!runner)
    {
	/*Must create a new icon*/
	defaultIcon = GetVar(dataset, DEFAULTICON);
	if (defaultIcon)
	{
	    icon = NewObject(defaultIcon, 0);
	    SetVar(icon, NAME, NewString(name));
	}
	else
	{
	    icon = NewIcon(0, 0, ICONQUESTION, name);
	    SetVar(icon, HELPSTRING,
		NewString("This icon represents a dataset.  You can select it and use the controls \
in this window to modify or visualize it, or you can drag it into the \
corral of an existing visualization window to visualize it there."));

	}
	SetVar(icon, REPOBJ, dataset);
	SetVar(icon, CORRAL, corral);
	SetVar(icon, ICONLOC, dsLocArray);
	dsLocArray = NULLOBJ;
	DropIconInCorral(corral, icon);
    }

    PrefixList(allDatasets, dataset);
    return true;
}

Bool RegisterDataset(dataset)
ObjPtr dataset;
/*Registers dataset as a dataset*/
{
    ObjPtr var;
    char *name;
    char *sPtr;

    var = GetStringVar("RegisteDataset", dataset, NAME);
    if (!var) return false;
    name = GetString(var);

    if (timedDatasets)
    {
	/*See if there is a time name*/
	sPtr = name;
	while (*sPtr)
	{
	    if (*sPtr == '@')
	    {
		/*Yes, it's a time slice!*/
		strncpy(tempStr, name, TEMPSTRSIZE);
		if (sPtr - name < TEMPSTRSIZE)
		{
		    tempStr[sPtr - name] = 0;
		}
		else
		{
		    tempStr[TEMPSTRSIZE] = 0;
		}
    
		/*Edit the dataset's name*/
		var = NewString(tempStr);
		SetVar(dataset, NAME, var);
    
		/*Copy the time*/
		strncpy(tempStr, sPtr + 1, TEMPSTRSIZE);
		tempStr[TEMPSTRSIZE] = 0;
    
		/*Get the edited name back into name*/
		name = GetString(var);
		break;
	    }
	    ++sPtr;
	}
    
	if (*sPtr == '@')
	{
	    /*It must be a time slice*/
	    real time;
	    int format;
    
	    /*Go through and parse the time*/
	    if (ParseTime(&time, &format, tempStr) <= 0)
	    {
		ReportError("RegisterDataset","Bad time format");
	    }
	    SetDatasetTimeFormat(dataset, format);
	    return RegisterTimeDataset(dataset, time);
	}
	else
	{
	    return RegisterNewDataset(dataset);
	}
    }
    else
    {
	return RegisterNewDataset(dataset);
    }
}

void ReportFileFormatError()
/*Task to report a bad read file error*/
{
    WinInfoPtr errWindow;
    errWindow = AlertUser(UIERRORALERT, DatasetsWindow(),
	"SciAn can only read data files with defined formats.  Set the file formats with Set Format and try again.",
	0, 0, "Yeah, right.");
    SetVar((ObjPtr) errWindow, HELPSTRING,
	NewString("You have tried to open a file cannot be opened because it \
is not a SciAn data file or its file format has not been set.  If you know what \
the proper format is, you can select the icon representing the file and press the \
Set Format button.  You will be presented with a series of choices.  Select the \
proper format, and try this operation again."));
}

void ReportDirError()
/*Task to report a directory tried to drag into datasets*/
{
    WinInfoPtr errWindow;
    errWindow = AlertUser(UIERRORALERT, DatasetsWindow(),
	"Directories cannot be opened as datasets.",
	0, 0, "Yeah, right.");
    SetVar((ObjPtr) errWindow, HELPSTRING,
	NewString("You have tried to open an entire directory as a dataset.  \
This does not work.  If you want to open all the files contained in the \
directory, open the directory, select all the files, and open them."));
}

static ObjPtr DropInDatasetsCorral(corral, icon, x, y)
ObjPtr corral, icon;
int x, y;
/*Drops an icon in a vis corral*/
{
    ObjPtr repObj;
    repObj = GetVar(icon, REPOBJ);
    if (repObj && IsFile(repObj))
    {
	ObjPtr fileName;		/*Name of the file*/
	ObjPtr fileType;		/*Type of the file*/
	ObjPtr fileReader;		/*Reader of the file*/
	FuncTyp method;
	real loc[2];
	loc[0] = x;
	loc[1] = y;

	fileName = GetVar(repObj, NAME);
	fileType = GetVar(repObj, FILETYPE);
	fileReader = GetVar(repObj, READER);

	if (GetInt(fileType) == DIRFILE)
	{
	    DoUniqueTask(ReportDirError);
	    return ObjFalse;
	}
	if (!fileName || !fileReader)
	{
	    DoUniqueTask(ReportFileFormatError);
	    return ObjFalse;
	}
	dsLocArray = NewRealArray(1, 2L);
	CArray2Array(dsLocArray, loc);

	method = GetMethod(repObj, OPEN);
	if (method)
	{
	    (*method)(repObj);
	}
	dsLocArray = NULLOBJ;
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

WinInfoPtr NewDatasetsWindow(void)
/*Create a new Datasets window*/
{
    WinInfoPtr objWin;
    ObjPtr panel, contents, corral, button;
    int l, r, b, t;
    int bw;

    /*Create the window*/
    objWin = NewObjWindow(NULLOBJ, "Datasets", WINDBUF, DSWINWIDTH, DSWINHEIGHT, SCRWIDTH, SCRHEIGHT);
    ContentsExpectWindowSize(objWin, DSWINWIDTH, DSWINHEIGHT);
    l = 0; r = DSWINWIDTH; b = 0; t = DSWINHEIGHT;

    /*Set a null but successful HIDE routine*/
    SetMethod((ObjPtr) objWin, HIDE, HideDatasetWindow);

    /*Add a help string*/
    SetVar((ObjPtr) objWin, HELPSTRING, 
	NewString("This window shows all the datasets that have been opened in \
Scian."));

    /*Put in a panel*/
    panel = NewPanel(greyPanelClass, 0, DSWINWIDTH, 0, DSWINHEIGHT);
    SetVar(panel, STICKINESS, NewInt(STICKYLEFT + STICKYRIGHT +
				     STICKYBOTTOM + STICKYTOP));

    contents = GetVar((ObjPtr) objWin, CONTENTS);
    PrefixList(contents, panel);
    SetVar(panel, PARENT, (ObjPtr) objWin);

    /*Put in buttons and an icon corral*/
    contents = GetListVar("NewDatasetsWindow", panel, CONTENTS);
    if (!contents)
    {
	return 0;
    }
    /*Make an icon corral*/
    corral = NewIconCorral(NULLOBJ, l + MINORBORDER, r - MINORBORDER, b + 3 * MINORBORDER + 2 * BUTTONHEIGHT, t - MINORBORDER, BARRIGHT + BARBOTTOM);
    SetVar(corral, STICKINESS, NewInt(STICKYLEFT + STICKYRIGHT +
				     STICKYBOTTOM + STICKYTOP));
    SetVar(corral, TOPDOWN, ObjTrue);
    SetVar((ObjPtr) objWin, CORRAL, corral);
    SetVar(corral, NAME, NewString("Datasets Corral"));
    SetVar(corral, HELPSTRING,
	NewString("This corral contains icons for all the datasets that have \
been read into SciAn.  You can visualize, show the controls of, or modify the datasets by selecting \
some of them and pressing the buttons at the bottom of the window.  You can delete \
datasets by choosing Delete from the Object menu.  You can also open new datasets by dragging icons from \
the file window into this corral."));
    SetMethod(corral, DROPINCONTENTS, DropInDatasetsCorral);
    PrefixList(contents, corral);
    SetVar(corral, PARENT, panel);

    l += MINORBORDER;
    r -= MINORBORDER;
    b += 2 * MINORBORDER + BUTTONHEIGHT;
    t = b + BUTTONHEIGHT;
    bw = (r - l - MINORBORDER) / 2;

    /*Make a visualize button*/
    button = NewFunctionButton(objWin,
		l, l + bw,
		b, b + BUTTONHEIGHT, OF_VISUALIZE); 
    if (button)
    {
	SetVar(button, PARENT, panel);
	SetVar(button, STICKINESS, NewInt(STICKYBOTTOM + STICKYLEFT + FLOATINGRIGHT));
	PrefixList(contents, button);
    }

    /*Make a visualize as... button*/
    button = NewFunctionButton(objWin,
		r - bw, r,
		b, b + BUTTONHEIGHT, OF_VISUALIZE_AS); 
    if (button)
    {
	SetVar(button, PARENT, panel);
	SetVar(button, STICKINESS, NewInt(STICKYBOTTOM + FLOATINGLEFT + STICKYRIGHT));
	PrefixList(contents, button);
    }

    t = b - MINORBORDER;
    b = t - BUTTONHEIGHT;
    /*Make a show info button*/
    button = NewFunctionButton(objWin,
		l, l + bw, 
		b, b + BUTTONHEIGHT, OF_SHOW_CONTROLS); 
    if (button)
    {
	SetVar(button, PARENT, panel);
	SetVar(button, STICKINESS, NewInt(STICKYBOTTOM + STICKYLEFT + FLOATINGRIGHT));
	PrefixList(contents, button);
    }

    /*Make a modify button*/
    button = NewFunctionButton(objWin, 
		r - bw, r,
		b, b + BUTTONHEIGHT, OF_MODIFY);
    if (button)
    {
	SetVar(button, PARENT, panel);
	SetVar(button, STICKINESS, NewInt(STICKYBOTTOM + FLOATINGLEFT + STICKYRIGHT));
	PrefixList(contents, button);
    }

    return objWin;
}

WinInfoPtr DatasetsWindow()
/*Returns or creates a datasets window*/
{
    WinInfoPtr retVal;

    retVal = GetWinFromTitle("Datasets");
    if (!retVal)
    {
	retVal = NewDatasetsWindow();
    }
    return retVal;
}

static ObjPtr GetDataMinMax(object)
ObjPtr object;
/*Gets and returns the min and max of the data in object for a dataset*/
{
    ObjPtr retVal;
    retVal = GetVar(object, MINMAX);
    if (retVal && IsRealArray(retVal) && RANK(retVal) == 1 && DIMS(retVal)[0] == 2)
    {
	return retVal;
    }
    else
    {
	if (GetDatasetInfo(object) &  (DS_HASGEOMETRY | DS_HASNEWGEOMETRY))
	{
	    real minmax[2];
	    minmax[0] = 0.0;
	    minmax[1] = 1.0;
	    retVal = NewRealArray(1, 2L);
	    CArray2Array(retVal, minmax);
	    SetVar(object, MINMAX, retVal);
	    return retVal;
	}
	else
	{
	    /*Must be a field*/
	    int nTraversalDims;		/*Number of dimensions to traverse*/
	    int whichDim;		/*Dimension counter*/
	    long *traversalDims = 0;	/*Dimensions of the dataset to traverse*/
	    long *index = 0;		/*Counting index*/
	    real sample;
	    real minmax[2];
	    FuncTyp tempMethod;

	    tempMethod = GetMethod(object, MINMAX);
	    SetMethod(object, MINMAX, (FuncTyp) 0);

	    SetCurField(FIELD5, object);

	    /*Get the information on traversing the dataset*/
	    nTraversalDims = CountTraversalDims(FIELD5);
	    if (nTraversalDims)
	    {
		traversalDims = (long *) malloc(sizeof(long) * nTraversalDims);
		index = (long *) malloc(sizeof(long) * nTraversalDims);
	    }
	    else
	    {
		index = (long *) malloc(sizeof(long));
	    }
	    GetTraversalDims(FIELD5, traversalDims);

	    /*Zero the index*/
	    for (whichDim = 0; whichDim < nTraversalDims; ++whichDim)
	    {
		index[whichDim] = 0;
	    }

	    minmax[0] = minmax[1] = missingData;

	    /*Traverse all the points*/
	    do
	    {
		/*Sample the location at a point*/
		sample = SelectFieldScalar(FIELD5, index);
		if (sample != missingData)
		{
		    if (minmax[0] == missingData)
		    {
			minmax[0] = sample;
		    }
		    else
		    {
			minmax[0] = MIN(minmax[0], sample);
		    }
		    if (minmax[1] == missingData)
		    {
			minmax[1] = sample;
		    }
		    else
		    {
			minmax[1] = MAX(minmax[1], sample);
		    }
		}

		/*Advance to next point*/
		for (whichDim = 0; whichDim < nTraversalDims; ++whichDim)
		{
		    if (traversalDims[whichDim] > 0)
		    {
			if ((++index[whichDim]) >= traversalDims[whichDim])
			{
			    index[whichDim] = 0;
			}
		        else
			{
			    break;
			}
		    }
		}
	    } while (whichDim < nTraversalDims); /*Break is based on advance*/

	    /*Free up temporary storage*/
	    SAFEFREE(traversalDims);
	    SAFEFREE(index);

	    retVal = NewRealArray(1, 2L);
	    CArray2Array(retVal, minmax);
	    SetVar(object, MINMAX, retVal);

	    SetMethod(object, MINMAX, tempMethod);
	    return retVal;
	}
    }
}

static ObjPtr MakeDatasetPalette(object)
ObjPtr object;
/*Gets a palette for a dataset object*/
{
    ObjPtr palette;
    palette = GetVar(object, CPALETTE);
    if (!palette)
    {
	ObjPtr minMax, directory, name;
	real min, max, *elements;
	ObjPtr var;

	MakeVar(object, MINMAX);
	minMax = GetVar(object, MINMAX);
	if (minMax)
	{
	    elements = ELEMENTS(minMax);
	    min = elements[0];
	    max = elements[1];
	}
	else
	{
	    min = 0.0;
	    max = 1.0;
	}

	if (onePalette)
	{
	    if (commonPalette)
	    {
		min = MIN(min, ((PPtr) commonPalette) -> min);
		max = MAX(max, ((PPtr) commonPalette) -> max);
		palette = commonPalette;
	    }
	}
	if (!palette)
	{
	    var = GetVar(object, UNITSNAME);
	    if (var)
	    {
		palette = UnitsNameToPalette(GetString(var), min, max);
	    }
	    else
	    {
		palette = UnitsNameToPalette((char *) 0, min, max);
	    }
	    FieldPaletteName(palette, object);
	}

	/*If there is a saved palette, use it.*/
	directory = GetVar(object, DIRECTORY);
	if (directory)
	{
	    ReadObjectControls(palette, directory);
	}

	SetVar(object, CPALETTE, palette);
	if (onePalette)
	{
	    commonPalette = palette;
	}
    }
    return ObjTrue;
}

static ObjPtr GetPlainDatasetInfo(dataset)
ObjPtr dataset;
/*Returns a sum of flags defined in ScianDatasets.h giving info on the dataset*/
{
    long retVal;
    ObjPtr data, form, var;

    retVal = 0;
    if (form = GetVar(dataset, DATAFORM))
    {
	retVal |= DS_HASFORM;
	if (GetPredicate(form, UNSTRUCTURED))
	{
	    retVal |= DS_UNSTRUCTURED;
	}
    }
    if (data = GetVar(dataset, DATA))
    {
	if (IsTimedObj(data))
	{
	    ObjPtr timeData;

	    retVal |= DS_TIMEDEPENDENT;
	    timeData = GetVar(data, TIMEDATA);
	    if (timeData)
	    {
		long dimension = 0;
		data = GetObjectElement(timeData, &dimension);
		if (IsPicture(data))
		{
		    retVal |= DS_HASGEOMETRY;
		}
		else if (IsGeometry(data))
		{
		    retVal |= DS_HASNEWGEOMETRY;
		}
		else
		{
		    retVal |= DS_HASFIELD;
		}
	    }
	    else
	    {
		return 0;
	    }
	}
	else
	{
	    if (IsPicture(data))
	    {
		retVal |= DS_HASGEOMETRY;
	    }
	    else if (IsGeometry(data))
	    {
		retVal |= DS_HASNEWGEOMETRY;
	    }
	    else
	    {
		retVal |= DS_HASFIELD;
	    }
	}
    }
    if (var = GetVar(dataset, NCOMPONENTS))
    {
	retVal |= DS_VECTOR;
    }
    return NewInt(retVal);
}

int GetTopDim(dataset)
ObjPtr dataset;
/*Returns the topological or computational dimensionality of the dataset
  Returns 0 if it has no topological dimensions*/
{
    FuncTyp method;
    method = GetMethodSurely("GetTopDim", dataset, GETTOPDIM);
    if (method)
    {
	ObjPtr result;
	result = (*method)(dataset);
	if (result)
	{
	    return GetInt(result);
	}
	else
	{
	    ReportError("GetTopDim", "GETTOPDIM did not return dimension");
	    return 0;
	}
    }
    else
    {
	return 0;
    }
}

int GetSpatialDim(dataset)
ObjPtr dataset;
/*Returns the spatial dimensionality of the dataset
  Returns 0 if it has no topological dimensions*/
{
    FuncTyp method;
    method = GetMethodSurely("GetSpatialDim", dataset, GETSPATIALDIM);
    if (method)
    {
	ObjPtr result;
	result = (*method)(dataset);
	if (result)
	{
	    return GetInt(result);
	}
	else
	{
	    return 1;
	}
    }
    else
    {
	return 1;
    }
}

long GetDatasetInfo(dataset)
ObjPtr dataset;
/*Returns the spatial dimensionality of the dataset
  Returns 0 if it has no topological dimensions*/
{
    FuncTyp method;
    method = GetMethodSurely("GetDatasetInfo", dataset, GETDATASETINFO);
    if (method)
    {
	ObjPtr result;
	result = (*method)(dataset);
	if (result)
	{
	    return GetInt(result);
	}
	else
	{
	    ReportError("GetDatasetInfo", "No info returned from GETDATASETINFO");
	    return 0;
	}
    }
    else
    {
	return 0;
    }
}

ObjPtr GetDataFormTopDim(dataForm)
ObjPtr dataForm;
/*Returns the topological dimension of dataForm*/
{
    ObjPtr dims;

    dims = GetArrayVar("GetDataFormTopDim", dataForm, DIMENSIONS);
    if (dims)
    {
	if (GetPredicate(dataForm, UNSTRUCTURED))
	{
	    return NewInt(DIMS(dims)[0] - 1);
	}
	else
	{
	    return NewInt(DIMS(dims)[0]);
	}
    }
    else
    {
	return NewInt(0);
    }
}

static ObjPtr GetDatasetTopDim(dataset)
ObjPtr dataset;
/*Returns the topological or computational dimensionality of the dataset
  Returns 0 if it has no topological dimensions*/
{
    ObjPtr dataForm;
    FuncTyp method;

    dataForm = GetVar(dataset, DATAFORM);

    if (dataForm)
    {
	method = GetMethod(dataForm, GETTOPDIM);
	if (method)
	{
	    return (*method)(dataForm);
	}
    }
    else
    {
	ObjPtr data;
	/*No data form, must get the top dim of its DATA*/
	data = GetVar(dataset, DATA);
	if (data)
	{
	    method = GetMethod(data, GETTOPDIM);
	    if (method)
	    {
		return (*method)(data);
	    }
	}
    }
	
    return NewInt(0);
}

static ObjPtr GetDatasetSpatialDim(dataset)
ObjPtr dataset;
/*Returns the spatial dimensionality of the dataset*/
{
    ObjPtr dataForm;
    ObjPtr nComponents, bounds;

    dataForm = GetVar(dataset, DATAFORM);
    if (!dataForm)
    {
	return NewInt(0);
    }

    nComponents = GetVar(dataForm, NCOMPONENTS);
    if (nComponents)
    {
	return nComponents;
    }

    bounds = GetVar(dataForm, BOUNDS);
    if (bounds)
    {
	return NewInt(DIMS(bounds)[0] / 2);
    }
    return ObjFalse;
}

ObjPtr GetDatasetFormBounds(dataset)
ObjPtr dataset;
/*Returns the bounds of the dataset's data form*/
{
    ObjPtr dataForm;
    ObjPtr dims;
    
    dataForm = GetVar(dataset, DATAFORM);
    if (!dataForm)
    {
	return 0;
    }

    dims = GetArrayVar("GetDatasetFormBounds", dataForm, BOUNDS);
    return dims;
}

ObjPtr GetDatasetFormDims(dataset)
ObjPtr dataset;
/*Returns the dimensions of the dataset's data form*/
{
    FuncTyp method;
    method = GetMethodSurely("GetDatasetFormDims", dataset, GETFORMDIMS);

    if (method)
    {
	return (*method)(dataset);
    }
    else
    {
	return NULLOBJ;
    }
}

ObjPtr GetPlainDatasetFormDims(dataset)
ObjPtr dataset;
/*Returns the dimensions of the dataset's data form*/
{
    ObjPtr dataForm;
    ObjPtr dims;
    
    dataForm = GetVar(dataset, DATAFORM);
    if (!dataForm)
    {
	return 0;
    }

    dims = GetArrayVar("GetPlainDatasetFormDims", dataForm, DIMENSIONS);
    return dims;
}

ObjPtr GetDatasetKEdges(dataset, k)
ObjPtr dataset;
int k;
/*Returns a list of k-edges of the dataset.  Only works for unstructured 
  data forms.
*/
{
    ObjPtr dataForm;
    dataForm = GetVar(dataset, DATAFORM);
    if (!dataForm)
    {
	return NULLOBJ;
    }
    if (k == 1)
    {
	return GetVar(dataForm, EDGES);
    }
    else if (k == 2)
    {
	return GetVar(dataForm, CELLS);
    }
    else
    {
	ReportError("GetDatasetKEdges", "Edges beyond 2 are not defined");
	return NULLOBJ;
    }
}

static ObjPtr RegisterDatasetField(dataset, whichField)
ObjPtr dataset;
int whichField;
/*Registers datastet's data as whichField.*/
{
    ObjPtr data;
    ObjPtr var;
    FuncTyp method;

    MakeVar(dataset, CURDATA);
    data = GetVar(dataset, CURDATA);

#if 0
    MakeVar(dataset, MINMAX);
    var = GetVar(dataset, MINMAX);
    if (var)
    {
	real *elements;
	elements = ELEMENTS(var);
	curFields[whichField] . min = elements[0];
	curFields[whichField] . max = elements[1];
    }
#endif

    if (!data)
    {
	ReportError("RegisterDatasetField", "No CURDATA");
	return ObjFalse;
    }

    /*It's an array or vector.  Stuff it recursively*/
    method = GetMethodSurely("RegisterDatasetField", data, REGISTERFIELD);
    if (method)
    {
	return (*method)(data, whichField);
    }

    return ObjFalse;
}

int GetNComponents(whichField)
int whichField;
/*Returns the number of components of whichField*/
{
    return curFields[whichField] . nComponents;
}

int CountComponentDims(whichField, whichComponent)
int whichField, whichComponent;
/*Returns a count of whichField's component dimensions*/
{
    return curFields[whichField] . components[whichComponent] . nIndices;
}

long *GetComponentDims(whichField, whichComponent)
int whichField, whichComponent;
/*Returns a count of whichField's component dimensions*/
{
    return curFields[whichField] . components[whichComponent] . dimensions;
}

#ifdef PROTO
Bool CompareReals(real *r1, real *r2, long nReals)
#else
Bool CompareReals(r1, r2, lnReals)
real *r1;
real *r2;
long nReals;
#endif
/*Compares a bunch of reals, with any luck fast.  Uses indices, not pointers,
  to take advantage of MIPS architecture*/
{
    register long k, max;
    register real *rc1, *rc2;

    rc1 = r1;
    rc2 = r2;
    max = nReals;

    for (k = 0; k < max; ++k)
    {
	if (rc1[k] != rc2[k]) return false;
    }
    return true;
}

#ifdef PROTO
Bool IdenticalFields(int field1, int field2)
#else
Bool IdenticalFields(field1, field2)
int field1, field2;
#endif
/*Returns true if the two fields are identical.  Tries to be quick about it.
  May return false falsely.
*/
{
    register int k;

    if (curFields[field1] . topDim !=
	curFields[field2] . topDim) return false;
    if (curFields[field1] . nComponents !=
	curFields[field2] . nComponents) return false;

    /*Compare gross quantities*/
    if (curFields[field1] . fieldObj == curFields[field2] . fieldObj)
    {
	return true;
    }

    for (k = 0; k < curFields[field1] . nComponents; ++k)
    {
	register Component *comp1, *comp2;
	register int i;

	comp1 = &(curFields[field1] . components[k]);
	comp2 = &(curFields[field2] . components[k]);

	if (comp1 -> dataSize != comp2 -> dataSize) return false;
	if (comp1 -> dataCompressed || comp2 -> dataCompressed)
	{
	    fprintf(stderr, "WARNING!  Can't do IdenticalFields on compressed grids\n");
	    return false;
	}
	else if (!CompareReals(comp1 -> data . unComp, comp2 -> data . unComp, comp1 -> dataSize))
	{
	    return false;
	}
    }

    /*Have to assume they're identical*/
    return true;
}

#ifdef PROTO
int CountTraversalDims(int whichField)
#else
int CountTraversalDims(whichField)
int whichField;
#endif
/*Counts the traversal dimensions in whichField*/
{
    int k, curIndex;
    int maxDim = 0, temp;

    for (k = 0; k < curFields[whichField] . nComponents; ++k)
    {
	temp = MaxComponentIndices(whichField, k);
	if (temp > maxDim) maxDim = temp;
    }
    return maxDim;
}

#ifdef PROTO
void GetTraversalDims(int whichField, long dims[])
#else
void GetTraversalDims(whichField, dims)
int whichField;
long dims[];
#endif
/*Gets the traversal dims in whichField into dims, which had better be big enough*/
{
    int whichComponent, whichIndex, index, indIndex;
    int nIndices;

    nIndices = CountTraversalDims(whichField);

    for (indIndex = 0; indIndex < nIndices; ++indIndex)
    {
	dims[indIndex] = -1;
    }

    for (whichComponent = 0; whichComponent < curFields[whichField] . nComponents; ++whichComponent)
    {
	for (whichIndex = 0; whichIndex < curFields[whichField] . components[whichComponent] . nIndices; ++whichIndex)
	{
	    indIndex = curFields[whichField] . components[whichComponent] . indices[whichIndex];
	    if (indIndex >= 0)
	    {
		dims[indIndex] = curFields[whichField] . components[whichComponent] . dimensions[whichIndex];
	    }
	}
    }
}

static ObjPtr RegisterDataFormField(form, whichField)
ObjPtr form;
int whichField;
/*Registers data form form in field whichField*/
{
    ObjPtr dataset;			/*Dataset that may define this dataform*/

    MakeVar(form, DATA);
    dataset = GetVar(form, DATA);
    if (dataset)
    {
	Bool test;
	/*It's based on a dataset*/

	MakeVar(dataset, CURDATA);
	test = SetCurField(whichField, dataset);
	curFields[whichField] . topDim = GetTopDim(form);
	return test ? ObjTrue : ObjFalse;
    }

    if (GetPredicate(form, UNSTRUCTURED))
    {
	/*Unstructured grid.  This is an error, as all unstructured grids
	  must have a dataset in their form*/
	ReportError("RegisterDataFormField", "Unstructured grid without data");
    }
    else
    {
	ObjPtr dimsArray, boundsArray;
	real *dimsPtr, *boundsPtr;
	Component *components;
	int whichComponent;
	long topDim;
	long dataSize;
	Bool omErr = false;		/*True if om error occurred*/

	dimsArray = GetArrayVar("RegisterDataFormField", form, DIMENSIONS);
	boundsArray = GetArrayVar("RegisterDataFormField", form, BOUNDS);
	if (!dimsArray || !boundsArray)
	{
	    return ObjFalse;
	}

	topDim = DIMS(dimsArray)[0];

	/*Topological dimension is now topDim  Create that many components*/
	components = (Component *) malloc(sizeof(Component) * topDim);

	dimsPtr = ELEMENTS(dimsArray);
	boundsPtr = ELEMENTS(boundsArray);

	for (whichComponent = 0; whichComponent < topDim; ++whichComponent)
	{
	    int *indices = 0;
	    long *dimensions = 0;
	    long *steps = 0;
	    ObjPtr dataArray;

	    /*Set up one index, the current component*/
	    components[whichComponent] . nIndices = 1;
	    indices = malloc(sizeof(int));
	    if (indices)
	    {
		*indices = whichComponent;
	    }
	    else
	    {
		omErr = true;
		OMErr();
	    }

	    /*One set of dimensions*/
	    dimensions = malloc(sizeof(int));
	    if (dimensions)
	    {
		*dimensions = *dimsPtr;
	    }
	    else
	    {
		omErr = true;
		OMErr();
	    }

	    /*One step, unity*/
	    steps = malloc(sizeof(int));
	    if (steps)
	    {
		*steps = 1;
	    }
	    else
	    {
		omErr = true;
		OMErr();
	    }

	    /*Fill in a data array*/
	    dataArray = NewRealArray(1, (long) *dimsPtr);
	    if (dataArray)
	    {
		real min, max, *dp;
		long k;
		dp = ELEMENTS(dataArray);
		min = *boundsPtr;
		max = *(boundsPtr + 1);

		if (*dimsPtr < 1.5)
		{
		    *dp = (min + max) * 0.5;
		}
		else for (k = 0; k < (long) *dimsPtr; ++k)
		{
		    *dp = min + (max - min) * (k / (*dimsPtr - 1));
		    ++dp;
		}
	    }
	    else
	    {
		omErr = true;
		OMErr();
	    }
	    components[whichComponent] . flags = 0;
	    components[whichComponent] . data . unComp = ELEMENTS(dataArray);
	    components[whichComponent] . dataCompressed = false;
	    components[whichComponent] . dataSize = (long) *dimsPtr;
	    components[whichComponent] . indices = indices;
	    components[whichComponent] . dimensions = dimensions;
	    components[whichComponent] . steps = steps;

	    ++dimsPtr;
	    boundsPtr += 2;
	}
	curFields[whichField] . components = components;
	curFields[whichField] . nComponents = topDim;
    }
    return ObjTrue;
}

static ObjPtr DeleteDatasetIcon(icon)
ObjPtr icon;
/*Delete repObj from dataset icon within a corral.  Always OK.*/
{
    DeleteFromList(allDatasets, GetVar(icon, REPOBJ));
    return ObjTrue;
}

static ObjPtr CleanupDataset(object)
ObjPtr object;
/*Cleans up a dataset*/
{
    return ObjTrue;
}

static ObjPtr MakeDatasetIconHelp(object, class)
ObjPtr object;
ObjPtr class;
/*Makes a dataset icon help string*/
{
    long info;
    long topDim;
    ObjPtr repObj;
    char *s;

    repObj = GetVar(object, REPOBJ);
    if (!repObj)
    {
	SetVar(class, HELPSTRING,
	   NewString("This icon is supposed to represent a dataset.  Unfortunately, \
it does not appear to be connected to anything right now.  See the section on Reporting Bugs \
to find out how to report this bug."));
	return ObjTrue;
    }
    info = GetDatasetInfo(repObj);
    if (info & DS_HASFORM)
    {
	topDim = GetTopDim(repObj);
    }
    else
    {
	topDim = 0;
    }
    s = tempStr;
    sprintf(s,
	"This icon represents a dataset, which contains");
    while (*s) ++s;
    if (info & (DS_HASGEOMETRY | DS_HASNEWGEOMETRY))
    {
	sprintf(s, "%s geometry.  ",
	    info & DS_TIMEDEPENDENT ? " time-dependent" : "");
    }
    else
    {
	if (topDim)
	{
	    sprintf(s, " a %d-D%s %s field.  ",
		topDim,
		info & DS_TIMEDEPENDENT ? " time-dependent" : "",
		info & DS_VECTOR ? "vector" : "scalar");
 	}
	else
	{
	    sprintf(s, " a%s %s field.  ",
		info & DS_TIMEDEPENDENT ? " time-dependent" : "",
		info & DS_VECTOR ? "vector" : "scalar");
	}
    }
    while (*s) ++s;
    strcpy(s, "You can select it and use the controls \
in this window to modify or visualize it, or you can drag it into the \
corral of an existing visualization window to visualize it there.");

    SetVar(class, HELPSTRING, NewString(tempStr));
    return ObjTrue;
}

ObjPtr DatasetPaletteSet(dataset)
ObjPtr dataset;
/*Sets PALETTESET on dataset*/
{
    SetVar(dataset, PALETTESET, ObjTrue);
    return ObjTrue;
}

static ObjPtr VisualizeDataset(object)
ObjPtr object;
/*Visualizes a single dataset in the current window*/
{
    if (object)
    {
	IdleAllWindows();
	AddObjToSpace(object, FindSpace(selWinInfo), GetVar((ObjPtr) selWinInfo, CORRAL), NULLOBJ, NULLOBJ);
	return ObjTrue;
    }
    return ObjFalse;
}

ObjPtr VisualizeDatasetAs(object)
ObjPtr object;
/*Visualizes an object as...*/
{
    AddToVisualizeAsList(object);
    return ObjTrue;
}

ObjPtr MakeDatasetControlIcon(dataset)
ObjPtr dataset;
/*Makes a dataset's control icon*/
{
    SetVar(dataset, CONTROLICON, GetVar(dataset, DEFAULTICON));
    return ObjTrue;
}

static ObjPtr MakeCurDataForm(dataset)
ObjPtr dataset;
/*Makes a dataset's or a data form's CURDATAFORM dummy variable*/
{
    SetVar(dataset, CURDATAFORM, ObjTrue);
    return ObjTrue;
}

void InitDatasets()
/*Initializes all the kinds of data sets*/
{
    int k;
    for (k = 0; k < MAXNCURFIELDS; ++k)
    {
	curFields[k] . nComponents = 0;
	curFields[k] . components = (Component *) 0;
    }

    iconDataset = NewIcon(0, 0, ICONQUESTION, "Dataset");
    AddToReferenceList(iconDataset);
    SetMethod(iconDataset, MAKE1HELPSTRING, MakeDatasetIconHelp);
    SetMethod(iconDataset, DELETEICON, DeleteDatasetIcon);

    icon4DScalar = NewObject(iconDataset, 0);
    SetVar(icon4DScalar, WHICHICON, NewInt(ICON4DSCALAR));
    AddToReferenceList(icon4DScalar);

    icon3DScalar = NewObject(iconDataset, 0);
    SetVar(icon3DScalar, WHICHICON, NewInt(ICON3DSCALAR));
    AddToReferenceList(icon3DScalar);

    icon2DScalar = NewObject(iconDataset, 0);
    SetVar(icon2DScalar, WHICHICON, NewInt(ICON2DSCALAR));
    AddToReferenceList(icon2DScalar);

    icon1DScalar = NewObject(iconDataset, 0);
    SetVar(icon1DScalar, WHICHICON, NewInt(ICON1DSCALAR));
    AddToReferenceList(icon1DScalar);


    icon4DVector = NewObject(iconDataset, 0);
    SetVar(icon4DVector, WHICHICON, NewInt(ICON4DVECTOR));
    AddToReferenceList(icon4DVector);

    icon3DVector = NewObject(iconDataset, 0);
    SetVar(icon3DVector, WHICHICON, NewInt(ICON3DVECTOR));
    AddToReferenceList(icon3DVector);

    icon2DVector = NewObject(iconDataset, 0);
    SetVar(icon2DVector, WHICHICON, NewInt(ICON2DVECTOR));
    AddToReferenceList(icon2DVector);

    icon1DVector = NewObject(iconDataset, 0);
    SetVar(icon1DVector, WHICHICON, NewInt(ICONVECTORS));
    AddToReferenceList(icon1DVector);

    datasetClass = NewObject(advertiseableClass, 0);
    AddToReferenceList(datasetClass);
    SetVar(datasetClass, CLASSID, NewInt(CLASS_DATASET));
    SetMethod(datasetClass, GETLONGNAME, GetPlainDatasetLongName);
    SetVar(datasetClass, DEFAULTICON, iconDataset);
    SetVar(datasetClass, NAME, NewString("Dataset"));
    SetVar(datasetClass, DOUBLECLICK, NewString(OF_SHOW_CONTROLS));
#ifdef OLDCODE
    SetMethod(datasetClass, LOCALCOPY, MakeLocalCopy);
#endif
    SetMethod(datasetClass, CLEANUP, CleanupDataset);
    SetMethod(datasetClass, GETDATASETINFO, GetPlainDatasetInfo);
    SetMethod(datasetClass, MINMAX, GetDataMinMax);
    SetMethod(datasetClass, NEWCTLWINDOW, ShowDatasetControls);
    SetMethod(datasetClass, SHOWCONTROLS, NewControlWindow);
    SetMethod(datasetClass, REGISTERFIELD, RegisterDatasetField);
    SetMethod(datasetClass, REGISTERFORM, RegisterDatasetForm);
    SetMethod(datasetClass, GETTOPDIM, GetDatasetTopDim);
    SetMethod(datasetClass, GETSPATIALDIM, GetDatasetSpatialDim);
    SetMethod(datasetClass, GETFORMDIMS, GetPlainDatasetFormDims);
    SetMethod(datasetClass, DELETE, DeleteObject);
    SetMethod(datasetClass, ADDCONTROLS, AddDatasetControls);
    SetMethod(datasetClass, CONTROLICON, MakeDatasetControlIcon);
    DeclareIndirectDependency(datasetClass, PALETTESET, CPALETTE, CHANGED);
    DeclareIndirectDependency(datasetClass, PALETTESET, CPALETTE, JUSTCOLORCHANGE);
    SetMethod(datasetClass, CPALETTE, MakeDatasetPalette);
    SetMethod(datasetClass, PALETTESET, DatasetPaletteSet);
    DeclareDependency(datasetClass, CURDATA, DATA);
    DeclareIndirectDependency(datasetClass, CURDATA, DATA, CURDATA);
    SetMethod(datasetClass, CURDATA, MakeDatasetCurData);
    DeclareDependency(datasetClass, TIMESTEPS, DATA);
    DeclareIndirectDependency(datasetClass, TIMESTEPS, DATA, TIMESTEPS);
    SetMethod(datasetClass, TIMESTEPS, MakeDatasetTimesteps);
    DeclareDependency(datasetClass, CURDATA, TIME);
    SetMethod(datasetClass, VISUALIZE, VisualizeDataset);
    SetMethod(datasetClass, VISUALIZEAS, VisualizeDatasetAs);
    DeclareIndirectDependency(datasetClass, CURDATAFORM, DATAFORM, CURDATAFORM);
    SetMethod(datasetClass, CURDATAFORM, MakeCurDataForm);

    dataFormClass = NewObject(NULLOBJ, 0);
    AddToReferenceList(dataFormClass);
    SetVar(dataFormClass, NCOMPONENTS, NewInt(3));
    SetMethod(dataFormClass, REGISTERFIELD, RegisterDataFormField);
    SetMethod(dataFormClass, GETTOPDIM, GetDataFormTopDim);
    DeclareIndirectDependency(dataFormClass, CURDATAFORM, DATA, CURDATA);
    SetMethod(dataFormClass, CURDATAFORM, MakeCurDataForm);
    /*SetMethod(dataFormClass, NORMALS, MakeDataFormNormals);*/

    data3DScalar = NewObject(datasetClass, 0);
    SetVar(data3DScalar, DEFAULTICON, icon3DScalar);
    AddToReferenceList(data3DScalar);

    data2DScalar = NewObject(datasetClass, 0);
    SetVar(data2DScalar, DEFAULTICON, icon2DScalar);
    AddToReferenceList(data2DScalar);

    data1DVector = NewObject(datasetClass, 0);
    SetVar(data1DVector, DEFAULTICON, icon1DVector);
    AddToReferenceList(data1DVector);

    data3DUnstructSurface = NewObject(datasetClass, 0);
    SetVar(data3DUnstructSurface, DEFAULTICON, icon2DScalar);
    AddToReferenceList(data3DUnstructSurface);

    /*Class for a geometry file*/
    iconGeometry = NewIcon(0, 0, ICONGEOMETRY, "Geometry");
    SetMethod(iconGeometry, DELETEICON, DeleteDatasetIcon);
    geometryClass = NewObject(datasetClass, 0);
    AddToReferenceList(geometryClass);
    SetVar(geometryClass, NAME, NewString("Geometry"));
    SetVar(geometryClass, DEFAULTICON, iconGeometry);
    SetMethod(geometryClass, DELETE, DeleteObject);

    allDatasets = NewList();
#if 0
    SetMethod(allDatasets, MARK, (FuncTyp) 0);
#endif
    AddToReferenceList(allDatasets);
}

void KillDatasets()
/*Kills the data sets*/
{
    int k;
    for (k = 0; k < MAXNCURFIELDS; ++k)
    {
	CleanCurField(k);
    }
    DeleteThing(allDatasets);
    DeleteThing(geometryClass);
    DeleteThing(data3DUnstructSurface);
    DeleteThing(data1DVector);
    DeleteThing(data3DScalar);
    DeleteThing(data2DScalar);
    DeleteThing(dataFormClass);
    DeleteThing(datasetClass);

    DeleteThing(icon1DVector);
    DeleteThing(icon2DVector);
    DeleteThing(icon3DVector);
    DeleteThing(icon4DVector);

    DeleteThing(icon1DScalar);
    DeleteThing(icon2DScalar);
    DeleteThing(icon3DScalar);
    DeleteThing(icon4DScalar);
    DeleteThing(iconDataset);
}
