
/*
 * bltGrElem.c --
 *
 *	This module implements generic elements for the BLT graph widget.
 *
 * Copyright 1993-1998 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 */

#include "bltGraph.h"
#include "bltChain.h"
#include <X11/Xutil.h>
#include <X11/Xatom.h>

static int StringToData _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp,
	Tk_Window tkwin, char *string, char *widgRec, int offset));
static char *DataToString _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin,
	char *widgRec, int offset, Tcl_FreeProc **freeProcPtr));

Tk_CustomOption bltDataOption =
{
    StringToData, DataToString, (ClientData)0
};

static int StringToDataPairs _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *DataPairsToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtr));

Tk_CustomOption bltDataPairsOption =
{
    StringToDataPairs, DataPairsToString, (ClientData)0
};

static int StringToAlong _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *AlongToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtr));

Tk_CustomOption alongOption =
{
    StringToAlong, AlongToString, (ClientData)0
};


static int counter;

#include "bltGrElem.h"

extern Element *Blt_BarElement();
extern Element *Blt_LineElement();

#ifdef __STDC__
static Blt_VectorChangedProc VectorChangedProc;
#endif /* __STDC__ */

extern Tk_CustomOption bltDistanceOption;

EXTERN int Blt_VectorExists2 _ANSI_ARGS_((Tcl_Interp *interp, char *vecName));

/*
 * ----------------------------------------------------------------------
 * Custom option parse and print procedures
 * ----------------------------------------------------------------------
 */
