
/*
 * bltGraph.c --
 *
 *	This module implements a graph widget for the BLT toolkit.
 *
 * Copyright 1991-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.
 *
 *	The graph widget was created by Sani Nassif and George Howlett.
 */

/*
 * To do:
 *
 * 2) Update manual pages.
 *
 * 3) Update comments.
 *
 * 5) Surface, contour, and flow graphs
 *
 * 7) Arrows for line markers
 *
 */

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

static char *classNames[] =
{
    "Graph", "Stripchart", "Barchart", (char *)NULL
};

extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltBarModeOption;
extern Tk_CustomOption bltPadOption;
extern Tk_CustomOption bltTileOption;
extern Tk_CustomOption bltShadowOption;

#define DEF_GRAPH_ASPECT_RATIO	"0.0"
#define DEF_GRAPH_BAR_BASELINE	"0.0"
#define DEF_GRAPH_BAR_MODE	"normal"
#define DEF_GRAPH_BAR_WIDTH	"0.8"
#define DEF_GRAPH_BG_COLOR	STD_COLOR_NORMAL_BG
#define DEF_GRAPH_BG_MONO	STD_MONO_NORMAL_BG
#define DEF_GRAPH_BORDER_WIDTH	STD_BORDERWIDTH
#define DEF_GRAPH_BUFFERED	"1"
#define DEF_GRAPH_CURSOR  	"crosshair"
#define DEF_GRAPH_FONT		STD_FONT_LARGE
#define DEF_GRAPH_HALO		"2m"
#define DEF_GRAPH_HALO_BAR	"0.1i"
#define DEF_GRAPH_HEIGHT	"4i"
#define DEF_GRAPH_HIGHLIGHT_BG_COLOR	STD_COLOR_NORMAL_BG
#define DEF_GRAPH_HIGHLIGHT_BG_MONO	STD_MONO_NORMAL_BG
#define DEF_GRAPH_HIGHLIGHT_COLOR	RGB_BLACK
#define DEF_GRAPH_HIGHLIGHT_WIDTH	"2"
#define DEF_GRAPH_INVERT_XY	"0"
#define DEF_GRAPH_JUSTIFY	"center"
#define DEF_GRAPH_MARGIN	"0"
#define DEF_GRAPH_MARGIN_VAR	(char *)NULL
#define DEF_GRAPH_PLOT_BG_COLOR	RGB_WHITE
#define DEF_GRAPH_PLOT_BG_MONO	RGB_WHITE
#define DEF_GRAPH_PLOT_BW_COLOR STD_BORDERWIDTH
#define DEF_GRAPH_PLOT_BW_MONO  "0"
#define DEF_GRAPH_PLOT_PADX	"8"
#define DEF_GRAPH_PLOT_PADY	"8"
#define DEF_GRAPH_PLOT_RELIEF	"sunken"
#define DEF_GRAPH_RELIEF	"flat"
#define DEF_GRAPH_SHADOW_COLOR	(char *)NULL
#define DEF_GRAPH_SHADOW_MONO	(char *)NULL
#define DEF_GRAPH_TAKE_FOCUS	""
#define DEF_GRAPH_TITLE		(char *)NULL
#define DEF_GRAPH_TITLE_COLOR	STD_COLOR_NORMAL_FG
#define DEF_GRAPH_TITLE_MONO	STD_MONO_NORMAL_FG
#define DEF_GRAPH_WIDTH		"5i"

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_DOUBLE, "-aspect", "aspect", "Aspect",
	DEF_GRAPH_ASPECT_RATIO, Tk_Offset(Graph, aspect),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_GRAPH_BG_COLOR, Tk_Offset(Graph, border),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_GRAPH_BG_MONO, Tk_Offset(Graph, border),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-barmode", "barMode", "BarMode",
	DEF_GRAPH_BAR_MODE, Tk_Offset(Graph, mode),
	TK_CONFIG_DONT_SET_DEFAULT, &bltBarModeOption},
    {TK_CONFIG_DOUBLE, "-barwidth", "barWidth", "BarWidth",
	DEF_GRAPH_BAR_WIDTH, Tk_Offset(Graph, barWidth), 0},
    {TK_CONFIG_DOUBLE, "-baseline", "baseline", "Baseline",
	DEF_GRAPH_BAR_BASELINE, Tk_Offset(Graph, baseline), 0},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bm", "bottomMargin",
	(char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_GRAPH_BORDER_WIDTH, Tk_Offset(Graph, borderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-bottommargin", "bottomMargin", "Margin",
	DEF_GRAPH_MARGIN, Tk_Offset(Graph, bottomMargin.reqSize), 0,
	&bltDistanceOption},
    {TK_CONFIG_STRING, "-bottomvariable", "bottomVariable", "BottomVariable",
	DEF_GRAPH_MARGIN_VAR, Tk_Offset(Graph, bottomMargin.varName), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_BOOLEAN, "-bufferelements", "bufferElements", "BufferElements",
	DEF_GRAPH_BUFFERED, Tk_Offset(Graph, backingStore),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	DEF_GRAPH_CURSOR, Tk_Offset(Graph, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_FONT, "-font", "font", "Font",
	DEF_GRAPH_FONT, Tk_Offset(Graph, titleStyle.font), 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_GRAPH_TITLE_COLOR, Tk_Offset(Graph, titleStyle.color), 
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_GRAPH_TITLE_MONO, Tk_Offset(Graph, titleStyle.color), 
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-halo", "halo", "Halo",
	DEF_GRAPH_HALO, Tk_Offset(Graph, halo), 0, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-height", "height", "Height",
	DEF_GRAPH_HEIGHT, Tk_Offset(Graph, reqHeight), 0, &bltDistanceOption},
    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
	"HighlightBackground",
	DEF_GRAPH_HIGHLIGHT_BG_COLOR, Tk_Offset(Graph, highlightBgColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
	"HighlightBackground",
	DEF_GRAPH_HIGHLIGHT_BG_MONO, Tk_Offset(Graph, highlightBgColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
	DEF_GRAPH_HIGHLIGHT_COLOR, Tk_Offset(Graph, highlightColor), 0},
    {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
	"HighlightThickness",
	DEF_GRAPH_HIGHLIGHT_WIDTH, Tk_Offset(Graph, highlightWidth),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BOOLEAN, "-invertxy", "invertXY", "InvertXY",
	DEF_GRAPH_INVERT_XY, Tk_Offset(Graph, inverted),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
	DEF_GRAPH_JUSTIFY, Tk_Offset(Graph, titleStyle.justify),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-leftmargin", "leftMargin", "Margin",
	DEF_GRAPH_MARGIN, Tk_Offset(Graph, leftMargin.reqSize),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-leftvariable", "leftVariable", "LeftVariable",
	DEF_GRAPH_MARGIN_VAR, Tk_Offset(Graph, leftMargin.varName), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_SYNONYM, "-lm", "leftMargin", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_COLOR, "-plotbackground", "plotBackground", "Background",
	DEF_GRAPH_PLOT_BG_MONO, Tk_Offset(Graph, plotBg),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-plotbackground", "plotBackground", "Background",
	DEF_GRAPH_PLOT_BG_COLOR, Tk_Offset(Graph, plotBg),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_CUSTOM, "-plotborderwidth", "plotBorderWidth", "BorderWidth",
	DEF_GRAPH_PLOT_BW_COLOR, Tk_Offset(Graph, plotBW),
	TK_CONFIG_COLOR_ONLY, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-plotborderwidth", "plotBorderWidth", "BorderWidth",
	DEF_GRAPH_PLOT_BW_MONO, Tk_Offset(Graph, plotBW),
	TK_CONFIG_MONO_ONLY, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-plotpadx", "plotPadX", "PlotPad",
	DEF_GRAPH_PLOT_PADX, Tk_Offset(Graph, padX),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-plotpady", "plotPadY", "PlotPad",
	DEF_GRAPH_PLOT_PADY, Tk_Offset(Graph, padY),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_RELIEF, "-plotrelief", "plotRelief", "Relief",
	DEF_GRAPH_PLOT_RELIEF, Tk_Offset(Graph, plotRelief),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_GRAPH_RELIEF, Tk_Offset(Graph, relief), TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-rightmargin", "rightMargin", "Margin",
	DEF_GRAPH_MARGIN, Tk_Offset(Graph, rightMargin.reqSize),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-rightvariable", "rightVariable", "RightVariable",
	DEF_GRAPH_MARGIN_VAR, Tk_Offset(Graph, rightMargin.varName), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_SYNONYM, "-rm", "rightMargin", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_CUSTOM, "-shadow", "shadow", "Shadow",
	DEF_GRAPH_SHADOW_COLOR, Tk_Offset(Graph, titleStyle.shadow),
	TK_CONFIG_COLOR_ONLY, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-shadow", "shadow", "Shadow",
	DEF_GRAPH_SHADOW_MONO, Tk_Offset(Graph, titleStyle.shadow),
	TK_CONFIG_MONO_ONLY, &bltShadowOption},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_GRAPH_TITLE_MONO, Tk_Offset(Graph, titleStyle.color), 
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
	DEF_GRAPH_TAKE_FOCUS, Tk_Offset(Graph, takeFocus), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-tile", "tile", "Tile",
	(char *)NULL, Tk_Offset(Graph, tile), 
	TK_CONFIG_NULL_OK, &bltTileOption},
    {TK_CONFIG_STRING, "-title", "title", "Title",
	DEF_GRAPH_TITLE, Tk_Offset(Graph, titleText), TK_CONFIG_NULL_OK},
    {TK_CONFIG_SYNONYM, "-tm", "topMargin", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_CUSTOM, "-topmargin", "topMargin", "Margin",
	DEF_GRAPH_MARGIN, Tk_Offset(Graph, topMargin.reqSize),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-topvariable", "topVariable", "TopVariable",
	DEF_GRAPH_MARGIN_VAR, Tk_Offset(Graph, topMargin.varName), 
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-width", "width", "Width",
	DEF_GRAPH_WIDTH, Tk_Offset(Graph, reqWidth),
	0, &bltDistanceOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

static int StringToFormat _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, char *string, char *record, int offset));

static Blt_CustomSwitch formatSwitch =
{
    StringToFormat, (ClientData)0,
};

typedef struct {
    char *name;
    int width, height;
    int format;
} SnapData;

enum SnapFormats { FORMAT_PHOTO, FORMAT_EMF, FORMAT_WMF };

static Blt_SwitchSpec snapSwitches[] = 
{
    {BLT_SWITCH_INT_POSITIVE, "-width", Blt_Offset(SnapData, width), 0},
    {BLT_SWITCH_INT_POSITIVE, "-height", Blt_Offset(SnapData, height), 0},
    {BLT_SWITCH_CUSTOM, "-format", Blt_Offset(SnapData, format), 0, 
	&formatSwitch},
    {BLT_SWITCH_END, NULL, 0, 0}
};

static void DisplayGraph _ANSI_ARGS_((ClientData clientData));
static void DestroyGraph _ANSI_ARGS_((DestroyData dataPtr));
static void GraphEventProc _ANSI_ARGS_((ClientData clientData,
	XEvent *eventPtr));
static int GraphInstCmd _ANSI_ARGS_((ClientData clientData, 
     Tcl_Interp *interp, int argc, char **argv));

#ifdef __STDC__
static BindPickProc PickEntry;
static Tcl_CmdProc StripchartCmd;
static Tcl_CmdProc BarchartCmd;
static Tcl_CmdProc GraphCmd;
static Tcl_CmdDeleteProc GraphInstCmdDeleteProc;
static Blt_TileChangedProc TileChangedProc;
#endif /* __STDC__ */

/*
 *--------------------------------------------------------------
 *
 * Blt_EventuallyRedrawGraph --
 *
 *	Tell the Tk dispatcher to call the graph display routine at
 *	the next idle point.  This request is made only if the window
 *	is displayed and no other redraw request is pending.
 *
 * Results: None.
 *
 * Side effects:
 *	The window is eventually redisplayed.
 *
 *--------------------------------------------------------------
 */
void
Blt_EventuallyRedrawGraph(graphPtr)
    Graph *graphPtr;		/* Graph widget record */
{
    if ((graphPtr->tkwin != NULL) && !(graphPtr->flags & REDRAW_PENDING)) {
	Tcl_DoWhenIdle(DisplayGraph, (ClientData)graphPtr);
	graphPtr->flags |= REDRAW_PENDING;
    }
}

/*
 *--------------------------------------------------------------
 *
 * GraphEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on graphs.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, the graph is eventually
 *	redisplayed.
 *
 *--------------------------------------------------------------
 */
static void
GraphEventProc(clientData, eventPtr)
    ClientData clientData;	/* Graph widget record */
    register XEvent *eventPtr;	/* Event which triggered call to routine */
{
    Graph *graphPtr = (Graph *)clientData;

    if (eventPtr->type == Expose) {
	if (eventPtr->xexpose.count == 0) {
	    graphPtr->flags |= REDRAW_WORLD;
	    Blt_EventuallyRedrawGraph(graphPtr);
	}
    } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
	if (eventPtr->xfocus.detail != NotifyInferior) {
	    if (eventPtr->type == FocusIn) {
		graphPtr->flags |= GRAPH_FOCUS;
	    } else {
		graphPtr->flags &= ~GRAPH_FOCUS;
	    }
	    graphPtr->flags |= REDRAW_WORLD;
	    Blt_EventuallyRedrawGraph(graphPtr);
	}
    } else if (eventPtr->type == DestroyNotify) {
	if (graphPtr->tkwin != NULL) {
	    char *cmdName;

	    cmdName = Tcl_GetCommandName(graphPtr->interp, graphPtr->cmdToken);
#ifdef ITCL_NAMESPACES
	    Itk_SetWidgetCommand(graphPtr->tkwin, (Tcl_Command) NULL);
#endif /* ITCL_NAMESPACES */
	    Blt_DeleteWindowInstanceData(graphPtr->tkwin);
	    graphPtr->tkwin = NULL;
	    Tcl_DeleteCommand(graphPtr->interp, cmdName);
	}
	if (graphPtr->flags & REDRAW_PENDING) {
	    Tcl_CancelIdleCall(DisplayGraph, (ClientData)graphPtr);
	}
	Tcl_EventuallyFree((ClientData)graphPtr, DestroyGraph);
    } else if (eventPtr->type == ConfigureNotify) {
	graphPtr->flags |= (COORDS_WORLD | REDRAW_WORLD);
	Blt_EventuallyRedrawGraph(graphPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GraphInstCmdDeleteProc --
 *
 *	This procedure is invoked when a widget command is deleted.  If
 *	the widget isn't already in the process of being destroyed,
 *	this command destroys it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The widget is destroyed.
 *
 *---------------------------------------------------------------------- */
static void
GraphInstCmdDeleteProc(clientData)
    ClientData clientData;	/* Pointer to widget record. */
{
    Graph *graphPtr = (Graph *)clientData;

    if (graphPtr->tkwin != NULL) {	/* NULL indicates window has
				    * already been destroyed. */
	Tk_Window tkwin;

	tkwin = graphPtr->tkwin;
	graphPtr->tkwin = NULL;
#ifdef ITCL_NAMESPACES
	Itk_SetWidgetCommand(tkwin, (Tcl_Command) NULL);
#endif /* ITCL_NAMESPACES */
	Blt_DeleteWindowInstanceData(tkwin);
	Tk_DestroyWindow(tkwin);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TileChangedProc
 *
 *	Rebuilds the designated GC with the new tile pixmap.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static void
TileChangedProc(clientData, tile)
    ClientData clientData;
    Blt_Tile tile;
{
    Graph *graphPtr = (Graph *)clientData;

    if (graphPtr->tkwin != NULL) {
	graphPtr->flags |= REDRAW_WORLD;
	Blt_EventuallyRedrawGraph(graphPtr);
    }
}

/*
 *--------------------------------------------------------------
 *
 * Blt_AdjustAxisPointers --
 *
 *	Sets the axis pointers according to whether the axis is
 *	inverted on not.  The axis sites are also reset.
 *
 * Results:
 *	None.
 *
 *--------------------------------------------------------------
 */
void
Blt_AdjustAxisPointers(graphPtr)
    Graph *graphPtr;		/* Graph widget record */
{
    if (graphPtr->inverted) {
	graphPtr->leftMargin.chainPtr = graphPtr->axes[0];
	graphPtr->bottomMargin.chainPtr = graphPtr->axes[1];
	graphPtr->rightMargin.chainPtr = graphPtr->axes[2];
	graphPtr->topMargin.chainPtr = graphPtr->axes[3];
    } else {
	graphPtr->leftMargin.chainPtr = graphPtr->axes[1];
	graphPtr->bottomMargin.chainPtr = graphPtr->axes[0];
	graphPtr->rightMargin.chainPtr = graphPtr->axes[3];
	graphPtr->topMargin.chainPtr = graphPtr->axes[2];
    }
}

static int
InitPens(graphPtr)
    Graph *graphPtr;
{
    Pen *penPtr;

    Tcl_InitHashTable(&(graphPtr->penTable), TCL_STRING_KEYS);
    penPtr = Blt_CreatePen(graphPtr, "activeLine", TYPE_ELEM_LINE, 0,
	(char **)NULL);
    if (penPtr == NULL) {
	return TCL_ERROR;
    }
    penPtr = Blt_CreatePen(graphPtr, "activeBar", TYPE_ELEM_BAR, 0, 
	(char **)NULL);
    if (penPtr == NULL) {
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_GraphTags --
 *
 *	Sets the binding tags for a graph object. This routine is
 *	called by Tk when an event occurs in the graph.  It fills
 *	an array of pointers with bind tag addresses.
 *
 *	The object addresses are strings hashed in one of two tag
 *	tables: one for elements and the another for markers.  Note
 *	that there's only one binding table for elements and markers.
 *	[We don't want to trigger both a marker and element bind
 *	command for the same event.]  But we don't want a marker and
 *	element with the same tag name to activate the others
 *	bindings. A tag "all" for markers should mean all markers, not
 *	all markers and elements.  As a result, element and marker
 *	tags are stored in separate hash tables, which means we can't
 *	generate the same tag address for both an elements and marker,
 *	even if they have the same name.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	This information will be used by the binding code in bltUtil.c
 *	to determine what graph objects match the current event.  The
 *	tags are placed in tagArr and *nTagsPtr is set with the
 *	number of tags found.
 *
 *----------------------------------------------------------------------
 */
void
Blt_GraphTags(table, object, tagArr, nTagsPtr)
    BindTable table;
    ClientData object;
    ClientData tagArr[];
    int *nTagsPtr;
{
    Element *elemPtr;
    int nTags;
    MakeTagProc *tagProc;
    Graph *graphPtr;

    graphPtr = (Graph *)Blt_GetBindingData(table);
    /*
     * Trick:	Both markers and elements have the same first few fields in
     *		their structures, such as "type", "name", or "tags".  This is
     *		so we can look at both graph objects interchangably.
     *		Therefore it doesn't matter if we cast the object to either
     *		a marker or element.
     */
    elemPtr = (Element *)object;

    if ((elemPtr->type >= TYPE_ELEM_LINE) &&
	(elemPtr->type <= TYPE_ELEM_BAR)) {
	tagProc = Blt_MakeElementTag;
    } else {
	tagProc = Blt_MakeMarkerTag;
    }
    /*
     * Always add the name of the object to the tag array.
     */
    tagArr[0] = (*tagProc) (graphPtr, elemPtr->name);
    nTags = 1;
    if (elemPtr->tags != NULL) {
	register char **p;
	register int i;

	p = elemPtr->tags;
	for (i = nTags; (*p != NULL) && (i < 10); p++, i++) {
	    tagArr[i] = (*tagProc) (graphPtr, *p);
	}
	nTags = i;
    }
    *nTagsPtr = nTags;
}

/*
 * 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 one that's on top (visible) is picked.
 */
static ClientData
PickEntry(clientData, x, y)
    ClientData clientData;
    int x, y;
{
    Graph *graphPtr = (Graph *)clientData;
    Blt_ChainLink *linkPtr;
    ClosestSearch search;
    Element *elemPtr;
    Marker *markerPtr;
    Extents2D extents;

    Blt_SetClipRegion(graphPtr, &extents);
    if ((x > extents.right) || (x < extents.left) || (y > extents.bottom) ||
	(y < extents.top)) {
	return NULL;
    }
    markerPtr = (Marker *)Blt_NearestMarker(graphPtr, x, y, FALSE);
    if (markerPtr != NULL) {
	return (ClientData) markerPtr;
    }
    search.along = ELEM_CLOSEST_XY;
    search.halo = graphPtr->halo + 1;
    search.index = -1;
    search.x = x;
    search.y = y;
    search.dist = (double)(search.halo + 1);
    search.mode = SEARCH_AUTO;

    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, &search);
	}
    }
    if (search.dist <= (double)search.halo) {
	return (ClientData) search.elemPtr;
    }
    markerPtr = (Marker *)Blt_NearestMarker(graphPtr, x, y, TRUE);
    if (markerPtr != NULL) {
	return (ClientData) markerPtr;
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureGraph --
 *
 *	Allocates resources for the graph.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Configuration information, such as text string, colors, font,
 *	etc. get set for graphPtr;  old resources get freed, if there
 *	were any.  The graph is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static void
ConfigureGraph(graphPtr)
    Graph *graphPtr;		/* Graph widget record */
{
    XColor *colorPtr;
    GC newGC;
    XGCValues gcValues;
    unsigned long gcMask;

    /* Don't allow negative bar widths. Reset to an arbitrary value (0.1) */
    if (graphPtr->barWidth <= 0.0) {
	graphPtr->barWidth = 0.1;
    }
    graphPtr->inset = graphPtr->borderWidth + graphPtr->highlightWidth + 1;
    if ((graphPtr->reqHeight != Tk_ReqHeight(graphPtr->tkwin)) ||
	(graphPtr->reqWidth != Tk_ReqWidth(graphPtr->tkwin))) {
	Tk_GeometryRequest(graphPtr->tkwin, graphPtr->reqWidth,
	    graphPtr->reqHeight);
    }
    Tk_SetInternalBorder(graphPtr->tkwin, graphPtr->borderWidth);
    colorPtr = Tk_3DBorderColor(graphPtr->border);

    if (graphPtr->titleText != NULL) {
	int textWidth, textHeight;

	Blt_GetTextExtents(&(graphPtr->titleStyle), graphPtr->titleText,
	    &textWidth, &textHeight);
	graphPtr->titleStyle.height = textHeight + 10;
    } else {
	graphPtr->titleStyle.width = graphPtr->titleStyle.height = 0;
    }

    /*
     * Create GCs for interior and exterior regions, and a background
     * GC for clearing the margins with XFillRectangle
     */

    /* Margin GC */

    gcValues.foreground = graphPtr->titleStyle.color->pixel;
    gcValues.background = colorPtr->pixel;
    gcMask = (GCForeground | GCBackground);
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (graphPtr->drawGC != NULL) {
	Tk_FreeGC(graphPtr->display, graphPtr->drawGC);
    }
    graphPtr->drawGC = newGC;

    /* Plot fill GC (Background = Foreground) */

    gcValues.foreground = graphPtr->plotBg->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (graphPtr->plotFillGC != NULL) {
	Tk_FreeGC(graphPtr->display, graphPtr->plotFillGC);
    }
    graphPtr->plotFillGC = newGC;

    /* Margin fill GC (Background = Foreground) */

    gcValues.foreground = colorPtr->pixel;
    gcValues.background = graphPtr->titleStyle.color->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (graphPtr->fillGC != NULL) {
	Tk_FreeGC(graphPtr->display, graphPtr->fillGC);
    }
    graphPtr->fillGC = newGC;
    if (graphPtr->tile != NULL) {
	Blt_SetTileChangedProc(graphPtr->tile, TileChangedProc,
	    (ClientData)graphPtr);
    }

    Blt_ResetTextStyle(graphPtr->tkwin, &(graphPtr->titleStyle));

    if (Blt_ConfigModified(configSpecs, "-invertxy", (char *)NULL)) {

	/*
	 * If the -inverted option changed, we need to readjust the pointers
	 * to the axes and recompute the their scales.
	 */

	Blt_AdjustAxisPointers(graphPtr);
	graphPtr->flags |= RESET_AXES;
    }
    if ((!graphPtr->backingStore) && (graphPtr->backPixmap != None)) {

	/*
	 * Free the pixmap if we're not buffering the display of elements
	 * anymore.
	 */

	Tk_FreePixmap(graphPtr->display, graphPtr->backPixmap);
	graphPtr->backPixmap = None;
    }
    /*
     * Reconfigure the crosshairs, just in case the background color of
     * the plotarea has been changed.
     */
    Blt_ConfigureCrosshairs(graphPtr);

    /*
     *  Update the layout of the graph (and redraw the elements) if
     *  any of the following graph options which affect the size of
     *	the plotting area has changed.
     *
     *	    -aspect
     *      -borderwidth, -plotborderwidth
     *	    -font, -title
     *	    -width, -height
     *	    -invertxy
     *	    -bottommargin, -leftmargin, -rightmargin, -topmargin,
     *	    -barmode, -barwidth
     */
    if (Blt_ConfigModified(configSpecs, "-invertxy", "-title", "-font",
	    "-*margin", "-*width", "-height", "-barmode", "-*pad*", "-aspect",
	    (char *)NULL)) {
	graphPtr->flags |= SET_ALL_FLAGS;
    }
    if (Blt_ConfigModified(configSpecs, "-plotbackground", (char *)NULL)) {
	graphPtr->flags |= REDRAW_BACKING_STORE;
    }
    graphPtr->flags |= REDRAW_WORLD;
    Blt_EventuallyRedrawGraph(graphPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyGraph --
 *
 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 *	to clean up the internal structure of a graph at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the widget is freed up.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyGraph(dataPtr)
    DestroyData dataPtr;
{
    Graph *graphPtr = (Graph *)dataPtr;

    /*
     * Destroy the individual components of the graph: elements, markers,
     * X and Y axes, legend, display lists etc.
     */
    Blt_DestroyMarkers(graphPtr);
    Blt_DestroyElements(graphPtr);

    Blt_DestroyAxes(graphPtr);
    Blt_DestroyPens(graphPtr);

    Blt_DestroyLegend(graphPtr);
    Blt_DestroyPostScript(graphPtr);
    Blt_DestroyCrosshairs(graphPtr);
    Blt_DestroyGrid(graphPtr);
    Blt_DestroyBindingTable(graphPtr->bindTable);

    /* Release allocated X resources and memory. */
    if (graphPtr->drawGC != NULL) {
	Tk_FreeGC(graphPtr->display, graphPtr->drawGC);
    }
    if (graphPtr->fillGC != NULL) {
	Tk_FreeGC(graphPtr->display, graphPtr->fillGC);
    }
    if (graphPtr->plotFillGC != NULL) {
	Tk_FreeGC(graphPtr->display, graphPtr->plotFillGC);
    }
    Blt_FreeTextStyle(graphPtr->display, &(graphPtr->titleStyle));
    if (graphPtr->backPixmap != None) {
	Tk_FreePixmap(graphPtr->display, graphPtr->backPixmap);
    }
    if (graphPtr->freqArr != NULL) {
	free((char *)graphPtr->freqArr);
    }
    if (graphPtr->nStacks > 0) {
	Tcl_DeleteHashTable(&(graphPtr->freqTable));
    }
    if (graphPtr->tile != NULL) {
	Blt_FreeTile(graphPtr->tile);
    }
    Tk_FreeOptions(configSpecs, (char *)graphPtr, graphPtr->display, 0);
    free((char *)graphPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateGraph --
 *
 *	This procedure creates and initializes a new widget.
 *
 * Results:
 *	The return value is a pointer to a structure describing
 *	the new widget.  If an error occurred, then the return
 *	value is NULL and an error message is left in interp->result.
 *
 * Side effects:
 *	Memory is allocated, a Tk_Window is created, etc.
 *
 *----------------------------------------------------------------------
 */

static Graph *
CreateGraph(interp, tkwin, argc, argv, type)
    Tcl_Interp *interp;
    Tk_Window tkwin;
    int argc;
    char **argv;
    ObjectType type;
{
    Graph *graphPtr;

    graphPtr = (Graph *)calloc(1, sizeof(Graph));
    assert(graphPtr);

    /* Initialize the data structure for the graph. */

    graphPtr->tkwin = tkwin;
    graphPtr->display = Tk_Display(tkwin);
    graphPtr->interp = interp;
    graphPtr->type = type;
    graphPtr->backingStore = TRUE;
    graphPtr->highlightWidth = 2;
    graphPtr->plotRelief = TK_RELIEF_SUNKEN;
    graphPtr->relief = TK_RELIEF_FLAT;
    graphPtr->flags = (SET_ALL_FLAGS);
    graphPtr->nextMarkerId = 1;
    graphPtr->padLeft = graphPtr->padRight = 8;
    graphPtr->padTop = graphPtr->padBottom = 8;
    graphPtr->bottomMargin.site = MARGIN_BOTTOM;
    graphPtr->leftMargin.site = MARGIN_LEFT;
    graphPtr->topMargin.site = MARGIN_TOP;
    graphPtr->rightMargin.site = MARGIN_RIGHT;

    Blt_InitTextStyle(&(graphPtr->titleStyle));

    Tcl_InitHashTable(&(graphPtr->axisTable), TCL_STRING_KEYS);
    Tcl_InitHashTable(&(graphPtr->elements.table), TCL_STRING_KEYS);
    Tcl_InitHashTable(&(graphPtr->markers.table), TCL_STRING_KEYS);
    Tcl_InitHashTable(&(graphPtr->markers.tagTable), TCL_STRING_KEYS);
    Tcl_InitHashTable(&(graphPtr->elements.tagTable), TCL_STRING_KEYS);
    graphPtr->elements.chainPtr = Blt_ChainCreate();
    graphPtr->markers.chainPtr = Blt_ChainCreate();

    Tk_SetClass(tkwin, classNames[type - 1]);
    Blt_SetWindowInstanceData(tkwin, (ClientData)graphPtr);

    if (InitPens(graphPtr) != TCL_OK) {
	return NULL;
    }
    if (Tk_ConfigureWidget(interp, tkwin, configSpecs, argc - 2, argv + 2,
	    (char *)graphPtr, 0) != TCL_OK) {
	return NULL;
    }
    if (Blt_DefaultAxes(graphPtr) != TCL_OK) {
	return NULL;
    }
    Blt_AdjustAxisPointers(graphPtr);

    if (Blt_CreatePostScript(graphPtr) != TCL_OK) {
	return NULL;
    }
    if (Blt_CreateCrosshairs(graphPtr) != TCL_OK) {
	return NULL;
    }
    if (Blt_CreateLegend(graphPtr) != TCL_OK) {
	return NULL;
    }
    if (Blt_CreateGrid(graphPtr) != TCL_OK) {
	return NULL;
    }
    Tk_CreateEventHandler(graphPtr->tkwin, 
	ExposureMask | StructureNotifyMask | FocusChangeMask,
        GraphEventProc, (ClientData)graphPtr);

    graphPtr->cmdToken = Tcl_CreateCommand(interp, argv[1], GraphInstCmd,
	(ClientData)graphPtr, GraphInstCmdDeleteProc);
#ifdef ITCL_NAMESPACES
    Itk_SetWidgetCommand(graphPtr->tkwin, graphPtr->cmdToken);
#endif
    ConfigureGraph(graphPtr);
    graphPtr->bindTable = Blt_CreateBindingTable(interp, tkwin,
	(ClientData)graphPtr, PickEntry, Blt_GraphTags);
    return graphPtr;
}

/* Widget sub-commands */

/*ARGSUSED*/
static int
XAxisOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int margin;

    margin = (graphPtr->inverted) ? MARGIN_LEFT : MARGIN_BOTTOM;
    return Blt_AxisOp(graphPtr, margin, argc, argv);
}

/*ARGSUSED*/
static int
X2AxisOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int margin;

    margin = (graphPtr->inverted) ? MARGIN_RIGHT : MARGIN_TOP;
    return Blt_AxisOp(graphPtr, margin, argc, argv);
}

/*ARGSUSED*/
static int
YAxisOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int margin;

    margin = (graphPtr->inverted) ? MARGIN_BOTTOM : MARGIN_LEFT;
    return Blt_AxisOp(graphPtr, margin, argc, argv);
}

/*ARGSUSED*/
static int
Y2AxisOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    int margin;

    margin = (graphPtr->inverted) ? MARGIN_TOP : MARGIN_RIGHT;
    return Blt_AxisOp(graphPtr, margin, argc, argv);
}

/*ARGSUSED*/
static int
BarOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    return Blt_ElementOp(graphPtr, interp, argc, argv, TYPE_ELEM_BAR);
}

/*ARGSUSED*/
static int
LineOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    return Blt_ElementOp(graphPtr, interp, argc, argv, TYPE_ELEM_LINE);
}

/*ARGSUSED*/
static int
ElementOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;		/* Not used. */
    int argc;
    char **argv;
{
    return Blt_ElementOp(graphPtr, interp, argc, argv, graphPtr->type);
}

static int
ConfigureOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int flags;

    flags = TK_CONFIG_ARGV_ONLY;
    if (argc == 2) {
	return Tk_ConfigureInfo(interp, graphPtr->tkwin, configSpecs,
	    (char *)graphPtr, (char *)NULL, flags);
    } else if (argc == 3) {
	return Tk_ConfigureInfo(interp, graphPtr->tkwin, configSpecs,
	    (char *)graphPtr, argv[2], flags);
    } else {
	if (Tk_ConfigureWidget(interp, graphPtr->tkwin, configSpecs, argc - 2,
		argv + 2, (char *)graphPtr, flags) != TCL_OK) {
	    return TCL_ERROR;
	}
	ConfigureGraph(graphPtr);
	return TCL_OK;
    }
}

/* ARGSUSED*/
static int
CgetOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    return Tk_ConfigureValue(interp, graphPtr->tkwin, configSpecs,
	(char *)graphPtr, argv[2], 0);
}

/*
 *--------------------------------------------------------------
 *
 * ExtentsOp --
 *
 *	Reports the size of one of several items within the graph.
 *	The following are valid items:
 *
 *	  "bottommargin"	Height of the bottom margin
 *	  "leftmargin"		Width of the left margin
 *	  "legend"		x y w h of the legend
 *	  "plotarea"		x y w h of the plotarea
 *	  "plotheight"		Height of the plot area
 *	  "rightmargin"		Width of the right margin
 *	  "topmargin"		Height of the top margin
 *        "plotwidth"		Width of the plot area
 *
 * Results:
 *	Always returns TCL_OK.
 *
 *--------------------------------------------------------------
 */
/* ARGSUSED*/
static int
ExtentsOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    char c;
    unsigned int length;
    char string[200];

    c = argv[2][0];
    length = strlen(argv[2]);
    if ((c == 'p') && (length > 4) &&
	(strncmp("plotheight", argv[2], length) == 0)) {
	Tcl_SetResult(interp, Blt_Itoa(graphPtr->bottom - graphPtr->top + 1),
	    TCL_VOLATILE);
    } else if ((c == 'p') && (length > 4) &&
	(strncmp("plotwidth", argv[2], length) == 0)) {
	Tcl_SetResult(interp, Blt_Itoa(graphPtr->right - graphPtr->left + 1),
	    TCL_VOLATILE);
    } else if ((c == 'p') && (length > 4) &&
	(strncmp("plotarea", argv[2], length) == 0)) {
	sprintf(string, "%d %d %d %d", graphPtr->left, graphPtr->top,
	    graphPtr->right - graphPtr->left + 1,
	    graphPtr->bottom - graphPtr->top + 1);
	Tcl_SetResult(interp, string, TCL_VOLATILE);
    } else if ((c == 'l') && (length > 2) &&
	(strncmp("legend", argv[2], length) == 0)) {
	Legend *legendPtr = graphPtr->legendPtr;

	sprintf(string, "%d %d %d %d", legendPtr->x, legendPtr->y,
	    legendPtr->width, legendPtr->height);
	Tcl_SetResult(interp, string, TCL_VOLATILE);
    } else if ((c == 'l') && (length > 2) &&
	(strncmp("leftmargin", argv[2], length) == 0)) {
	Tcl_SetResult(interp, Blt_Itoa(graphPtr->leftMargin.width), 
		      TCL_VOLATILE);
    } else if ((c == 'r') && (length > 1) &&
	(strncmp("rightmargin", argv[2], length) == 0)) {
	Tcl_SetResult(interp, Blt_Itoa(graphPtr->rightMargin.width), 
		      TCL_VOLATILE);
    } else if ((c == 't') && (length > 1) &&
	(strncmp("topmargin", argv[2], length) == 0)) {
	Tcl_SetResult(interp, Blt_Itoa(graphPtr->topMargin.height), TCL_VOLATILE);
    } else if ((c == 'b') && (length > 1) &&
	(strncmp("bottommargin", argv[2], length) == 0)) {
	Tcl_SetResult(interp, Blt_Itoa(graphPtr->bottomMargin.height), 
		      TCL_VOLATILE);
    } else {
	Tcl_AppendResult(interp, "bad extent item \"", argv[2],
	    "\": should be plotheight, plotwidth, leftmargin, rightmargin, \
topmargin, bottommargin, plotarea, or legend", (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * InsideOp --
 *
 *	Returns true of false whether the given point is inside
 *	the plotting area (defined by left,bottom right, top).
 *
 * Results:
 *	Always returns TCL_OK.  interp->result will contain
 *	the boolean string representation.
 *
 *--------------------------------------------------------------
 */
/* ARGSUSED*/
static int
InsideOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int x, y;
    Extents2D extents;
    int result;

    if (Tk_GetPixels(interp, graphPtr->tkwin, argv[2], &x) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tk_GetPixels(interp, graphPtr->tkwin, argv[3], &y) != TCL_OK) {
	return TCL_ERROR;
    }
    Blt_SetClipRegion(graphPtr, &extents);
    result = PointInRegion(&extents, x, y);
    Tcl_SetResult(interp, (result) ? "1" : "0", TCL_STATIC);
    return TCL_OK;
}

/*
 * -------------------------------------------------------------------------
 *
 * InvtransformOp --
 *
 *	This procedure returns a list of the graph coordinate
 *	values corresponding with the given window X and Y
 *	coordinate positions.
 *
 * Results:
 *	Returns a standard Tcl result.  If an error occurred while
 *	parsing the window positions, TCL_ERROR is returned, and
 *	interp->result will contain the error message.  Otherwise
 *	interp->result will contain a Tcl list of the x and y
 *	coordinates.
 *
 * ------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InvtransformOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget record */
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    double x, y;
    Point2D point;
    Axis2D axes;

    if (Tcl_ExprDouble(interp, argv[2], &x) != TCL_OK ||
	Tcl_ExprDouble(interp, argv[3], &y) != TCL_OK) {
	return TCL_ERROR;
    }
    if (graphPtr->flags & RESET_AXES) {
	Blt_ResetAxes(graphPtr);
    }
    /* Perform the reverse transformation, converting from window
     * coordinates to graph data coordinates.  Note that the point is
     * always mapped to the bottom and left axes (which may not be
     * what the user wants).  */

    /*  Pick the first pair of axes */
    axes.x = Blt_GetFirstAxis(graphPtr->axes[0]);
    axes.y = Blt_GetFirstAxis(graphPtr->axes[1]);
    point = Blt_InvMap2D(graphPtr, x, y, &axes);

    Tcl_AppendElement(interp, Blt_Dtoa(interp, point.x));
    Tcl_AppendElement(interp, Blt_Dtoa(interp, point.y));
    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * TransformOp --
 *
 *	This procedure returns a list of the window coordinates
 *	corresponding with the given graph x and y coordinates.
 *
 * Results:
 *	Returns a standard Tcl result.  interp->result contains
 *	the list of the graph coordinates. If an error occurred
 *	while parsing the window positions, TCL_ERROR is returned,
 *	then interp->result will contain an error message.
 *
 * -------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TransformOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget record */
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    double x, y;
    Point2D point;
    Axis2D axes;

    if (Blt_GetCoordinate(interp, argv[2], &x) != TCL_OK ||
	Blt_GetCoordinate(interp, argv[3], &y) != TCL_OK) {
	return TCL_ERROR;
    }
    if (graphPtr->flags & RESET_AXES) {
	Blt_ResetAxes(graphPtr);
    }
    /*
     * Perform the transformation from window to graph coordinates.
     * Note that the points are always mapped onto the bottom and left
     * axes (which may not be the what the user wants).
     */
    axes.x = Blt_GetFirstAxis(graphPtr->axes[0]);
    axes.y = Blt_GetFirstAxis(graphPtr->axes[1]);

    point = Blt_Map2D(graphPtr, x, y, &axes);
    Tcl_AppendElement(interp, Blt_Itoa(ROUND(point.x)));
    Tcl_AppendElement(interp, Blt_Itoa(ROUND(point.y)));
    return TCL_OK;
}

#ifndef NO_PRINTER
/*
 * --------------------------------------------------------------------------
 *
 * Print1Op --
 *
 *	Prints the equivalent of a screen snapshot of the graph
 *	to the designated printer.
 *
 * Results:
 *	Returns a standard Tcl result.  If an error occurred
 *	TCL_ERROR is returned and interp->result will contain an
 *	error message.
 *
 * -------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
Print1Op(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget record */
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int noBackingStore = 0;
    BITMAPINFO info;
    void *data;
    TkWinDCState state;
    TkWinBitmap bitmapDraw;
    DIBSECTION ds;
    Drawable drawable;
    HBITMAP hBitmap;
    HDC hDC;
    DOCINFO di;
    double pageWidth, pageHeight;
    int result;
    double scale, sx, sy;
    int jobId;

    graphPtr->width = Tk_Width(graphPtr->tkwin);
    graphPtr->height = Tk_Height(graphPtr->tkwin);
    if ((graphPtr->width < 2) && (graphPtr->reqWidth > 0)) {
	graphPtr->width = graphPtr->reqWidth;
    }
    if ((graphPtr->height < 2) && (graphPtr->reqHeight > 0)) {
	graphPtr->height = graphPtr->reqHeight;
    }
    if (Blt_GetOpenPrinter(interp, argv[2], &drawable) != TCL_OK) {
	return TCL_ERROR;
    }
    /*  
     * This is a taken from Blt_SnapPhoto.  The difference is that
     * here we're using the DIBSection directly, without converting 
     * the section into a ColorImage.  
     */
    ZeroMemory(&info, sizeof(info));
    info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    info.bmiHeader.biWidth = graphPtr->width;
    info.bmiHeader.biHeight = graphPtr->height;
    info.bmiHeader.biPlanes = 1;
    info.bmiHeader.biBitCount = 32;
    info.bmiHeader.biCompression = BI_RGB;
    hDC = TkWinGetDrawableDC(graphPtr->display, Tk_WindowId(graphPtr->tkwin),
		&state);
    hBitmap = CreateDIBSection(hDC, &info, DIB_RGB_COLORS, &data, NULL, 0);
    TkWinReleaseDrawableDC(Tk_WindowId(graphPtr->tkwin), hDC, &state);
    
    /*
     * Create our own drawable by hand using the DIB we just created.
     * We'll then draw into it using the standard drawing functions.
     */
    bitmapDraw.type = TWD_BITMAP;
    bitmapDraw.handle = hBitmap;
    bitmapDraw.colormap = DefaultColormap(graphPtr->display,
					  DefaultScreen(graphPtr->display));
    bitmapDraw.depth = Tk_Depth(graphPtr->tkwin);
    
    graphPtr->flags |= SET_ALL_FLAGS;
    Blt_DrawGraph(graphPtr, (Drawable)&bitmapDraw, noBackingStore);

    /*
     * Now that the DIB contains the image of the graph, get the the
     * data bits and write them to the printer device, stretching the
     * image to the fit the printer's resolution.
     */
    result = TCL_ERROR;
    if (GetObject(hBitmap, sizeof(DIBSECTION), &ds) == 0) {
	Tcl_AppendResult(interp, "can't get object: ", Blt_LastError(),
	    (char *)NULL);
	goto done;
    }
    hDC = ((TkWinDC *) drawable)->hdc;
    /* Get the resolution of the printer device. */
    sx = (double)GetDeviceCaps(hDC, HORZRES) / (double)graphPtr->width;
    sy = (double)GetDeviceCaps(hDC, VERTRES) / (double)graphPtr->height;
    scale = MIN(sx, sy);
    pageWidth = scale * graphPtr->width;
    pageHeight = scale * graphPtr->height;

    ZeroMemory(&di, sizeof(di));
    di.cbSize = sizeof(di);
    di.lpszDocName = "Graph Contents";
    jobId = StartDoc(hDC, &di);
    if (jobId <= 0) {
	Tcl_AppendResult(interp, "can't start document: ", Blt_LastError(),
	    (char *)NULL);
	goto done;
    }
    if (StartPage(hDC) <= 0) {
	Tcl_AppendResult(interp, "error starting page: ", Blt_LastError(),
	    (char *)NULL);
	goto done;
    }
    StretchDIBits(hDC, 0, 0, ROUND(pageWidth), ROUND(pageHeight), 0, 0, 
	graphPtr->width, graphPtr->height, ds.dsBm.bmBits, 
	(LPBITMAPINFO)&ds.dsBmih, DIB_RGB_COLORS, SRCCOPY);
    EndPage(hDC);
    EndDoc(hDC);
    result = TCL_OK;
  done:
    DeleteBitmap(hBitmap);
    return result;
}

/*
 * --------------------------------------------------------------------------
 *
 * Print2Op --
 *
 *	Prints directly to the designated printer device.
 *
 * Results:
 *	Returns a standard Tcl result.  If an error occurred,
 *	TCL_ERROR is returned and interp->result will contain an
 *	error message.
 *
 * -------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
Print2Op(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget record */
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    Drawable drawable;
    int noBackingStore = 0;
    int width, height;

    graphPtr->width = Tk_Width(graphPtr->tkwin);
    graphPtr->height = Tk_Height(graphPtr->tkwin);
    if ((graphPtr->width < 2) && (graphPtr->reqWidth > 0)) {
	graphPtr->width = graphPtr->reqWidth;
    }
    if ((graphPtr->height < 2) && (graphPtr->reqHeight > 0)) {
	graphPtr->height = graphPtr->reqHeight;
    }
    if (Blt_GetOpenPrinter(interp, argv[2], &drawable) != TCL_OK) {
	return TCL_ERROR;
    } {
	int oldMode;
	HDC hDC;
	double xRatio, yRatio;
	TkWinDC *drawPtr;
	double vportWidth, vportHeight; 

	drawPtr = (TkWinDC *) drawable;
	hDC = drawPtr->hdc;
	Blt_GetPrinterScale(hDC, &xRatio, &yRatio);
	oldMode = SetMapMode(hDC, MM_ISOTROPIC);
	if (oldMode == 0) {
	    Tcl_AppendResult(interp, "can't set mode for printer DC: ",
		Blt_LastError(), (char *)NULL);
	    return TCL_ERROR;
	}
	vportWidth = graphPtr->width * xRatio;
	vportHeight = graphPtr->height * yRatio;
	SetViewportExtEx(hDC, ROUND(vportWidth), ROUND(vportHeight), NULL);
	SetWindowExtEx(hDC, graphPtr->width, graphPtr->height, NULL);
    }
    Blt_StartPrintJob(interp, argv[2]);
    graphPtr->flags |= SET_ALL_FLAGS;
    Blt_DrawGraph(graphPtr, drawable, noBackingStore);
    width = graphPtr->width, height = graphPtr->height;
    Blt_EndPrintJob(interp, argv[2]);
    return TCL_OK;
}

#endif /* NO_PRINTER */

/*
 *----------------------------------------------------------------------
 *
 * StringToFormat --
 *
 *	Convert a string represent a node number into its integer
 *	value.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToFormat(clientData, interp, string, record, offset)
    ClientData clientData;	/* Contains a pointer to the tabset containing
				 * this image. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    char *string;		/* String representation */
    char *record;		/* Structure record */
    int offset;			/* Offset to field in structure */
{
    int *formatPtr = (int *)(record + offset);
    char c;

    c = string[0];
    if ((c == 'p') && (strcmp(string, "photo") == 0)) {
	*formatPtr = FORMAT_PHOTO;
#ifdef WIN32
    } else if ((c == 'e') && (strcmp(string, "emf") == 0)) {
	*formatPtr = FORMAT_EMF;
    } else if ((c == 'w') && (strcmp(string, "wmf") == 0)) {
	*formatPtr = FORMAT_WMF;
#endif /* WIN32 */
    } else {
#ifdef WIN32
	Tcl_AppendResult(interp, "bad format \"", string, 
		 "\": should be photo, emf, or wmf.", (char *)NULL);
#else
	Tcl_AppendResult(interp, "bad format \"", string, 
		 "\": should be photo.", (char *)NULL);
#endif /* WIN32 */
	return TCL_ERROR;
    }
    return TCL_OK;
}

#ifdef WIN32
static int
InitMetaFileHeader(tkwin, width, height, mfhPtr)
    Tk_Window tkwin;
    int width, height;
    ALDUSMFHEADER *mfhPtr;
{
    int *p;
    int sum;
    Screen *screenPtr;
#define MM_INCH (float)25.4
    double dpiX, dpiY;

    mfhPtr->key = 0x9ac6cdd7L;
    mfhPtr->hmf = 0;
    mfhPtr->inch = 1440;

    screenPtr = Tk_Screen(tkwin);
    dpiX = (WidthOfScreen(screenPtr) * MM_INCH) / WidthMMOfScreen(screenPtr);
    dpiY = (HeightOfScreen(screenPtr) * MM_INCH) / HeightMMOfScreen(screenPtr);

    mfhPtr->bbox.left = mfhPtr->bbox.top = 0;
    width *= 1440;
    height *= 1440;
    mfhPtr->bbox.bottom = (short)(width / dpiX);
    mfhPtr->bbox.right = (short)(height / dpiY);
    mfhPtr->reserved = 0;
    sum = 0;
    for (p = (int *)mfhPtr; p < (int *)&(mfhPtr->checksum); p++) {
	sum ^= *p;
    }
    mfhPtr->checksum = sum;
    return TCL_OK;
}

static int
CreateAPMetaFile(interp, hMetaFile, hDC, mfhPtr, fileName)
    Tcl_Interp *interp;
    HANDLE hMetaFile;
    HDC hDC;
    ALDUSMFHEADER *mfhPtr;
    char *fileName;
{
    HANDLE hFile;
    HANDLE hMem;
    LPVOID buffer;
    int nBytes, result;
    int count;

    result = TCL_ERROR;
    hMem = NULL;
    hFile = CreateFile(
       fileName,	/* File path */
       GENERIC_WRITE,	/* Access mode */
       0,		/* No sharing. */
       NULL,		/* Security attributes */
       CREATE_ALWAYS,	/* Overwrite any existing file */
       FILE_ATTRIBUTE_NORMAL,
       NULL);			/* No template file */
    if (hFile == INVALID_HANDLE_VALUE) {
	Tcl_AppendResult(interp, "can't create metafile \"", fileName, 
		"\":", Blt_LastError(), (char *)NULL);
	return TCL_ERROR;
    }
    if ((!WriteFile(hFile, (LPVOID)mfhPtr, sizeof(ALDUSMFHEADER), &count, 
		NULL)) || (count != sizeof(ALDUSMFHEADER))) {
	Tcl_AppendResult(interp, "can't create metafile header to \"", 
			 fileName, "\":", Blt_LastError(), (char *)NULL);
	goto error;
    }
    nBytes = GetWinMetaFileBits(hMetaFile, 0, NULL, MM_ANISOTROPIC, hDC);
    hMem = GlobalAlloc(GHND, nBytes);
    if (hMem == NULL) {
	Tcl_AppendResult(interp, "can't create allocate global memory:", 
		Blt_LastError(), (char *)NULL);
	goto error;
    }
    buffer = (LPVOID)GlobalLock(hMem);
    if (!GetWinMetaFileBits(hMetaFile, nBytes, buffer, MM_ANISOTROPIC, hDC)) {
	Tcl_AppendResult(interp, "can't get metafile bits:", 
		Blt_LastError(), (char *)NULL);
	goto error;
    }
    if ((!WriteFile(hFile, buffer, nBytes, &count, NULL)) ||
	(count != nBytes)) {
	Tcl_AppendResult(interp, "can't write metafile bits:", 
		Blt_LastError(), (char *)NULL);
	goto error;
    }
    result = TCL_OK;
 error:
    CloseHandle(hFile);
    if (hMem != NULL) {
	GlobalUnlock(hMem);
	GlobalFree(hMem);
    }
    return result;
}
#endif /*WIN32*/

/*
 * --------------------------------------------------------------------------
 *
 * SnapOp --
 *
 *	Snaps a picture of the graph and stores it in the specified image
 *
 * Results:
 *	Returns a standard Tcl result.  interp->result contains
 *	the list of the graph coordinates. If an error occurred
 *	while parsing the window positions, TCL_ERROR is returned,
 *	then interp->result will contain an error message.
 *
 * -------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SnapOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;		/* Graph widget record */
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char **argv;
{
    int result, count;
    Pixmap drawable;
    int noBackingStore = 0;
    register int i;
    SnapData data;

    /* .g snap ?switches? name */
    /* Count switch arguments */
    for (i = 2; i < argc; i += 2) {
	if (argv[i][0] != '-') {
	    break;
	}
    }
    if (i >= argc) {
	Tcl_AppendResult(interp, "missing name argument: should be \"",
		 argv[0], "snap ?switches? name\"", (char *)NULL);
	return TCL_ERROR;
    }
    count = i - 2;
    data.name = argv[i];
    data.height = Tk_Height(graphPtr->tkwin);
    data.width = Tk_Width(graphPtr->tkwin);
    data.format = FORMAT_PHOTO;

    /* Process switches  */
    if (Blt_ProcessSwitches(interp, snapSwitches, count, argv + 2, 
	     (char *)&data, 0) != TCL_OK) {
	return TCL_ERROR;
    }
    if (data.width < 2) {
	data.width = 400;
    }
    if (data.height < 2) {
	data.height = 400;
    }
    /* Always re-compute the layout of the graph before snapping the photo. */
    graphPtr->width = data.width;
    graphPtr->height = data.height;
    Blt_LayoutGraph(graphPtr);

    result = TCL_ERROR;
    drawable = Tk_WindowId(graphPtr->tkwin);
    if (data.format == FORMAT_PHOTO) {
	drawable = Tk_GetPixmap(graphPtr->display, drawable, graphPtr->width, 
		graphPtr->height, Tk_Depth(graphPtr->tkwin));
#ifdef WIN32
	assert(drawable != None);
#endif
	graphPtr->flags |= SET_ALL_FLAGS;
	Blt_DrawGraph(graphPtr, drawable, noBackingStore);
	result = Blt_SnapPhoto(interp, graphPtr->tkwin, drawable, data.width, 
	       data.height, data.width, data.height, data.name);
	Tk_FreePixmap(graphPtr->display, drawable);
#ifdef WIN32
    } else {
	TkWinDC drawableDC;
	TkWinDCState state;
	HDC hRefDC, hDC;
	HENHMETAFILE hMetaFile;
	Tcl_DString dString;
	char *title;

	hRefDC = TkWinGetDrawableDC(graphPtr->display, drawable, &state);

	Tcl_DStringInit(&dString);
	Tcl_DStringAppend(&dString, "BLT Graph ", -1);
	Tcl_DStringAppend(&dString, BLT_VERSION, -1);
	Tcl_DStringAppend(&dString, "\0", -1);
	Tcl_DStringAppend(&dString, Tk_PathName(graphPtr->tkwin), -1);
	Tcl_DStringAppend(&dString, "\0", -1);
	title = Tcl_DStringValue(&dString);
	hDC = CreateEnhMetaFile(hRefDC, NULL, NULL, title);
	Tcl_DStringFree(&dString);
	
	if (hDC == NULL) {
	    Tcl_AppendResult(interp, "can't create metafile: ",
		     Blt_LastError(), (char *)NULL);
	    return TCL_ERROR;
	}

	drawableDC.hdc = hDC;
	drawableDC.type = TWD_WINDC;

	Blt_LayoutGraph(graphPtr);
	graphPtr->flags |= SET_ALL_FLAGS;
	Blt_DrawGraph(graphPtr, (Drawable)&drawableDC, FALSE);

	hMetaFile = CloseEnhMetaFile(hDC);
	if (strcmp(data.name, "CLIPBOARD") == 0) {
	    HWND hWnd;
	    
	    hWnd = Tk_GetHWND(drawable);
	    OpenClipboard(hWnd);
	    EmptyClipboard();
	    SetClipboardData(CF_ENHMETAFILE, hMetaFile);
	    CloseClipboard();
	    result = TCL_OK;
	} else {
	    result = TCL_ERROR;
	    if (data.format == FORMAT_WMF) {
		ALDUSMFHEADER mfh;

		assert(sizeof(mfh) == 22);
		InitMetaFileHeader(graphPtr->tkwin, data.width, data.height, 
			&mfh);
		result = CreateAPMetaFile(interp, hMetaFile, hRefDC, &mfh, 
			data.name);
	    } else {
		HENHMETAFILE hMetaFile2;

		hMetaFile2 = CopyEnhMetaFile(hMetaFile, data.name);
		if (hMetaFile2 != NULL) {
		    result = TCL_OK;
		    DeleteEnhMetaFile(hMetaFile2); 
		}
	    }
	    DeleteEnhMetaFile(hMetaFile); 
	}
	TkWinReleaseDrawableDC(drawable, hRefDC, &state);
#endif /*WIN32*/
    }
    graphPtr->flags = COORDS_WORLD;
    Blt_EventuallyRedrawGraph(graphPtr);
    return result;
}

/*
 * --------------------------------------------------------------------------
 *
 * GraphWidgetCmd --
 *
 *	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.
 *
 * --------------------------------------------------------------------------
 */
static Blt_OpSpec graphOps[] =
{
    {"axis", 1, (Blt_OpProc)Blt_VirtualAxisOp, 2, 0, "oper ?args?",},
    {"bar", 2, (Blt_OpProc)BarOp, 2, 0, "oper ?args?",},
    {"cget", 2, (Blt_OpProc)CgetOp, 3, 3, "option",},
    {"configure", 2, (Blt_OpProc)ConfigureOp, 2, 0, "?option value?...",},
    {"crosshairs", 2, (Blt_OpProc)Blt_CrosshairsOp, 2, 0, "oper ?args?",},
    {"element", 2, (Blt_OpProc)ElementOp, 2, 0, "oper ?args?",},
    {"extents", 2, (Blt_OpProc)ExtentsOp, 3, 3, "item",},
    {"grid", 1, (Blt_OpProc)Blt_GridOp, 2, 0, "oper ?args?",},
    {"inside", 3, (Blt_OpProc)InsideOp, 4, 4, "winX winY",},
    {"invtransform", 3, (Blt_OpProc)InvtransformOp, 4, 4, "winX winY",},
    {"legend", 2, (Blt_OpProc)Blt_LegendOp, 2, 0, "oper ?args?",},
    {"line", 2, (Blt_OpProc)LineOp, 2, 0, "oper ?args?",},
    {"marker", 2, (Blt_OpProc)Blt_MarkerOp, 2, 0, "oper ?args?",},
    {"pen", 2, (Blt_OpProc)Blt_PenOp, 2, 0, "oper ?args?",},
    {"postscript", 2, (Blt_OpProc)Blt_PostScriptOp, 2, 0, "oper ?args?",},
#ifndef NO_PRINTER
    {"print1", 2, (Blt_OpProc)Print1Op, 3, 3, "printerId",},
    {"print2", 2, (Blt_OpProc)Print2Op, 3, 3, "printerId",},
#endif /*NO_PRINTER*/
    {"snap", 1, (Blt_OpProc)SnapOp, 3, 0, "?switches? name",},
    {"transform", 1, (Blt_OpProc)TransformOp, 4, 4, "x y",},
    {"x2axis", 2, (Blt_OpProc)X2AxisOp, 2, 0, "oper ?args?",},
    {"xaxis", 2, (Blt_OpProc)XAxisOp, 2, 0, "oper ?args?",},
    {"y2axis", 2, (Blt_OpProc)Y2AxisOp, 2, 0, "oper ?args?",},
    {"yaxis", 2, (Blt_OpProc)YAxisOp, 2, 0, "oper ?args?",},
};
static int nGraphOps = sizeof(graphOps) / sizeof(Blt_OpSpec);

static int
GraphInstCmd(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;
    Graph *graphPtr = (Graph *)clientData;

    opProc = Blt_GetOperation(interp, nGraphOps, graphOps, BLT_OPER_ARG1,
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    Tcl_Preserve((ClientData)graphPtr);
    result = (*opProc) (graphPtr, interp, argc, argv);
    Tcl_Release((ClientData)graphPtr);
    return result;
}

/*
 * --------------------------------------------------------------------------
 *
 * NewGraph --
 *
 *	Creates a new window and Tcl command representing an
 *	instance of a graph widget.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 * --------------------------------------------------------------------------
 */
static int
NewGraph(interp, argc, argv, type)
    Tcl_Interp *interp;
    int argc;
    char **argv;
    ObjectType type;
{
    Tk_Window tkwin;
    Graph *graphPtr;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	    " pathName ?option value?...\"", (char *)NULL);
	return TCL_ERROR;
    }
    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), argv[1], 
	    (char *)NULL);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    graphPtr = CreateGraph(interp, tkwin, argc, argv, type);
    if (graphPtr == NULL) {
	Tk_DestroyWindow(tkwin);
	return TCL_ERROR;
    }
    Tcl_SetResult(interp, Tk_PathName(tkwin), TCL_STATIC);
    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * GraphCmd --
 *
 *	Creates a new window and Tcl command representing an
 *	instance of a graph widget.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 * --------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
GraphCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    return NewGraph(interp, argc, argv, TYPE_ELEM_LINE);
}

/*
 *--------------------------------------------------------------
 *
 * BarchartCmd --
 *
 *	Creates a new window and Tcl command representing an
 *	instance of a barchart widget.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */
/*ARGSUSED*/
static int
BarchartCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    return NewGraph(interp, argc, argv, TYPE_ELEM_BAR);
}

/*
 *--------------------------------------------------------------
 *
 * StripchartCmd --
 *
 *	Creates a new window and Tcl command representing an
 *	instance of a barchart widget.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StripchartCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    return NewGraph(interp, argc, argv, TYPE_ELEM_STRIP);
}

/*
 * -----------------------------------------------------------------------
 *
 * DrawMargins --
 *
 * 	Draws the exterior region of the graph (axes, ticks, titles, etc)
 *	onto a pixmap. The interior region is defined by the given
 *	rectangle structure.
 *
 *	---------------------------------
 *      |                               |
 *      |           rectArr[0]          |
 *      |                               |
 *	---------------------------------
 *      |     |top           right|     |
 *      |     |                   |     |
 *      |     |                   |     |
 *      | [1] |                   | [2] |
 *      |     |                   |     |
 *      |     |                   |     |
 *      |     |                   |     |
 *      |     |                   |     |
 *      |     |                   |     |
 *      |     |left         bottom|     |
 *	---------------------------------
 *      |                               |
 *      |          rectArr[3]           |
 *      |                               |
 *	---------------------------------
 *
 *		X coordinate axis
 *		Y coordinate axis
 *		legend
 *		interior border
 *		exterior border
 *		titles (X and Y axis, graph)
 *
 * Returns:
 *	None.
 *
 * Side Effects:
 *	Exterior of graph is displayed in its window.
 *
 * -----------------------------------------------------------------------
 */
static void
DrawMargins(graphPtr, drawable)
    Graph *graphPtr;
    Drawable drawable;		/* Pixmap or window to draw into */
{
    XRectangle rectArr[4];
    LegendSite site;

    /*
     * Draw the four outer rectangles which encompass the plotting
     * surface. This clears the surrounding area and clips the plot.
     */
    rectArr[0].x = rectArr[0].y = rectArr[3].x = rectArr[1].x = 0;
    rectArr[0].width = rectArr[3].width = (short int)graphPtr->width;
    rectArr[0].height = (short int)graphPtr->top;
    rectArr[3].y = graphPtr->bottom;
    rectArr[3].height = graphPtr->height - graphPtr->bottom;
    rectArr[2].y = rectArr[1].y = graphPtr->top;
    rectArr[1].width = graphPtr->left;
    rectArr[2].height = rectArr[1].height = graphPtr->bottom - graphPtr->top;
    rectArr[2].x = graphPtr->right;
    rectArr[2].width = graphPtr->width - graphPtr->right;

    if (graphPtr->tile != NULL) {
	Blt_SetTileOrigin(graphPtr->tkwin, graphPtr->tile, 0, 0);
	Blt_TileRectangles(graphPtr->display, drawable, graphPtr->tile, 
		rectArr, 4);
    } else {
	XFillRectangles(graphPtr->display, drawable, graphPtr->fillGC, 
		rectArr, 4);
    }

    /* Draw 3D border around the plotting area */

    if (graphPtr->plotBW > 0) {
	int x, y, width, height;

	x = graphPtr->left - graphPtr->plotBW;
	y = graphPtr->top - graphPtr->plotBW;
	width = (graphPtr->right - graphPtr->left) + (2 * graphPtr->plotBW);
	height = (graphPtr->bottom - graphPtr->top) + (2 * graphPtr->plotBW);
	Tk_Draw3DRectangle(graphPtr->tkwin, drawable, graphPtr->border,
	    x, y, width, height, graphPtr->plotBW, graphPtr->plotRelief);
    }
    site = GetLegendSite(graphPtr);
    if (site < LEGEND_PLOT) {
	/* Legend is drawn on one of the graph margins */
	Blt_DrawLegend(graphPtr, drawable);
    }
    if (graphPtr->titleText != NULL) {
	Blt_DrawText(graphPtr->tkwin, drawable, graphPtr->titleText,
	    &(graphPtr->titleStyle), graphPtr->titleX, graphPtr->titleY);
    }
    Blt_DrawAxes(graphPtr, drawable);

}