static void
SyncElemVector(vPtr)
    ElemVector *vPtr;
{
    vPtr->nValues = Blt_VecLength(vPtr->vecPtr);
    vPtr->valueArr = Blt_VecData(vPtr->vecPtr);
    vPtr->min = Blt_VecMin(vPtr->vecPtr);
    vPtr->max = Blt_VecMax(vPtr->vecPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * FindRange --
 *
 *	Find the minimum, positive minimum, and maximum values in a
 *	given vector and store the results in the vector structure.
 *
 * Results:
 *     	None.
 *
 * Side Effects:
 *	Minimum, positive minimum, and maximum values are stored in
 *	the vector.
 *
 *----------------------------------------------------------------------
 */
static void
FindRange(vPtr)
    ElemVector *vPtr;
{
    register int i;
    register double *x;
    register double min, max;

    if ((vPtr->nValues < 1) || (vPtr->valueArr == NULL)) {
	return;			/* This shouldn't ever happen */
    }
    x = vPtr->valueArr;

    /*  Initialize values to track the vector range */
    min = max = x[0];
    for (i = 1; i < vPtr->nValues; i++) {
	if (x[i] < min) {
	    min = x[i];
	} else if (x[i] > max) {
	    max = x[i];
	}
    }
    vPtr->min = min, vPtr->max = max;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_FindElemVectorMinimum --
 *
 *	Find the minimum, positive minimum, and maximum values in a
 *	given vector and store the results in the vector structure.
 *
 * Results:
 *     	None.
 *
 * Side Effects:
 *	Minimum, positive minimum, and maximum values are stored in
 *	the vector.
 *
 *----------------------------------------------------------------------
 */
double
Blt_FindElemVectorMinimum(vPtr, minLimit)
    ElemVector *vPtr;
    double minLimit;
{
    register int i;
    register double *arr;
    register double min, x;

    min = bltPosInfinity;
    arr = vPtr->valueArr;
    for (i = 0; i < vPtr->nValues; i++) {
	x = arr[i];
	if (x < 0.0) {
	    /* What do you do about negative values when using log
	     * scale values seems like a grey area.  Mirror. */
	    x = -x;
	}
	if ((x > minLimit) && (min > x)) {
	    min = x;
	}
    }
    if (min == bltPosInfinity) {
	min = minLimit;
    }
    return min;
}

static void
FreeDataVector(vPtr)
    ElemVector *vPtr;
{
    if (vPtr->clientId != NULL) {
	Blt_FreeVectorId(vPtr->clientId);	/* Free the old vector */
	vPtr->clientId = NULL;
    } else if (vPtr->valueArr != NULL) {
	free((char *)vPtr->valueArr);
    }
    vPtr->valueArr = NULL;
    vPtr->nValues = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * VectorChangedProc --
 *
 *
 * Results:
 *     	None.
 *
 * Side Effects:
 *	Graph is redrawn.
 *
 *----------------------------------------------------------------------
 */
static void
VectorChangedProc(interp, clientData, notify)
    Tcl_Interp *interp;
    ClientData clientData;
    Blt_VectorNotify notify;
{
    ElemVector *vPtr = (ElemVector *)clientData;
    Element *elemPtr = vPtr->elemPtr;
    Graph *graphPtr = elemPtr->graphPtr;

    switch (notify) {
    case BLT_VECTOR_NOTIFY_DESTROY:
	vPtr->clientId = NULL;
	vPtr->valueArr = NULL;
	vPtr->nValues = 0;
	break;

    case BLT_VECTOR_NOTIFY_UPDATE:
    default:
	Blt_GetVectorById(interp, vPtr->clientId, &(vPtr->vecPtr));
	SyncElemVector(vPtr);
	break;
    }
    graphPtr->flags |= RESET_AXES;
    elemPtr->flags |= COORDS_NEEDED;
    if (!elemPtr->hidden) {
	graphPtr->flags |= REDRAW_BACKING_STORE;
	Blt_EventuallyRedrawGraph(graphPtr);
    }
}

static int
EvalExprList(interp, list, nElemPtr, arrayPtr)
    Tcl_Interp *interp;
    char *list;
    int *nElemPtr;
    double **arrayPtr;
{
    int nElem;
    char **elemArr;
    double *array;
    int result;

    result = TCL_ERROR;
    elemArr = NULL;
    if (Tcl_SplitList(interp, list, &nElem, &elemArr) != TCL_OK) {
	return TCL_ERROR;
    }
    array = NULL;
    if (nElem > 0) {
	register double *valuePtr;
	register int i;

	counter++;
	array = (double *)malloc(sizeof(double) * nElem);
	if (array == NULL) {
	    Tcl_AppendResult(interp, "can't allocate new vector", (char *)NULL);
	    goto badList;
	}
	valuePtr = array;
	for (i = 0; i < nElem; i++) {
	    if (Tcl_ExprDouble(interp, elemArr[i], valuePtr) != TCL_OK) {
		goto badList;
	    }
	    valuePtr++;
	}
    }
    result = TCL_OK;

  badList:
    free((char *)elemArr);
    *arrayPtr = array;
    *nElemPtr = nElem;
    if (result != TCL_OK) {
	free((char *)array);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToData --
 *
 *	Given a Tcl list of numeric expression representing the element
 *	values, convert into an array of double precision values. In
 *	addition, the minimum and maximum values are saved.  Since
 *	elastic values are allow (values which translate to the
 *	min/max of the graph), we must try to get the non-elastic
 *	minimum and maximum.
 *
 * Results:
 *	The return value is a standard Tcl result.  The vector is passed
 *	back via the vPtr.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToData(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Type of axis vector to fill */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Not used. */
    char *string;		/* Tcl list of expressions */
    char *widgRec;		/* Element record */
    int offset;			/* Offset of vector in Element record */
{
    Element *elemPtr = (Element *)(widgRec);
    ElemVector *vPtr = (ElemVector *)(widgRec + offset);

    FreeDataVector(vPtr);
    if (Blt_VectorExists2(interp, string)) {
	Blt_VectorId clientId;

	clientId = Blt_AllocVectorId(interp, string);
	if (Blt_GetVectorById(interp, clientId, &(vPtr->vecPtr)) != TCL_OK) {
	    return TCL_ERROR;
	}
	Blt_SetVectorChangedProc(clientId, VectorChangedProc, (ClientData)vPtr);
	vPtr->elemPtr = elemPtr;
	vPtr->clientId = clientId;
	SyncElemVector(vPtr);
    } else {
	double *newArr;
	int nValues;

	if (EvalExprList(interp, string, &nValues, &newArr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (nValues > 0) {
	    vPtr->valueArr = newArr;
	}
	vPtr->nValues = nValues;
	FindRange(vPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DataToString --
 *
 *	Convert the vector of floating point values into a Tcl list.
 *
 * Results:
 *	The string representation of the vector is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
DataToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Type of axis vector to print */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Element record */
    int offset;			/* Offset of vector in Element record */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    Element *elemPtr = (Element *)(widgRec);
    ElemVector *vPtr = (ElemVector *)(widgRec + offset);
    Tcl_DString dStr;
    char string[TCL_DOUBLE_SPACE + 1];
    char *result;
    register int i;

    if (vPtr->clientId != NULL) {
	return Blt_NameOfVectorId(vPtr->clientId);
    }
    if (vPtr->nValues == 0) {
	return "";
    }
    Tcl_DStringInit(&dStr);
    for (i = 0; i < vPtr->nValues; i++) {
	Tcl_PrintDouble(elemPtr->graphPtr->interp, vPtr->valueArr[i], string);
	Tcl_DStringAppendElement(&dStr, string);
    }
    result = Tcl_DStringValue(&dStr);

    /*
     * If memory wasn't allocated for the dynamic string, do it here (it's
     * currently on the stack), so that Tcl can free it normally.
     */
    if (result == dStr.staticSpace) {
	result = strdup(result);
    }
    *freeProcPtr = (Tcl_FreeProc *)free;
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToDataPairs --
 *
 *	This procedure is like StringToData except that it interprets
 *	the list of numeric expressions as X Y coordinate pairs.  The
 *	minimum and maximum for both the X and Y vectors are
 *	determined.
 *
 * Results:
 *	The return value is a standard Tcl result.  The vectors are
 *	passed back via the widget record (elemPtr).
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToDataPairs(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Not used. */
    char *string;		/* Tcl list of numeric expressions */
    char *widgRec;		/* Element record */
    int offset;			/* Not used. */
{
    Element *elemPtr = (Element *)widgRec;
    int nElem, newSize;
    double *newArr;

    if (EvalExprList(interp, string, &nElem, &newArr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (nElem & 1) {
	Tcl_AppendResult(interp, "odd number of data points", (char *)NULL);
	free((char *)newArr);
	return TCL_ERROR;
    }
    nElem /= 2;
    newSize = nElem * sizeof(double);

    FreeDataVector(&(elemPtr->x));
    FreeDataVector(&(elemPtr->y));

    elemPtr->x.valueArr = (double *)malloc(newSize);
    elemPtr->y.valueArr = (double *)malloc(newSize);
    assert(elemPtr->x.valueArr && elemPtr->y.valueArr);
    elemPtr->x.nValues = elemPtr->y.nValues = nElem;

    if (newSize > 0) {
	register double *dataPtr;
	register int i;

	for (dataPtr = newArr, i = 0; i < nElem; i++) {
	    elemPtr->x.valueArr[i] = *dataPtr++;
	    elemPtr->y.valueArr[i] = *dataPtr++;
	}
	free((char *)newArr);
	FindRange(&(elemPtr->x));
	FindRange(&(elemPtr->y));
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DataPairsToString --
 *
 *	Convert pairs of floating point values in the X and Y arrays
 *	into a Tcl list.
 *
 * Results:
 *	The return value is a string (Tcl list).
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
DataPairsToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Element information record */
    int offset;			/* Not used. */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    Element *elemPtr = (Element *)widgRec;
    Tcl_Interp *interp = elemPtr->graphPtr->interp;
    int i;
    int length;
    char *result;
    char string[TCL_DOUBLE_SPACE + 1];
    Tcl_DString dStr;

    length = NumberOfPoints(elemPtr);
    if (length < 1) {
	return "";
    }
    Tcl_DStringInit(&dStr);
    for (i = 0; i < length; i++) {
	Tcl_PrintDouble(interp, elemPtr->x.valueArr[i], string);
	Tcl_DStringAppendElement(&dStr, string);
	Tcl_PrintDouble(interp, elemPtr->y.valueArr[i], string);
	Tcl_DStringAppendElement(&dStr, string);
    }
    result = Tcl_DStringValue(&dStr);

    /*
     * If memory wasn't allocated for the dynamic string, do it here
     * (it's currently on the stack), so that Tcl can free it
     * normally.
     */
    if (result == dStr.staticSpace) {
	result = strdup(result);
    }
    *freeProcPtr = (Tcl_FreeProc *)free;
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToAlong --
 *
 *	Given a Tcl list of numeric expression representing the element
 *	values, convert into an array of double precision values. In
 *	addition, the minimum and maximum values are saved.  Since
 *	elastic values are allow (values which translate to the
 *	min/max of the graph), we must try to get the non-elastic
 *	minimum and maximum.
 *
 * Results:
 *	The return value is a standard Tcl result.  The vector is passed
 *	back via the vPtr.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToAlong(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Not used. */
    char *string;		/* String representation of value. */
    char *widgRec;		/* Widget record. */
    int offset;			/* Offset of field in widget record. */
{
    int *intPtr = (int *)(widgRec + offset);

    if ((string[0] == 'x') && (string[1] == '\0')) {
	*intPtr = ELEM_CLOSEST_X;
    } else if ((string[0] == 'y') && (string[1] == '\0')) { 
	*intPtr = ELEM_CLOSEST_Y;
    } else if ((string[0] == 'b') && (strcmp(string, "both") == 0)) {
	*intPtr = ELEM_CLOSEST_XY;
    } else {
	Tcl_AppendResult(interp, "bad along value \"", string, "\"",
			 (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * AlongToString --
 *
 *	Convert the vector of floating point values into a Tcl list.
 *
 * Results:
 *	The string representation of the vector is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
AlongToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Widget record */
    int offset;			/* Offset of field in widget record */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    int along = *(int *)(widgRec + offset);

    switch (along) {
    case ELEM_CLOSEST_X:
	return "x";
    case ELEM_CLOSEST_Y:
	return "y";
    case ELEM_CLOSEST_XY:
	return "both";
    default:
	return "unknown along value";
    }
}


/*
 * Generic element routines:
 */
ObjectType
Blt_GetElementType(string)
    char *string;
{
    unsigned int length;
    char c;

    c = string[0];
    length = strlen(string);
    if ((c == 'l') && (strncmp(string, "line", length) == 0)) {
	return TYPE_ELEM_LINE;
    } else if ((c == 's') && (strncmp(string, "strip", length) == 0)) {
	return TYPE_ELEM_STRIP;
    } else if ((c == 'b') && (strncmp(string, "bar", length) == 0)) {
	return TYPE_ELEM_BAR;
    } else {
	return TYPE_UNKNOWN;
    }
}

char *
Blt_NameOfElementType(type)
    ObjectType type;
{
    switch (type) {
    case TYPE_ELEM_LINE:
	return "line";
    case TYPE_ELEM_STRIP:
	return "strip";
    case TYPE_ELEM_BAR:
	return "bar";
    default:
	return "unknown element type";
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetIndex --
 *
 *	Given a string representing the index of a pair of x,y
 *	coordinates, return the numeric index.
 *
 * Results:
 *     	A standard TCL result.
 *
 *----------------------------------------------------------------------
 */
static int
GetIndex(interp, elemPtr, string, indexPtr)
    Tcl_Interp *interp;
    Element *elemPtr;
    char *string;
    int *indexPtr;
{
    long ielem;
    int last;

    last = NumberOfPoints(elemPtr) - 1;
    if ((*string == 'e') && (strcmp("end", string) == 0)) {
	ielem = last;
    } else if (Tcl_ExprLong(interp, string, &ielem) != TCL_OK) {
	return TCL_ERROR;
    }
    *indexPtr = (int)ielem;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_NameToElement --
 *
 *	Find the element represented the given name,  returning
 *	a pointer to its data structure via elemPtrPtr.
 *
 * Results:
 *     	A standard TCL result.
 *
 *----------------------------------------------------------------------
 */
int
Blt_NameToElement(graphPtr, name, elemPtrPtr)
    Graph *graphPtr;
    char *name;
    Element **elemPtrPtr;
{
    Tcl_HashEntry *hPtr;

    if (name == NULL) {
	return TCL_ERROR;
    }
    hPtr = Tcl_FindHashEntry(&(graphPtr->elements.table), name);
    if (hPtr == NULL) {
	Tcl_AppendResult(graphPtr->interp, "can't find element \"", name,
	    "\" in \"", Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL);
	return TCL_ERROR;
    }
    *elemPtrPtr = (Element *)Tcl_GetHashValue(hPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyElement --
 *
 *	Add a new element to the graph.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyElement(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    Blt_ChainLink *linkPtr;

    Blt_DeleteBindings(graphPtr->bindTable, (ClientData)elemPtr);
    Blt_DeleteBindings(graphPtr->legendPtr->bindTable, (ClientData)elemPtr);

    Tk_FreeOptions(elemPtr->classPtr->configSpecs, (char *)elemPtr,
	graphPtr->display, GraphType(elemPtr));
    /*
     * Call the element's own destructor to release the memory and
     * resources allocated for it.
     */
    (*elemPtr->classPtr->destroyProc) (graphPtr, elemPtr);

    /* Remove it also from the element display list */
    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	if (elemPtr == (Element *)Blt_ChainGetValue(linkPtr)) {
	    Blt_ChainDeleteLink(graphPtr->elements.chainPtr, linkPtr);
	    if (!elemPtr->hidden) {
		graphPtr->flags |= SET_ALL_FLAGS;
		Blt_EventuallyRedrawGraph(graphPtr);
	    }
	    break;
	}
    }
    /* Remove the element for the graph's hash table of elements */
    if (elemPtr->hashPtr != NULL) {
	Tcl_DeleteHashEntry(elemPtr->hashPtr);
    }
    if (elemPtr->name != NULL) {
	free((char *)elemPtr->name);
    }
    free((char *)elemPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateElement --
 *
 *	Add a new element to the graph.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static int
CreateElement(graphPtr, interp, argc, argv, type)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
    ObjectType type;
{
    Element *elemPtr;
    Tcl_HashEntry *hPtr;
    int isNew;

    hPtr = Tcl_CreateHashEntry(&(graphPtr->elements.table), argv[3], &isNew);
    if (!isNew) {
	Tcl_AppendResult(interp, "element \"", argv[3],
	    "\" already exists in \"", argv[0], "\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (type == TYPE_ELEM_BAR) {
	elemPtr = Blt_BarElement();
    } else {
	elemPtr = Blt_LineElement();	/* Stripcharts are line graphs
					 * with some options enabled. */
    }
    elemPtr->hashPtr = hPtr;

    /* By default an element's name and label are the same. */
    elemPtr->label = strdup(argv[3]);
    elemPtr->name = strdup(argv[3]);

    elemPtr->graphPtr = graphPtr;
    elemPtr->hidden = FALSE;
    elemPtr->type = type;
    Tcl_SetHashValue(hPtr, (ClientData)elemPtr);

    if (Blt_ConfigureWidgetComponent(interp, graphPtr->tkwin, elemPtr->name,
	    "Element", elemPtr->classPtr->configSpecs, argc - 4, argv + 4,
	    (char *)elemPtr, GraphType(elemPtr)) != TCL_OK) {
	DestroyElement(graphPtr, elemPtr);
	return TCL_ERROR;
    }
    (*elemPtr->classPtr->configProc) (graphPtr, elemPtr);
    Blt_ChainPrepend(graphPtr->elements.chainPtr, (ClientData)elemPtr);

    if (!elemPtr->hidden) {
	/* If the new element isn't hidden then redraw the graph.  */
	graphPtr->flags |= REDRAW_BACKING_STORE;
	Blt_EventuallyRedrawGraph(graphPtr);
    }
    elemPtr->flags |= COORDS_NEEDED;
    graphPtr->flags |= RESET_AXES;
    Tcl_SetResult(interp, elemPtr->name, TCL_STATIC);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RebuildDisplayList --
 *
 *	Given a Tcl list of element names, this procedure rebuilds the
 *	display list, ignoring invalid element names. This list describes
 *	not only only which elements to draw, but in what order.  This is
 *	only important for bar and pie charts.
 *
 * Results:
 *	The return value is a standard Tcl result.  Only if the Tcl list
 *	cannot be split, a TCL_ERROR is returned and interp->result contains
 *	an error message.
 *
 * Side effects:
 *	The graph is eventually redrawn using the new display list.
 *
 *----------------------------------------------------------------------
 */
static int
RebuildDisplayList(graphPtr, newList)
    Graph *graphPtr;		/* Graph widget record */
    char *newList;		/* Tcl list of element names */
{
    int nNames;			/* Number of names found in Tcl name list */
    char **nameArr;		/* Broken out array of element names */
    Tcl_HashSearch cursor;
    register int i;
    register Tcl_HashEntry *hPtr;
    Element *elemPtr;		/* Element information record */

    if (Tcl_SplitList(graphPtr->interp, newList, &nNames, &nameArr) != TCL_OK) {
	Tcl_AppendResult(graphPtr->interp, "can't split name list \"", newList,
	    "\"", (char *)NULL);
	return TCL_ERROR;
    }
    /* Clear the display list and mark all elements as hidden.  */
    Blt_ChainReset(graphPtr->elements.chainPtr);
    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->elements.table), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	elemPtr = (Element *)Tcl_GetHashValue(hPtr);
	elemPtr->hidden = TRUE;
    }

    /* Rebuild the display list, checking that each name it exists
     * (currently ignoring invalid element names).  */
    for (i = 0; i < nNames; i++) {
	if (Blt_NameToElement(graphPtr, nameArr[i], &elemPtr) == TCL_OK) {
	    elemPtr->hidden = FALSE;
	    Blt_ChainAppend(graphPtr->elements.chainPtr, (ClientData)elemPtr);
	}
    }
    free((char *)nameArr);
    graphPtr->flags |= SET_ALL_FLAGS;
    Blt_EventuallyRedrawGraph(graphPtr);
    Tcl_ResetResult(graphPtr->interp);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_DestroyElements --
 *
 *	Removes all the graph's elements. This routine is called when
 *	the graph is destroyed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory allocated for the graph's elements is freed.
 *
 *----------------------------------------------------------------------
 */
void
Blt_DestroyElements(graphPtr)
    Graph *graphPtr;
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Element *elemPtr;

    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->elements.table), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	elemPtr = (Element *)Tcl_GetHashValue(hPtr);
	elemPtr->hashPtr = NULL;
	DestroyElement(graphPtr, elemPtr);
    }
    Tcl_DeleteHashTable(&(graphPtr->elements.table));
    Tcl_DeleteHashTable(&(graphPtr->elements.tagTable));
    Blt_ChainDestroy(graphPtr->elements.chainPtr);
}

void
Blt_MapElements(graphPtr)
    Graph *graphPtr;
{
    Element *elemPtr;
    Blt_ChainLink *linkPtr;

    if (graphPtr->mode != MODE_INFRONT) {
	Blt_ResetStacks(graphPtr);
    }
    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (elemPtr->hidden) {
	    continue;
	}
	if ((graphPtr->flags & COORDS_ALL_PARTS) ||
	    (elemPtr->flags & COORDS_NEEDED)) {
	    (*elemPtr->classPtr->mapProc) (graphPtr, elemPtr);
	    elemPtr->flags &= ~COORDS_NEEDED;
	}
    }
}

/*
 * -----------------------------------------------------------------
 *
 * Blt_DrawElements --
 *
 *	Calls the individual element drawing routines for each
 *	element.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	Elements are drawn into the drawable (pixmap) which will
 *	eventually be displayed in the graph window.
 *
 * -----------------------------------------------------------------
 */
void
Blt_DrawElements(graphPtr, drawable)
    Graph *graphPtr;
    Drawable drawable;		/* Pixmap or window to draw into */
{
    Blt_ChainLink *linkPtr;
    Element *elemPtr;

    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (!elemPtr->hidden) {
	    (*elemPtr->classPtr->drawNormalProc) (graphPtr, drawable, elemPtr);
	}
    }
}

/*
 * -----------------------------------------------------------------
 *
 * Blt_DrawActiveElements --
 *
 *	Calls the individual element drawing routines to display
 *	the active colors for each element.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	Elements are drawn into the drawable (pixmap) which will
 *	eventually be displayed in the graph window.
 *
 * -----------------------------------------------------------------
 */
void
Blt_DrawActiveElements(graphPtr, drawable)
    Graph *graphPtr;
    Drawable drawable;		/* Pixmap or window to draw into */
{
    Blt_ChainLink *linkPtr;
    Element *elemPtr;

    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if ((!elemPtr->hidden) && (elemPtr->flags & ELEM_ACTIVE)) {
	    (*elemPtr->classPtr->drawActiveProc) (graphPtr, drawable, elemPtr);
	}
    }
}

/*
 * -----------------------------------------------------------------
 *
 * Blt_PrintElements --
 *
 *	Generates PostScript output for each graph element in the
 *	element display list.
 *
 * -----------------------------------------------------------------
 */
void
Blt_PrintElements(graphPtr, printable)
    Graph *graphPtr;
    Printable printable;
{
    Blt_ChainLink *linkPtr;
    Element *elemPtr;

    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (!elemPtr->hidden) {
	    /* Comment the PostScript to indicate the start of the element */
	    Blt_PrintFormat(printable, "\n%% Element \"%s\"\n\n", elemPtr->name);
	    (*elemPtr->classPtr->printNormalProc) (graphPtr, printable, elemPtr);
	}
    }
}

/*
 * -----------------------------------------------------------------
 *
 * Blt_PrintActiveElements --
 *
 * -----------------------------------------------------------------
 */
void
Blt_PrintActiveElements(graphPtr, printable)
    Graph *graphPtr;
    Printable printable;
{
    Blt_ChainLink *linkPtr;
    Element *elemPtr;

    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if ((!elemPtr->hidden) && (elemPtr->flags & ELEM_ACTIVE)) {
	    Blt_PrintFormat(printable, "\n%% Active Element \"%s\"\n\n",
		elemPtr->name);
	    (*elemPtr->classPtr->printActiveProc) (graphPtr, printable, elemPtr);
	}
    }
}

int
Blt_GraphUpdateNeeded(graphPtr)
    Graph *graphPtr;
{
    Blt_ChainLink *linkPtr;
    Element *elemPtr;

    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (elemPtr->hidden) {
	    continue;
	}
	/* Check if the x or y vectors have notifications pending */
	if (((elemPtr->x.clientId != NULL) &&
		(Blt_VectorNotifyPending(elemPtr->x.clientId))) ||
	    ((elemPtr->y.clientId != NULL) &&
		(Blt_VectorNotifyPending(elemPtr->y.clientId)))) {
	    return 1;
	}
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ActivateOp --
 *
 *	Marks data points of elements (given by their index) as active.
 *
 * Results:
 *	Returns TCL_OK if no errors occurred.
 *
 *----------------------------------------------------------------------
 */
static int
ActivateOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget */
    Tcl_Interp *interp;		/* Interpreter to report errors to */
    int argc;			/* Number of element names */
    char **argv;		/* List of element names */
{
    Element *elemPtr;
    register int i;
    int *activeArr;
    int reqNumActive;

    if (argc == 3) {
	register Tcl_HashEntry *hPtr;
	Tcl_HashSearch cursor;

	/* List all the currently active elements */
	for (hPtr = Tcl_FirstHashEntry(&(graphPtr->elements.table), &cursor);
	    hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	    elemPtr = (Element *)Tcl_GetHashValue(hPtr);
	    if (elemPtr->flags & ELEM_ACTIVE) {
		Tcl_AppendElement(graphPtr->interp, elemPtr->name);
	    }
	}
	return TCL_OK;
    }
    if (Blt_NameToElement(graphPtr, argv[3], &elemPtr) != TCL_OK) {
	return TCL_ERROR;	/* Can't find named element */
    }
    elemPtr->flags |= ELEM_ACTIVE | ELEM_UPDATE_ACTIVE;

    activeArr = NULL;
    reqNumActive = -1;
    if (argc > 4) {
	register int *activePtr;

	reqNumActive = argc - 4;
	activePtr = activeArr = (int *)malloc(sizeof(int) * reqNumActive);
	assert(activeArr);
	for (i = 4; i < argc; i++) {
	    if (GetIndex(interp, elemPtr, argv[i], activePtr) != TCL_OK) {
		return TCL_ERROR;
	    }
	    activePtr++;
	}
    }
    if (elemPtr->reqActiveArr != NULL) {
	free((char *)elemPtr->reqActiveArr);
    }
    elemPtr->reqNumActive = reqNumActive;
    elemPtr->reqActiveArr = activeArr;
    Blt_EventuallyRedrawGraph(graphPtr);
    return TCL_OK;
}

ClientData
Blt_MakeElementTag(graphPtr, tagName)
    Graph *graphPtr;
    char *tagName;
{
    Tcl_HashEntry *hPtr;
    int isNew;

    hPtr = Tcl_CreateHashEntry(&(graphPtr->elements.tagTable), tagName, &isNew);
    assert(hPtr);
    return (ClientData) Tcl_GetHashKey(&(graphPtr->elements.tagTable), hPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * BindOp --
 *
 *	.g element bind elemName sequence command
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
BindOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc == 3) {
	Tcl_HashEntry *hPtr;
	Tcl_HashSearch cursor;
	char *tagName;

	for (hPtr = Tcl_FirstHashEntry(&(graphPtr->elements.tagTable), &cursor);
	    hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	    tagName = Tcl_GetHashKey(&(graphPtr->elements.tagTable), hPtr);
	    Tcl_AppendElement(interp, tagName);
	}
	return TCL_OK;
    }
    return Blt_ConfigureBindings(interp, graphPtr->bindTable,
	Blt_MakeElementTag(graphPtr, argv[3]), argc - 4, argv + 4);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateOp --
 *
 *	Add a new element to the graph (using the default type of the
 *	graph).
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static int
CreateOp(graphPtr, interp, argc, argv, type)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
    ObjectType type;
{
    return CreateElement(graphPtr, interp, argc, argv, type);
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char *argv[];
{
    Element *elemPtr;

    if (Blt_NameToElement(graphPtr, argv[3], &elemPtr) != TCL_OK) {
	return TCL_ERROR;	/* Can't find named element */
    }
    if (Tk_ConfigureValue(interp, graphPtr->tkwin, elemPtr->classPtr->configSpecs,
	    (char *)elemPtr, argv[4], GraphType(elemPtr)) != TCL_OK) {
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ClosestOp --
 *
 *	Find the element closest to the specified screen coordinates.
 *	Options:
 *	-halo		Consider points only with this maximum distance
 *			from the picked coordinate.
 *	-interpolate	Find closest point along element traces, not just
 *			data points.
 *	-along
 *
 * Results:
 *	A standard Tcl result. If an element could be found within
 *	the halo distance, the interpreter result is "1", otherwise
 *	"0".  If a closest element exists, the designated Tcl array
 *	variable will be set with the following information:
 *
 *	1) the element name,
 *	2) the index of the closest point,
 *	3) the distance (in screen coordinates) from the picked X-Y
 *	   coordinate and the closest point,
 *	4) the X coordinate (graph coordinate) of the closest point,
 *	5) and the Y-coordinate.
 *
 *----------------------------------------------------------------------
 */

static Tk_ConfigSpec closestSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-halo", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(ClosestSearch, halo), 0, &bltDistanceOption},
    {TK_CONFIG_BOOLEAN, "-interpolate", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(ClosestSearch, mode), 0 }, 
    {TK_CONFIG_CUSTOM, "-along", (char *)NULL, (char *)NULL,
	(char *)NULL, Tk_Offset(ClosestSearch, along), 0, &alongOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
	(char *)NULL, 0, 0}
};

static int
ClosestOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget */
    Tcl_Interp *interp;		/* Interpreter to report results to */
    int argc;			/* Number of element names */
    char **argv;		/* List of element names */
{
    Element *elemPtr;
    ClosestSearch info;
    int i;
    int flags = TCL_LEAVE_ERR_MSG;

    if (graphPtr->flags & RESET_AXES) {
	Blt_ResetAxes(graphPtr);
    }
    if (Tk_GetPixels(interp, graphPtr->tkwin, argv[3], &(info.x)) != TCL_OK) {
	Tcl_AppendResult(interp, ": bad window x-coordinate", (char *)NULL);
	return TCL_ERROR;
    }
    if (Tk_GetPixels(interp, graphPtr->tkwin, argv[4], &(info.y)) != TCL_OK) {
	Tcl_AppendResult(interp, ": bad window y-coordinate", (char *)NULL);
	return TCL_ERROR;
    }
    if (graphPtr->inverted) {
	int temp;

	temp = info.x, info.x = info.y, info.y = temp;
    }
    for (i = 6; i < argc; i += 2) {	/* Count switches-value pairs */
	if ((argv[i][0] != '-') || 
	    ((argv[i][1] == '-') && (argv[i][2] == '\0'))) {
	    break;
	}
    }
    if (i > argc) {
	i = argc;
    }

    info.mode = SEARCH_POINTS;
    info.halo = graphPtr->halo;
    info.index = -1;
    info.along = ELEM_CLOSEST_XY;
    
    if (Tk_ConfigureWidget(interp, graphPtr->tkwin, closestSpecs, i - 6,
	    argv + 6, (char *)&info, TK_CONFIG_ARGV_ONLY) != TCL_OK) {
	return TCL_ERROR;	/* Error processing option */
    }
    if ((i < argc) && (argv[i][0] == '-')) {
	i++;			/* Skip "--" */
    }
    info.dist = (double)(info.halo + 1);

    if (i < argc) {
	for ( /* empty */ ; i < argc; i++) {
	    if (Blt_NameToElement(graphPtr, argv[i], &elemPtr) != TCL_OK) {
		return TCL_ERROR;	/* Can't find named element */
	    }
	    if (elemPtr->hidden) {
		Tcl_AppendResult(interp, "element \"", argv[i], "\" is hidden",
		    (char *)NULL);
		return TCL_ERROR;	/* Element isn't visible */
	    }
	    (*elemPtr->classPtr->closestProc) (graphPtr, elemPtr, &info);
	}
    } else {
	Blt_ChainLink *linkPtr;

	/* 
	 * Find the closest point from the set of displayed elements,
	 * searching the display list from back to front.  That way if
	 * the points from two different elements overlay each other
	 * exactly, the last one picked will be the topmost.  
	 */
	for (linkPtr = Blt_ChainLastLink(graphPtr->elements.chainPtr);
	    linkPtr != NULL; linkPtr = Blt_ChainPrevLink(linkPtr)) {
	    elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	    if (!elemPtr->hidden) {
		(*elemPtr->classPtr->closestProc) (graphPtr, elemPtr, &info);
	    }
	}

    }
    if (info.dist < (double)info.halo) {
	char string[200];
	/*
	 *  Return an array of 5 elements
	 */
	if (Tcl_SetVar2(interp, argv[5], "name",
		info.elemPtr->name, flags) == NULL) {
	    return TCL_ERROR;
	}
	sprintf(string, "%d", info.index);
	if (Tcl_SetVar2(interp, argv[5], "index", string, flags) == NULL) {
	    return TCL_ERROR;
	}
	Tcl_PrintDouble(interp, info.point.x, string);
	if (Tcl_SetVar2(interp, argv[5], "x", string, flags) == NULL) {
	    return TCL_ERROR;
	}
	Tcl_PrintDouble(interp, info.point.y, string);
	if (Tcl_SetVar2(interp, argv[5], "y", string, flags) == NULL) {
	    return TCL_ERROR;
	}
	Tcl_PrintDouble(interp, info.dist, string);
	if (Tcl_SetVar2(interp, argv[5], "dist", string, flags) == NULL) {
	    return TCL_ERROR;
	}
	Tcl_SetResult(interp, "1", TCL_STATIC);
    } else {
	if (Tcl_SetVar2(interp, argv[5], "name", "", flags) == NULL) {
	    return TCL_ERROR;
	}
	Tcl_SetResult(interp, "0", TCL_STATIC);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 *	Sets the element specifications by the given the command line
 *	arguments and calls the element specification configuration
 *	routine. If zero or one command line options are given, only
 *	information about the option(s) is returned in interp->result.
 *	If the element configuration has changed and the element is
 *	currently displayed, the axis limits are updated and
 *	recomputed.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * Side Effects:
 *	Graph will be redrawn to reflect the new display list.
 *
 *---------------------------------------------------------------------- 
 */
static int
ConfigureOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char *argv[];
{
    Element *elemPtr;
    int flags;
    int numNames, numOpts;
    char **options;
    register int i;
    Tk_ConfigSpec *specsPtr;

    /* Figure out where the option value pairs begin */
    argc -= 3;
    argv += 3;
    for (i = 0; i < argc; i++) {
	if (argv[i][0] == '-') {
	    break;
	}
	if (Blt_NameToElement(graphPtr, argv[i], &elemPtr) != TCL_OK) {
	    return TCL_ERROR;	/* Can't find named element */
	}
    }
    numNames = i;		/* Number of element names specified */
    numOpts = argc - i;		/* Number of options specified */
    options = argv + numNames;	/* Start of options in argv  */

    for (i = 0; i < numNames; i++) {
	Blt_NameToElement(graphPtr, argv[i], &elemPtr);
	flags = TK_CONFIG_ARGV_ONLY | GraphType(elemPtr);
	specsPtr = elemPtr->classPtr->configSpecs;
	if (numOpts == 0) {
	    return Tk_ConfigureInfo(interp, graphPtr->tkwin, specsPtr,
		    (char *)elemPtr, (char *)NULL, flags);
	} else if (numOpts == 1) {
	    return Tk_ConfigureInfo(interp, graphPtr->tkwin, specsPtr,
		    (char *)elemPtr, options[0], flags);
	}
	if (Tk_ConfigureWidget(interp, graphPtr->tkwin, specsPtr, numOpts,
		options, (char *)elemPtr, flags) != TCL_OK) {
	    return TCL_ERROR;
	}
	if ((*elemPtr->classPtr->configProc) (graphPtr, elemPtr) != TCL_OK) {
	    return TCL_ERROR;	/* Failed to configure element */
	}
	if (Blt_ConfigModified(specsPtr, "-hide", (char *)NULL)) {
#ifdef notdef
	    Blt_ChainLink *linkPtr;

	    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
		linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
		if (elemPtr == (Element *)Blt_ChainGetValue(linkPtr)) {
		    break;
		}
	    }
	    if ((elemPtr->hidden) != (linkPtr == NULL)) {

		/* The element's "hidden" variable is out of sync with
		 * the display list. [That's what you get for having
		 * two ways to do the same thing.] Update the display
		 * list by either by adding or removing the element.  */

		if (linkPtr == NULL) {
		    Blt_ChainPrepend(graphPtr->elements.chainPtr,
			(ClientData)elemPtr);
		} else {
		    Blt_ChainDeleteLink(graphPtr->elements.chainPtr, linkPtr);
		}
	    }
#endif
	    graphPtr->flags |= RESET_AXES;
	    elemPtr->flags |= COORDS_NEEDED;
	}
	/* If data points or axes have changed, reset the axes (may
	 * affect autoscaling) and recalculate the screen points of
	 * the element. */

	if (Blt_ConfigModified(specsPtr, "-*data", "-map*", (char *)NULL)) {
	    graphPtr->flags |= RESET_AXES;
	    elemPtr->flags |= COORDS_NEEDED;
	}
	/* The new label may change the size of the legend */
	if (Blt_ConfigModified(specsPtr, "-label", (char *)NULL)) {
	    graphPtr->flags |= (COORDS_WORLD | REDRAW_WORLD);
	}
    }
    /* Update the pixmap if any configuration option changed */
    graphPtr->flags |= REDRAW_BACKING_STORE | REDRAW_MARGINS;
    Blt_EventuallyRedrawGraph(graphPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DeactivateOp --
 *
 *	Clears the active bit for the named elements.
 *
 * Results:
 *	Returns TCL_OK if no errors occurred.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DeactivateOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget */
    Tcl_Interp *interp;		/* Not used. */
    int argc;			/* Number of element names */
    char **argv;		/* List of element names */
{
    Element *elemPtr;
    register int i;

    for (i = 3; i < argc; i++) {
	if (Blt_NameToElement(graphPtr, argv[i], &elemPtr) != TCL_OK) {
	    return TCL_ERROR;	/* Can't find named element */
	}
	elemPtr->flags &= ~ELEM_ACTIVE;
	if (elemPtr->reqActiveArr != NULL) {
	    free((char *)elemPtr->reqActiveArr);
	    elemPtr->reqActiveArr = NULL;
	}
	elemPtr->reqNumActive = 0;
    }
    Blt_EventuallyRedrawGraph(graphPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteOp --
 *
 *	Delete the named elements from the graph.
 *
 * Results:
 *	TCL_ERROR is returned if any of the named elements can not be
 *	found.  Otherwise TCL_OK is returned;
 *
 * Side Effects:
 *	If the element is currently displayed, the plotting area of
 *	the graph is redrawn. Memory and resources allocated by the
 *	elements are released.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DeleteOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget */
    Tcl_Interp *interp;		/* Not used. */
    int argc;			/* Number of element names */
    char **argv;		/* List of element names */
{
    Element *elemPtr;
    register int i;

    for (i = 3; i < argc; i++) {
	if (Blt_NameToElement(graphPtr, argv[i], &elemPtr) != TCL_OK) {
	    return TCL_ERROR;	/* Can't find named element */
	}
	DestroyElement(graphPtr, elemPtr);
    }
    Blt_EventuallyRedrawGraph(graphPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ExistsOp --
 *
 *	Indicates if the named element exists in the graph.
 *
 * Results:
 *	The return value is a standard Tcl result.  The interpreter
 *	result will contain "1" or "0".
 *
 *----------------------------------------------------------------------
 */
/* ARGSUSED */
static int
ExistsOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;			/* unused */
    char **argv;
{
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_FindHashEntry(&(graphPtr->elements.table), argv[3]);
    Tcl_SetResult(interp, (hPtr == NULL) ? "0" : "1", TCL_STATIC);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetOp --
 *
 * 	Returns the name of the picked element (using the element
 *	bind operation).  Right now, the only name accepted is
 *	"current".
 *
 * Results:
 *	A standard Tcl result.  The interpreter result will contain
 *	the name of the element.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
GetOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char *argv[];
{
    register Element *elemPtr;

    if ((argv[3][0] == 'c') && (strcmp(argv[3], "current") == 0)) {
	elemPtr = (Element *)Blt_GetCurrentItem(graphPtr->bindTable);
	/* Report only on elements. */
	if ((elemPtr != NULL) && (elemPtr->type > TYPE_UNKNOWN) &&
	    (elemPtr->type < TYPE_MARKER_BITMAP)) {
	    Tcl_SetResult(interp, elemPtr->name, TCL_STATIC);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NamesOp --
 *
 *	Returns the names of the elements is the graph matching
 *	one of more patterns provided.  If no pattern arguments
 *	are given, then all element names will be returned.
 *
 * Results:
 *	The return value is a standard Tcl result. The interpreter
 *	result will contain a Tcl list of the element names.
 *
 *----------------------------------------------------------------------
 */
static int
NamesOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Element *elemPtr;
    Tcl_HashSearch cursor;
    register Tcl_HashEntry *hPtr;
    register int i;

    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->elements.table), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	elemPtr = (Element *)Tcl_GetHashValue(hPtr);
	if (argc == 3) {
	    Tcl_AppendElement(graphPtr->interp, elemPtr->name);
	    continue;
	}
	for (i = 3; i < argc; i++) {
	    if (Tcl_StringMatch(elemPtr->name, argv[i])) {
		Tcl_AppendElement(interp, elemPtr->name);
		break;
	    }
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ShowOp --
 *
 *	Queries or resets the element display list.
 *
 * Results:
 *	The return value is a standard Tcl result. The interpreter
 *	result will contain the new display list of element names.
 *
 *----------------------------------------------------------------------
 */
static int
ShowOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Element *elemPtr;
    Blt_ChainLink *linkPtr;

    if (argc == 4) {
	if (RebuildDisplayList(graphPtr, argv[3]) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	Tcl_AppendElement(interp, elemPtr->name);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TypeOp --
 *
 *	Returns the name of the type of the element given by some
 *	element name.
 *
 * Results:
 *	A standard Tcl result. Returns the type of the element in
 *	interp->result. If the identifier given doesn't represent an
 *	element, then an error message is left in interp->result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TypeOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget */
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;		/* Element name */
{
    Element *elemPtr;

    if (Blt_NameToElement(graphPtr, argv[3], &elemPtr) != TCL_OK) {
	return TCL_ERROR;	/* Can't find named element */
    }
    Tcl_SetResult(interp, Blt_NameOfElementType(elemPtr->type), TCL_STATIC);
    return TCL_OK;
}

/*
 * Global routines:
 */
static Blt_OpSpec elemOps[] =
{
    {"activate", 1, (Blt_OpProc)ActivateOp, 3, 0, "?elemName? ?index...?",},
    {"bind", 1, (Blt_OpProc)BindOp, 3, 6, "elemName sequence command",},
    {"cget", 2, (Blt_OpProc)CgetOp, 5, 5, "elemName option",},
    {"closest", 2, (Blt_OpProc)ClosestOp, 6, 0,
	"x y varName ?option value?... ?elemName?...",},
    {"configure", 2, (Blt_OpProc)ConfigureOp, 4, 0,
	"elemName ?elemName?... ?option value?...",},
    {"create", 2, (Blt_OpProc)CreateOp, 4, 0, "elemName ?option value?...",},
    {"deactivate", 3, (Blt_OpProc)DeactivateOp, 3, 0, "?elemName?...",},
    {"delete", 3, (Blt_OpProc)DeleteOp, 3, 0, "?elemName?...",},
    {"exists", 1, (Blt_OpProc)ExistsOp, 4, 4, "elemName",},
    {"get", 1, (Blt_OpProc)GetOp, 4, 4, "name",},
    {"names", 1, (Blt_OpProc)NamesOp, 3, 0, "?pattern?...",},
    {"show", 1, (Blt_OpProc)ShowOp, 3, 4, "?elemList?",},
    {"type", 1, (Blt_OpProc)TypeOp, 4, 4, "elemName",},
};
static int numElemOps = sizeof(elemOps) / sizeof(Blt_OpSpec);


/*
 * ----------------------------------------------------------------
 *
 * Blt_ElementOp --
 *
 *	This procedure is invoked to process the Tcl command that
 *	corresponds to a widget managed by this module.  See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 * ----------------------------------------------------------------
 */
int
Blt_ElementOp(graphPtr, interp, argc, argv, type)
    Graph *graphPtr;		/* Graph widget record */
    Tcl_Interp *interp;
    int argc;			/* # arguments */
    char **argv;		/* Argument list */
    ObjectType type;
{
    Blt_OpProc opProc;
    int result;

    opProc = Blt_GetOperation(interp, numElemOps, elemOps, BLT_OPER_ARG2,
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    if (opProc == CreateOp) {
	result = CreateOp(graphPtr, interp, argc, argv, type);
    } else {
	result = (*opProc) (graphPtr, interp, argc, argv);
    }
    return result;
}