/*
 *----------------------------------------------------------------------
 *
 * DrawPlotRegion --
 *
 *	Draws the contents of the plotting area.  This consists of
 *	the elements, markers (draw under elements), axis limits,
 *	grid lines, and possibly the legend.  Typically, the output
 *	will be cached into a backing store pixmap, so that redraws
 *	can occur quickly.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static void
DrawPlotRegion(graphPtr, drawable)
    Graph *graphPtr;
    Drawable drawable;		/* Pixmap or window to draw into */
{
    LegendSite site;
#ifdef notdef
    fprintf(stderr, "Calling DrawPlotRegion(%s)\n",
	Tk_PathName(graphPtr->tkwin));
#endif
    /* Clear the background of the plotting area. */
    XFillRectangle(graphPtr->display, drawable, graphPtr->plotFillGC,
	graphPtr->left, graphPtr->top, graphPtr->right - graphPtr->left + 1,
	graphPtr->bottom - graphPtr->top + 1);

    /* Draw the elements, markers, legend, and axis limits. */

    if (!graphPtr->gridPtr->hidden) {
	Blt_DrawGrid(graphPtr, drawable);
    }
    site = GetLegendSite(graphPtr);
    Blt_DrawMarkers(graphPtr, drawable, MARKER_UNDER);
    if (((site == LEGEND_XY) || (site == LEGEND_PLOT)) &&
	(!graphPtr->legendPtr->raised)) {
	Blt_DrawLegend(graphPtr, drawable);
    }
    Blt_DrawAxisLimits(graphPtr, drawable);
    Blt_DrawElements(graphPtr, drawable);
}

void
Blt_LayoutGraph(graphPtr)
    Graph *graphPtr;
{
    if (graphPtr->flags & RESET_AXES) {
	Blt_ResetAxes(graphPtr);
    }
    if (graphPtr->flags & LAYOUT_NEEDED) {
	Blt_LayoutMargins(graphPtr);
	graphPtr->flags &= ~LAYOUT_NEEDED;
    }
    /* Compute coordinate transformations for graph components */
    if ((graphPtr->vRange <= 1) || (graphPtr->hRange <= 1)) {
	return;
    }
    if (graphPtr->flags & COORDS_WORLD) {
	Blt_MapAxes(graphPtr);
    }
    Blt_MapElements(graphPtr);
    Blt_MapMarkers(graphPtr);
    Blt_MapGrid(graphPtr);
    graphPtr->flags &= ~(COORDS_ALL_PARTS);
}

void
Blt_DrawGraph(graphPtr, drawable, backingStore)
    Graph *graphPtr;
    Drawable drawable;		/* Pixmap or window to draw into */
    int backingStore;		/* If non-zero, use backing store for
				 * plotting area. */
{
    LegendSite site;
#ifdef notdef
    fprintf(stderr, "Calling Blt_DrawGraph(%s)\n", Tk_PathName(graphPtr->tkwin));
#endif
    if (backingStore) {
	if ((graphPtr->backPixmap == None) ||
	    (graphPtr->backWidth != graphPtr->width) ||
	    (graphPtr->backHeight != graphPtr->height)) {

	    /*
	     * Create another pixmap to save elements if one doesn't
	     * already exist or the size of the window has changed.
	     */
	    if (graphPtr->backPixmap != None) {
		Tk_FreePixmap(graphPtr->display, graphPtr->backPixmap);
	    }
	    graphPtr->backPixmap = Tk_GetPixmap(graphPtr->display,
		Tk_WindowId(graphPtr->tkwin), graphPtr->width, 
		graphPtr->height, Tk_Depth(graphPtr->tkwin));
	    graphPtr->backWidth = graphPtr->width;
	    graphPtr->backHeight = graphPtr->height;
	    graphPtr->flags |= REDRAW_BACKING_STORE;
	}
	if (graphPtr->flags & REDRAW_BACKING_STORE) {

	    /* The backing store is new or out-of-date. */

	    DrawPlotRegion(graphPtr, graphPtr->backPixmap);
	    graphPtr->flags &= ~REDRAW_BACKING_STORE;
	}
	/* Copy the pixmap to the one used for drawing the entire graph. */
	XCopyArea(graphPtr->display, graphPtr->backPixmap, drawable,
	    graphPtr->drawGC, graphPtr->left, graphPtr->top,
	    (graphPtr->right - graphPtr->left + 1),
	    (graphPtr->bottom - graphPtr->top + 1),
	    graphPtr->left, graphPtr->top);
    } else {
	DrawPlotRegion(graphPtr, drawable);
    }

    /* Draw markers above elements */
    Blt_DrawMarkers(graphPtr, drawable, MARKER_ABOVE);
    Blt_DrawActiveElements(graphPtr, drawable);

    if (graphPtr->flags & REDRAW_MARGINS) {
	DrawMargins(graphPtr, drawable);
    }
    site = GetLegendSite(graphPtr);
    if (((site == LEGEND_XY) || (site == LEGEND_PLOT)) &&
	(graphPtr->legendPtr->raised)) {
	Blt_DrawLegend(graphPtr, drawable);
    }
    /* Draw 3D border just inside of the focus highlight ring. */
    if ((graphPtr->borderWidth > 0) && (graphPtr->relief != TK_RELIEF_FLAT)) {
	Tk_Draw3DRectangle(graphPtr->tkwin, drawable, graphPtr->border, 
	    graphPtr->highlightWidth, graphPtr->highlightWidth,
	    graphPtr->width - 2 * graphPtr->highlightWidth, 
	    graphPtr->height - 2 * graphPtr->highlightWidth, 
	    graphPtr->borderWidth, graphPtr->relief);
    }
    /* Draw focus highlight ring. */
    if ((graphPtr->highlightWidth > 0) && (graphPtr->flags & GRAPH_FOCUS)) {
	GC gc;

	gc = Tk_GCForColor(graphPtr->highlightColor, drawable);
	Tk_DrawFocusHighlight(graphPtr->tkwin, gc, graphPtr->highlightWidth,
	    drawable);
    }
}

static void
UpdateMarginTraces(graphPtr)
    Graph *graphPtr;
{
    Margin *marginPtr;
    int size;
    register int i;

    for (i = 0; i < 4; i++) {
	marginPtr = graphPtr->margins + i;
	if (marginPtr->varName != NULL) {	/* Trigger variable traces */
	    if ((marginPtr->site == MARGIN_LEFT) || 
		(marginPtr->site == MARGIN_RIGHT)) {
		size = marginPtr->width;
	    } else {
		size = marginPtr->height;
	    }
	    Tcl_SetVar(graphPtr->interp, marginPtr->varName, Blt_Itoa(size), 
		TCL_GLOBAL_ONLY);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DisplayGraph --
 *
 *	This procedure is invoked to display a graph widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the graph in its
 *	current mode.
 *
 *----------------------------------------------------------------------
 */
static void
DisplayGraph(clientData)
    ClientData clientData;
{
    Graph *graphPtr = (Graph *)clientData;
    Pixmap drawable;

    graphPtr->flags &= ~REDRAW_PENDING;
    if (graphPtr->tkwin == NULL) {
	return;			/* Window destroyed (should not get here) */
    }
#ifdef notdef
    fprintf(stderr, "Calling DisplayGraph(%s)\n", Tk_PathName(graphPtr->tkwin));
#endif
    if (Blt_GraphUpdateNeeded(graphPtr)) {
	/*
	 * One of the elements of the graph has a vector notification
	 * pending.  This means that the vector will eventually notify
	 * the graph that its data has changed.  Since the graph uses
	 * the actual vector (not a copy) we need to keep in-sync.
	 * Therefore don't draw right now but wait until we've been
	 * notified before redrawing.
	 */
	return;
    }
    graphPtr->width = Tk_Width(graphPtr->tkwin);
    graphPtr->height = Tk_Height(graphPtr->tkwin);
    if (graphPtr->flags & RESET_AXES) {
	Blt_ResetAxes(graphPtr);
    }
    Blt_LayoutGraph(graphPtr);

    if (!Tk_IsMapped(graphPtr->tkwin)) {
	/* The graph's window isn't displayed, so don't bother
	 * drawing anything.  By getting this far, we've at least
	 * computed the coordinates of the graph's new layout.  */
	return;
    }
    /*
     * Create a pixmap the size of the window for double buffering.
     */
    drawable = Tk_GetPixmap(graphPtr->display, Tk_WindowId(graphPtr->tkwin),
	graphPtr->width, graphPtr->height, Tk_Depth(graphPtr->tkwin));
#ifdef WIN32
    assert(drawable != None);
#endif
    Blt_DrawGraph(graphPtr, drawable, graphPtr->backingStore);

    /* Disable crosshairs before redisplaying to the screen */
    Blt_DisableCrosshairs(graphPtr);
    Blt_UpdateCrosshairs(graphPtr);

    if (graphPtr->flags & REDRAW_MARGINS) {
	XCopyArea(graphPtr->display, drawable, Tk_WindowId(graphPtr->tkwin),
	    graphPtr->drawGC, 0, 0, graphPtr->width, graphPtr->height, 0, 0);
    } else {
	XCopyArea(graphPtr->display, drawable, Tk_WindowId(graphPtr->tkwin),
		  graphPtr->drawGC, graphPtr->left, graphPtr->top,
		  (graphPtr->right - graphPtr->left + 1), 
		  (graphPtr->bottom - graphPtr->top + 1),
		  graphPtr->left, graphPtr->top);
    }
    Blt_EnableCrosshairs(graphPtr);

    Tk_FreePixmap(graphPtr->display, drawable);

    graphPtr->flags &= ~SET_ALL_FLAGS;
    UpdateMarginTraces(graphPtr);
}

int
Blt_GraphInit(interp)
    Tcl_Interp *interp;
{
    static Blt_CmdSpec cmdSpecs[] =
    {
	{"graph", GraphCmd,},
	{"barchart", BarchartCmd,},
	{"stripchart", StripchartCmd,},
    };
    return Blt_InitCmds(interp, "blt", cmdSpecs, 3);
}

Graph *
Blt_GetGraphFromWindowData(tkwin)
    Tk_Window tkwin;
{
    Graph *graphPtr;

    while (tkwin != NULL) {
	graphPtr = (Graph *)Blt_GetWindowInstanceData(tkwin);
	if (graphPtr != NULL) {
	    return graphPtr;
	}
	tkwin = Tk_Parent(tkwin);
    }
    return NULL;
}
