
/*
 * bltGrLegd.c --
 *
 *	This module implements the legend 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 "bltGrElem.h"

#define padLeft  	padX.side1
#define padRight  	padX.side2
#define padTop		padY.side1
#define padBottom	padY.side2
#define PADDING(x)	((x).side1 + (x).side2)

#define DEF_LEGEND_ACTIVE_BG_COLOR 	STD_COLOR_ACTIVE_BG
#define DEF_LEGEND_ACTIVE_BG_MONO	STD_MONO_ACTIVE_BG
#define DEF_LEGEND_ACTIVE_BORDER_WIDTH  "2"
#define DEF_LEGEND_ACTIVE_FG_COLOR	STD_COLOR_ACTIVE_FG
#define DEF_LEGEND_ACTIVE_FG_MONO	STD_MONO_ACTIVE_FG
#define DEF_LEGEND_ACTIVE_RELIEF	"flat"
#define DEF_LEGEND_ANCHOR	   	"n"
#define DEF_LEGEND_BG_COLOR	   	(char *)NULL
#define DEF_LEGEND_BG_MONO		(char *)NULL
#define DEF_LEGEND_BORDER_WIDTH 	STD_BORDERWIDTH
#define DEF_LEGEND_FG_COLOR		STD_COLOR_NORMAL_FG
#define DEF_LEGEND_FG_MONO		STD_MONO_NORMAL_FG
#define DEF_LEGEND_FONT			STD_FONT_SMALL
#define DEF_LEGEND_HIDE			"no"
#define DEF_LEGEND_IPAD_X		"1"
#define DEF_LEGEND_IPAD_Y		"1"
#define DEF_LEGEND_PAD_X		"1"
#define DEF_LEGEND_PAD_Y		"1"
#define DEF_LEGEND_POSITION		"rightmargin"
#define DEF_LEGEND_RAISED       	"no"
#define DEF_LEGEND_RELIEF		"sunken"
#define DEF_LEGEND_SHADOW_COLOR		(char *)NULL
#define DEF_LEGEND_SHADOW_MONO		(char *)NULL

static int StringToPosition _ANSI_ARGS_((ClientData, Tcl_Interp *, Tk_Window,
	char *, char *, int));
static char *PositionToString _ANSI_ARGS_((ClientData, Tk_Window, char *, int,
	Tcl_FreeProc **));

static Tk_CustomOption legendPositionOption =
{
    StringToPosition, PositionToString, (ClientData)0
};

extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltPadOption;
extern Tk_CustomOption bltShadowOption;

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground",
	"ActiveBackground", DEF_LEGEND_ACTIVE_BG_COLOR,
	Tk_Offset(Legend, activeBorder), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground",
	"ActiveBackground", DEF_LEGEND_ACTIVE_BG_MONO,
	Tk_Offset(Legend, activeBorder), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-activeborderwidth", "activeBorderWidth",
	"BorderWidth", DEF_LEGEND_BORDER_WIDTH, Tk_Offset(Legend, entryBW),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground",
	"ActiveForeground", DEF_LEGEND_ACTIVE_FG_COLOR,
	Tk_Offset(Legend, style.activeColor), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-activeforeground", "activeForeground",
	"ActiveForeground", DEF_LEGEND_ACTIVE_FG_MONO,
	Tk_Offset(Legend, style.activeColor), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_RELIEF, "-activerelief", "activeRelief", "Relief",
	DEF_LEGEND_ACTIVE_RELIEF, Tk_Offset(Legend, activeRelief),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
	DEF_LEGEND_ANCHOR, Tk_Offset(Legend, anchor),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_LEGEND_BG_MONO, Tk_Offset(Legend, border),
	TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_LEGEND_BG_COLOR, Tk_Offset(Legend, border),
	TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_LEGEND_BORDER_WIDTH, Tk_Offset(Legend, borderWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_FONT, "-font", "font", "Font",
	DEF_LEGEND_FONT, Tk_Offset(Legend, style.font), 0},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_LEGEND_FG_COLOR, Tk_Offset(Legend, style.color),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_LEGEND_FG_MONO, Tk_Offset(Legend, style.color),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide",
	DEF_LEGEND_HIDE, Tk_Offset(Legend, hidden), TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-ipadx", "iPadX", "Pad",
	DEF_LEGEND_IPAD_X, Tk_Offset(Legend, ipadX),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-ipady", "iPadY", "Pad",
	DEF_LEGEND_IPAD_Y, Tk_Offset(Legend, ipadY),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-padx", "padX", "Pad",
	DEF_LEGEND_PAD_X, Tk_Offset(Legend, padX),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-pady", "padY", "Pad",
	DEF_LEGEND_PAD_Y, Tk_Offset(Legend, padY),
	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-position", "position", "Position",
	DEF_LEGEND_POSITION, Tk_Offset(Legend, anchorPos),
	TK_CONFIG_DONT_SET_DEFAULT, &legendPositionOption},
    {TK_CONFIG_BOOLEAN, "-raised", "raised", "Raised",
	DEF_LEGEND_RAISED, Tk_Offset(Legend, raised),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_LEGEND_RELIEF, Tk_Offset(Legend, relief),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-shadow", "shadow", "Shadow",
	DEF_LEGEND_SHADOW_COLOR, Tk_Offset(Legend, style.shadow),
	TK_CONFIG_COLOR_ONLY, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-shadow", "shadow", "Shadow",
	DEF_LEGEND_SHADOW_MONO, Tk_Offset(Legend, style.shadow),
	TK_CONFIG_MONO_ONLY, &bltShadowOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

#ifdef __STDC__
static Tcl_IdleProc DrawLegend;
static BindPickProc PickLegendEntry;
#endif

static int
GetLegendPosition(interp, string, posPtr)
    Tcl_Interp *interp;
    char *string;
    LegendPosition *posPtr;
{
    char c;
    unsigned int length;

    if ((string == NULL) || (*string == '\0')) {
	posPtr->site = LEGEND_RIGHT;
	return TCL_OK;		/* Position marked as default */
    }
    c = string[0];
    length = strlen(string);
    if ((c == 'l') && (strncmp(string, "leftmargin", length) == 0)) {
	posPtr->site = LEGEND_LEFT;
    } else if ((c == 'r') && (strncmp(string, "rightmargin", length) == 0)) {
	posPtr->site = LEGEND_RIGHT;
    } else if ((c == 't') && (strncmp(string, "topmargin", length) == 0)) {
	posPtr->site = LEGEND_TOP;
    } else if ((c == 'b') && (strncmp(string, "bottommargin", length) == 0)) {
	posPtr->site = LEGEND_BOTTOM;
    } else if ((c == 'p') && (strncmp(string, "plotarea", length) == 0)) {
	posPtr->site = LEGEND_PLOT;
    } else {
	if (c == '@') {
	    char *comma;
	    
	    string++;
	    comma = strchr(string, ',');
	    if (comma != NULL) {
		long x, y;
		int result;

		x = y = 0;
		*comma = '\0';
		result = ((Tcl_ExprLong(interp, string, &x) == TCL_OK) &&
			  (Tcl_ExprLong(interp, comma + 1, &y) == TCL_OK));
		*comma = ',';
		if (!result) {
		    return TCL_ERROR;
		}
		posPtr->x = (int)x, posPtr->y = (int)y;
		posPtr->site = LEGEND_XY;
		return TCL_OK;
	    }
	}
	Tcl_AppendResult(interp, "bad position \"", string, "\": should be  \
\"leftmargin\", \"rightmargin\", \"topmargin\", \"bottommargin\", \
\"plotarea\", or @x,y", (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToPosition --
 *
 *	Convert the string representation of a legend XY position into
 *	window coordinates.  The form of the string must be "@x,y" or
 *	none.
 *
 * Results:
 *	The return value is a standard Tcl result.  The symbol type is
 *	written into the widget record.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToPosition(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;		/* New legend position string */
    char *widgRec;		/* Widget record */
    int offset;			/* offset to XPoint structure */
{
    LegendPosition *posPtr = (LegendPosition *)(widgRec + offset);

    if (GetLegendPosition(interp, string, posPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PositionToString --
 *
 *	Convert the window coordinates into a string.
 *
 * Results:
 *	The string representing the coordinate position is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
PositionToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Widget record */
    int offset;			/* offset of XPoint in record */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    LegendPosition *posPtr = (LegendPosition *)(widgRec + offset);
    char string[200];
    char *result;

    switch (posPtr->site) {
    case LEGEND_LEFT:
	return "leftmargin";
    case LEGEND_RIGHT:
	return "rightmargin";
    case LEGEND_TOP:
	return "topmargin";
    case LEGEND_BOTTOM:
	return "bottommargin";
    case LEGEND_PLOT:
	return "plotarea";
    case LEGEND_XY:
	sprintf(string, "@%d,%d", posPtr->x, posPtr->y);
	result = strdup(string);
	*freeProcPtr = (Tcl_FreeProc *)free;
	return result;
    default:
	return "unknown legend position";
    }
}

static void
SetLegendOrigin(graphPtr)
    Graph *graphPtr;
{
    Legend *legendPtr = graphPtr->legendPtr;
    int extra;
    int x, y, w, h;

    x = y = w = h = 0;		/* Suppress compiler warning. */
    switch (legendPtr->anchorPos.site) {
    case LEGEND_RIGHT:
	extra = graphPtr->rightMargin.width - graphPtr->rightMargin.size;
	w = legendPtr->width + extra;
	h = (graphPtr->bottom - graphPtr->top);
	x = graphPtr->width - graphPtr->inset - w - 1;
	y = graphPtr->top;
	break;
    case LEGEND_LEFT:
	extra = graphPtr->leftMargin.width - graphPtr->leftMargin.size;
	w = legendPtr->width + extra;
	h = (graphPtr->bottom - graphPtr->top);
	x = graphPtr->inset, y = graphPtr->top;
	break;
    case LEGEND_TOP:
	extra = graphPtr->topMargin.height - graphPtr->topMargin.size;
	w = (graphPtr->right - graphPtr->left);
	h = legendPtr->height + extra;
	x = graphPtr->left, y = graphPtr->inset;
	if (graphPtr->titleText != NULL) {
	    y += graphPtr->titleStyle.height;
	}
	break;
    case LEGEND_BOTTOM:
	extra = graphPtr->bottomMargin.height - graphPtr->bottomMargin.size;
	w = (graphPtr->right - graphPtr->left);
	h = legendPtr->height + extra;
	x = graphPtr->left;
	y = graphPtr->height - graphPtr->inset - legendPtr->height;
	break;
    case LEGEND_PLOT:
	w = (graphPtr->right - graphPtr->left), h = (graphPtr->bottom - graphPtr->top);
	x = graphPtr->left, y = graphPtr->top;
	break;
    case LEGEND_XY:
	w = legendPtr->width, h = legendPtr->height;
	x = legendPtr->anchorPos.x, y = legendPtr->anchorPos.y;
	if (x < 0) {
	    x += graphPtr->width;
	}
	if (y < 0) {
	    y += graphPtr->height;
	}
	break;
    }
    w = legendPtr->width - w;
    h = legendPtr->height - h;
    Blt_TranslateAnchor(x, y, w, h, legendPtr->anchor, &x, &y);
#ifdef notdef
    if ((legendPtr->anchorPos.site == LEGEND_XY) && 
	((graphPtr->width != Tk_Width(graphPtr->tkwin)) ||
	 (graphPtr->height != Tk_Height(graphPtr->tkwin)))) {
	double scale;
	/*
	 * Legend position was given in window coordinates so we have to
	 * scale using the current page coordinates.
	 */
	scale = (double)graphPtr->width / Tk_Width(graphPtr->tkwin);
	x = (int)(legendPtr->anchorPos.x * scale);
	scale = (double)graphPtr->height / Tk_Height(graphPtr->tkwin);
	y = (int)(legendPtr->anchorPos.y * scale);
    } 
#endif
    legendPtr->x = x + legendPtr->padLeft;
    legendPtr->y = y + legendPtr->padTop;
}


static ClientData
PickLegendEntry(clientData, x, y)
    ClientData clientData;
    int x, y;			/* Point to be tested */
{
    Graph *graphPtr = (Graph *)clientData;
    Legend *legendPtr;
    int n;
    int width, height;
    int row, column;
    Blt_ChainLink *linkPtr;
    Element *elemPtr;

    legendPtr = graphPtr->legendPtr;
    width = legendPtr->width;
    height = legendPtr->height;
    x -= (legendPtr->x + legendPtr->borderWidth);
    y -= (legendPtr->y + legendPtr->borderWidth);
    width -= (2 * legendPtr->borderWidth + PADDING(legendPtr->padX));
    height -= (2 * legendPtr->borderWidth + PADDING(legendPtr->padY));

    if ((x < 0) || (x >= width) || (y < 0) || (y >= height)) {
	return NULL;
    }
    /*
     * It's in the bounding box, so compute the index.
     */
    row = y / legendPtr->style.height;
    column = x / legendPtr->style.width;
    n = (column * legendPtr->nRows) + row;
    if (n >= legendPtr->nEntries) {
	return NULL;
    }
    elemPtr = NULL;
    for (linkPtr = Blt_ChainLastLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainPrevLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (elemPtr->label == NULL) {
	    continue;		/* Skip this label */
	}
	if (n == 0) {
	    break;
	}
	n--;
    }
    return (ClientData) elemPtr;
}

/*
 * -----------------------------------------------------------------
 *
 * Blt_LayoutLegend --
 *
 * 	Calculates the dimensions (width and height) needed for
 *	the legend.  Also determines the number of rows and columns
 *	necessary to list all the valid element labels.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *   	The following fields of the legend are calculated and set.
 *
 * 	nEntries   - number of valid labels of elements in the
 *		      display list.
 * 	nRows	    - number of rows of entries
 * 	nCols	    - number of columns of entries
 * 	style.height - height of each entry
 * 	style.width  - width of each entry
 * 	height	    - width of legend (includes borders and padding)
 * 	width	    - height of legend (includes borders and padding)
 *
 * -----------------------------------------------------------------
 */
void
Blt_LayoutLegend(graphPtr, plotWidth, plotHeight)
    Graph *graphPtr;
    int plotWidth;		/* Maximum width available in window
				 * to draw the legend. Will calculate number
				 * of columns from this. */
    int plotHeight;		/* Maximum height available in window
				 * to draw the legend. Will calculate number
				 * of rows from this. */
{
    Legend *legendPtr = graphPtr->legendPtr;
    Blt_ChainLink *linkPtr;
    Element *elemPtr;
    int nRows, nCols;
    int nEntries;
    int width, height;
    int lw, lh;
    int symbolWidth;
    int outer, inner;
    Tk_FontMetrics fontMetrics;

    /* Initialize legend values to default (no legend displayed) */

    legendPtr->style.width = legendPtr->style.height = 0;
    legendPtr->nRows = legendPtr->nCols = 0;
    legendPtr->nEntries = 0;
    legendPtr->height = legendPtr->width = 0;

    if ((legendPtr->hidden) || (plotWidth < 1) || (plotHeight < 1)) {
	return;			/* Legend is not being displayed */
    }

    /* Determine the number of labels and the widest label */

    nEntries = 0;
    lw = lh = 0;
    for (linkPtr = Blt_ChainLastLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainPrevLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (elemPtr->label == NULL) {
	    continue;		/* Skip this label */
	}
	Blt_GetTextExtents(&(legendPtr->style), elemPtr->label, &width, &height);
	if (lw < width) {
	    lw = width;
	}
	if (lh < height) {
	    lh = height;
	}
	nEntries++;
    }

    if (nEntries == 0) {
	return;			/* No labels to display in legend */
    }
    /*
     * Calculate the space need to for the legend based upon the size
     * of a label entry and the number of rows and columns needed.
     * Bound the height of the area by *maxHeight* which is the size
     * of the plotting area.
     */
    Tk_GetFontMetrics(legendPtr->style.font, &fontMetrics);
    symbolWidth = 2 * fontMetrics.ascent;
    legendPtr->nEntries = nEntries;

    outer = 2 * legendPtr->borderWidth;
    inner = 2 * legendPtr->entryBW;

    legendPtr->style.height = lh + inner + PADDING(legendPtr->ipadY);
    legendPtr->style.width = lw + inner + PADDING(legendPtr->ipadX) + 
	5 + symbolWidth;

    width = plotWidth - (outer + PADDING(legendPtr->padX));
    height = plotHeight - (outer + PADDING(legendPtr->padY));
    nRows = height / legendPtr->style.height;
    if (nRows < 1) {
	nRows = 1;
    }
    nCols = width / legendPtr->style.width;
    if (nCols < 1) {
	nCols = 1;
    }
    if ((legendPtr->anchorPos.site == LEGEND_TOP) ||
	(legendPtr->anchorPos.site == LEGEND_BOTTOM)) {
	if (nCols > 0) {
	    nRows = ((nEntries - 1) / nCols) + 1;
	    if (nCols > nEntries) {
		nCols = nEntries;
	    } else {
		nCols = ((nEntries - 1) / nRows) + 1;
	    }
	}
    } else {
	if (nRows > 0) {
	    nCols = ((nEntries - 1) / nRows) + 1;
	    if (nRows > nEntries) {
		nRows = nEntries;
	    }
	}
    }
    legendPtr->height = outer + PADDING(legendPtr->padY) + 
	(nRows * legendPtr->style.height);

    legendPtr->width = outer + PADDING(legendPtr->padX) + 
	(nCols * legendPtr->style.width);
    legendPtr->nRows = nRows;
    legendPtr->nCols = nCols;
}

void
Blt_DrawLegend(graphPtr, drawable)
    Graph *graphPtr;
    Drawable drawable;		/* Pixmap or window to draw into */
{
    Legend *legendPtr = graphPtr->legendPtr;
    Blt_ChainLink *linkPtr;
    int x, y;
    int width, height;
    int labelX, startY, symbolX, symbolY;
    int counter;
    int symSize, midX, midY;
    int redraw;
    register Element *elemPtr;
    Tk_FontMetrics fontMetrics;

    graphPtr->flags &= ~DRAW_LEGEND;
    if ((legendPtr->hidden) || (legendPtr->nEntries == 0)) {
	return;
    }
    SetLegendOrigin(graphPtr);

    width = legendPtr->width - PADDING(legendPtr->padX);
    height = legendPtr->height - PADDING(legendPtr->padY);
    Tk_GetFontMetrics(legendPtr->style.font, &fontMetrics);
    symSize = fontMetrics.ascent;
    midX = symSize + 1 + legendPtr->entryBW;
    midY = (symSize / 2) + 1 + legendPtr->entryBW;
    labelX = 2 * symSize + legendPtr->entryBW + legendPtr->ipadX.side1 + 5;
    symbolY = midY + legendPtr->ipadY.side1;
    symbolX = midX + legendPtr->ipadX.side1;

    x = legendPtr->x, y = legendPtr->y;

    redraw = FALSE;
    if (drawable == None) {

	/*
	 * If there's no pixmap already to draw into, then this
	 * routine was called from Tcl_DoWhenIdle instead of
	 * DisplayGraph.  Create a temporary pixmap and reset the x,y
	 * coordinates to do a quick redraw of just the legend.
	 */
	drawable = Tk_GetPixmap(graphPtr->display, Tk_WindowId(graphPtr->tkwin),
	    width, height, Tk_Depth(graphPtr->tkwin));

	if (legendPtr->border != NULL) {
	    Tk_Fill3DRectangle(graphPtr->tkwin, drawable, legendPtr->border,
		0, 0, width, height, legendPtr->borderWidth, legendPtr->relief);
	} else {

	    if (legendPtr->anchorPos.site > LEGEND_TOP) {
		if (graphPtr->backPixmap != None) {
		    XCopyArea(graphPtr->display, graphPtr->backPixmap, drawable,
			graphPtr->drawGC, x, y, width, height, 0, 0);
		} else {
		    XFillRectangle(graphPtr->display, drawable,
			graphPtr->plotFillGC, 0, 0, width, height);
		}
	    } else {
		if (graphPtr->tile != NULL) {
		    Blt_SetTileOrigin(graphPtr->tkwin, graphPtr->tile,
			legendPtr->x, legendPtr->y);
		    Blt_TileRectangle(graphPtr->display, drawable, 
			graphPtr->tile, 0, 0, width, height);
		} else {
		    XFillRectangle(graphPtr->display, drawable, 
			   graphPtr->fillGC, 0, 0, width, height);
		}
		Tk_Draw3DRectangle(graphPtr->tkwin, drawable, graphPtr->border,
			0, 0, width, height, legendPtr->borderWidth, 
			legendPtr->relief);
	    }
	}
	x = y = 0;
	redraw = TRUE;
    }
    /*
     * Draw the border and/or background of the legend.
     */
    if (legendPtr->border != NULL) {
	Tk_Fill3DRectangle(graphPtr->tkwin, drawable, legendPtr->border, x, y,
	    width, height, legendPtr->borderWidth, legendPtr->relief);
    } else {
	Tk_Draw3DRectangle(graphPtr->tkwin, drawable, graphPtr->border, x, y,
	    width, height, legendPtr->borderWidth, legendPtr->relief);
    }
    x += legendPtr->borderWidth;
    y += legendPtr->borderWidth;

    counter = 0;
    startY = y;
    for (linkPtr = Blt_ChainLastLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainPrevLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (elemPtr->label == NULL) {
	    continue;		/* Skip this entry */
	}
	if (elemPtr->flags & LABEL_ACTIVE) {
	    legendPtr->style.state |= STATE_ACTIVE;
	    Tk_Fill3DRectangle(graphPtr->tkwin, drawable, legendPtr->activeBorder,
		x, y, legendPtr->style.width, legendPtr->style.height,
		legendPtr->entryBW, elemPtr->labelRelief);
	} else {
	    legendPtr->style.state &= ~STATE_ACTIVE;
	    if (elemPtr->labelRelief != TK_RELIEF_FLAT) {
		Tk_Draw3DRectangle(graphPtr->tkwin, drawable, graphPtr->border,
		    x, y, legendPtr->style.width, legendPtr->style.height,
		    legendPtr->entryBW, elemPtr->labelRelief);
	    }
	}
	(*elemPtr->classPtr->drawSymbolProc) (graphPtr, drawable, elemPtr,
	    x + symbolX, y + symbolY, symSize);
	Blt_DrawText(graphPtr->tkwin, drawable, elemPtr->label,
	    &(legendPtr->style), x + labelX,
	    y + legendPtr->entryBW + legendPtr->ipadY.side1);
	counter++;

	/* Check when to move to the next column */
	if ((counter % legendPtr->nRows) > 0) {
	    y += legendPtr->style.height;
	} else {
	    x += legendPtr->style.width;
	    y = startY;
	}
    }
    if (redraw) {
	Blt_DisableCrosshairs(graphPtr);
	XCopyArea(graphPtr->display, drawable, Tk_WindowId(graphPtr->tkwin),
	    graphPtr->drawGC, 0, 0, width, height, legendPtr->x, legendPtr->y);
	Blt_EnableCrosshairs(graphPtr);
	Tk_FreePixmap(graphPtr->display, drawable);
    }
}

/*
 * -----------------------------------------------------------------
 *
 * PrintLegend --
 *
 * -----------------------------------------------------------------
 */
void
Blt_PrintLegend(graphPtr, printable)
    Graph *graphPtr;
    Printable printable;
{
    Legend *legendPtr = graphPtr->legendPtr;
    int x, y, startY;
    Element *elemPtr;
    int labelX, symbolX, symbolY;
    int counter;
    Blt_ChainLink *linkPtr;
    int symSize, midX, midY;
    int width, height;
    Tk_FontMetrics fontMetrics;

    if ((legendPtr->hidden) || (legendPtr->nEntries == 0)) {
	return;
    }
    SetLegendOrigin(graphPtr);

    x = legendPtr->x, y = legendPtr->y;
    width = legendPtr->width - PADDING(legendPtr->padX);
    height = legendPtr->height - PADDING(legendPtr->padY);

    if (graphPtr->postscript->decorations) {
	if (legendPtr->border != NULL) {
	    Blt_Fill3DRectangleToPostScript(printable, legendPtr->border, x, y,
		width, height, legendPtr->borderWidth, legendPtr->relief);
	} else {
	    Blt_Draw3DRectangleToPostScript(printable, graphPtr->border, x, y,
		width, height, legendPtr->borderWidth, legendPtr->relief);
	}
    } else {
	Blt_ClearBackgroundToPostScript(printable);
	Blt_RectangleToPostScript(printable, x, y, width, height);
    }
    x += legendPtr->borderWidth;
    y += legendPtr->borderWidth;

    Tk_GetFontMetrics(legendPtr->style.font, &fontMetrics);
    symSize = fontMetrics.ascent;
    midX = symSize + 1 + legendPtr->entryBW;
    midY = (symSize / 2) + 1 + legendPtr->entryBW;
    labelX = 2 * symSize + legendPtr->entryBW +
	legendPtr->ipadX.side1 + 5;
    symbolY = midY + legendPtr->ipadY.side1;
    symbolX = midX + legendPtr->ipadX.side1;

    counter = 0;
    startY = y;
    for (linkPtr = Blt_ChainLastLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainPrevLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (elemPtr->label == NULL) {
	    continue;		/* Skip this label */
	}
	if (elemPtr->flags & LABEL_ACTIVE) {
	    legendPtr->style.state |= STATE_ACTIVE;
	    Blt_Fill3DRectangleToPostScript(printable, legendPtr->activeBorder,
		    x, y, legendPtr->style.width, legendPtr->style.height,
		    legendPtr->entryBW, elemPtr->labelRelief);
	} else {
	    legendPtr->style.state &= ~STATE_ACTIVE;
	    if (elemPtr->labelRelief != TK_RELIEF_FLAT) {
		Blt_Draw3DRectangleToPostScript(printable, graphPtr->border,
		    x, y, legendPtr->style.width, legendPtr->style.height,
		    legendPtr->entryBW, elemPtr->labelRelief);
	    }
	}
	(*elemPtr->classPtr->printSymbolProc) (graphPtr, printable, elemPtr,
	    x + symbolX, y + symbolY, symSize);
	Blt_PrintText(printable, elemPtr->label, &(legendPtr->style),
	    x + labelX,
	    y + legendPtr->entryBW + legendPtr->ipadY.side1);
	counter++;
	if ((counter % legendPtr->nRows) > 0) {
	    y += legendPtr->style.height;
	} else {
	    x += legendPtr->style.width;
	    y = startY;
	}
    }
}

/*
 * -----------------------------------------------------------------
 *
 * DrawLegend --
 *
 * -----------------------------------------------------------------
 */
static void
DrawLegend(clientData)
    ClientData clientData;
{
    Graph *graphPtr = (Graph *)clientData;
    Blt_DrawLegend(graphPtr, None);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureLegend --
 *
 * 	Routine to configure the legend.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side Effects:
 *	Graph will be redrawn to reflect the new legend attributes.
 *
 *----------------------------------------------------------------------
 */
static void
ConfigureLegend(graphPtr, legendPtr)
    Graph *graphPtr;
    Legend *legendPtr;
{
    Blt_ResetTextStyle(graphPtr->tkwin, &(legendPtr->style));

    /*
     *  Update the layout of the graph (and redraw the elements) if
     *  any of the following legend options (all of which affect the
     *	size of the legend) have changed.
     *
     *		-activeborderwidth, -borderwidth
     *		-border
     *		-font
     *		-hide
     *		-ipadx, -ipady, -padx, -pady
     *		-rows
     *
     *  If the position of the legend changed to/from the default
     *  position, also indicate that a new layout is needed.
     *
     */
    if (Blt_ConfigModified(configSpecs, "-*border*", "-*pad?",
	    "-position", "-hide", "-font", "-rows", (char *)NULL)) {
	graphPtr->flags |= COORDS_WORLD;
    }
    graphPtr->flags |= (REDRAW_WORLD | REDRAW_BACKING_STORE);
    Blt_EventuallyRedrawGraph(graphPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_DestroyLegend --
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resources associated with the legend are freed.
 *
 *----------------------------------------------------------------------
 */
void
Blt_DestroyLegend(graphPtr)
    Graph *graphPtr;
{
    Legend *legendPtr = graphPtr->legendPtr;

    Tk_FreeOptions(configSpecs, (char *)legendPtr, graphPtr->display, 0);
    Blt_FreeTextStyle(graphPtr->display, &(legendPtr->style));
    Blt_DestroyBindingTable(legendPtr->bindTable);
    free((char *)legendPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_CreateLegend --
 *
 * 	Creates and initializes a legend structure with default settings
 *
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
int
Blt_CreateLegend(graphPtr)
    Graph *graphPtr;
{
    Legend *legendPtr;

    legendPtr = (Legend *)calloc(1, sizeof(Legend));
    assert(legendPtr);
    legendPtr->hidden = FALSE;
    legendPtr->anchorPos.x = legendPtr->anchorPos.y = -SHRT_MAX;
    legendPtr->relief = TK_RELIEF_SUNKEN;
    legendPtr->activeRelief = TK_RELIEF_FLAT;
    legendPtr->entryBW = legendPtr->borderWidth = 2;
    legendPtr->ipadX.side1 = legendPtr->ipadX.side2 = 1;
    legendPtr->ipadY.side1 = legendPtr->ipadY.side2 = 1;
    legendPtr->padX.side1 = legendPtr->padX.side2 = 1;
    legendPtr->padY.side1 = legendPtr->padY.side2 = 1;
    legendPtr->anchor = TK_ANCHOR_N;
    legendPtr->anchorPos.site = LEGEND_RIGHT;
    graphPtr->legendPtr = legendPtr;
    Blt_InitTextStyle(&(legendPtr->style));
    legendPtr->style.justify = TK_JUSTIFY_LEFT;
    legendPtr->style.anchor = TK_ANCHOR_NW;
    legendPtr->bindTable = Blt_CreateBindingTable(graphPtr->interp,
	graphPtr->tkwin, graphPtr, PickLegendEntry, Blt_GraphTags);

    if (Blt_ConfigureWidgetComponent(graphPtr->interp, graphPtr->tkwin,
	    "legend", "Legend", configSpecs, 0, (char **)NULL,
	    (char *)legendPtr, 0) != TCL_OK) {
	return TCL_ERROR;
    }
    ConfigureLegend(graphPtr, legendPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetOp --
 *
 * 	Find the legend entry from the given argument.  The argument
 *	can be either a screen position "@x,y" or the name of an
 *	element.
 *
 *	I don't know how useful it is to test with the name of an
 *	element.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side Effects:
 *	Graph will be redrawn to reflect the new legend attributes.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
GetOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;			/* Not used. */
    char *argv[];
{
    register Element *elemPtr;
    Legend *legendPtr = graphPtr->legendPtr;
    int x, y;
    char c;

    if ((legendPtr->hidden) || (legendPtr->nEntries == 0)) {
	return TCL_OK;
    }
    elemPtr = NULL;
    c = argv[3][0];
    if ((c == 'c') && (strcmp(argv[3], "current") == 0)) {
	elemPtr = (Element *)Blt_GetCurrentItem(legendPtr->bindTable);
    } else if ((c == '@') &&
       (Blt_GetXY(interp, graphPtr->tkwin, argv[3], &x, &y) == TCL_OK)) { 
	elemPtr = (Element *)PickLegendEntry(graphPtr, x, y);
    }
    if (elemPtr != NULL) {
	Tcl_SetResult(interp, elemPtr->name, TCL_STATIC);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ActivateOp --
 *
 * 	Activates a particular label in the legend.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side Effects:
 *	Graph will be redrawn to reflect the new legend attributes.
 *
 *----------------------------------------------------------------------
 */
static int
ActivateOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char *argv[];
{
    Legend *legendPtr = graphPtr->legendPtr;
    Element *elemPtr;
    unsigned int active, redraw;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    register int i;

    active = (argv[2][0] == 'a') ? LABEL_ACTIVE : 0;
    redraw = 0;
    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->elements.table), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	elemPtr = (Element *)Tcl_GetHashValue(hPtr);
	for (i = 3; i < argc; i++) {
	    if (Tcl_StringMatch(elemPtr->name, argv[i])) {
		break;
	    }
	}
	if ((i < argc) && (active != (elemPtr->flags & LABEL_ACTIVE))) {
	    elemPtr->flags ^= LABEL_ACTIVE;
	    if (elemPtr->label != NULL) {
		redraw++;
	    }
	}
    }
    if ((redraw) && (!legendPtr->hidden)) {
	/*
	 * We need to redraw the legend. If there isn't a redraw already
	 * pending for the whole graph, we can redraw just the legend,
	 * calling the legend's display routine rather than the graph's.
	 * The window must be hidden though.
	 */
	if (legendPtr->anchorPos.site > LEGEND_TOP) {
	    graphPtr->flags |= REDRAW_BACKING_STORE;
	}
	if (graphPtr->flags & REDRAW_PENDING) {
	    graphPtr->flags |= REDRAW_WORLD;	/* Need to entire graph */
	} else if (!(graphPtr->flags & DRAW_LEGEND)) {
	    if ((graphPtr->tkwin != NULL) && (Tk_IsMapped(graphPtr->tkwin))) {
		Tcl_DoWhenIdle(DrawLegend, (ClientData)graphPtr);
		graphPtr->flags |= DRAW_LEGEND;
	    }
	}
    }
    /* Return the names of all the active legend entries */
    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->elements.table), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	elemPtr = (Element *)Tcl_GetHashValue(hPtr);
	if (elemPtr->flags & LABEL_ACTIVE) {
	    Tcl_AppendElement(interp, elemPtr->name);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * BindOp --
 *
 *	  .t bind index 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->legendPtr->bindTable,
	Blt_MakeElementTag(graphPtr, argv[3]), argc - 4, argv + 4);
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 * 	Queries or resets options for the legend.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side Effects:
 *	Graph will be redrawn to reflect the new legend attributes.
 *
 *----------------------------------------------------------------------
 */
/* ARGSUSED */
static int
CgetOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    return Tk_ConfigureValue(interp, graphPtr->tkwin, configSpecs,
	    (char *)graphPtr->legendPtr, argv[3], 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 * 	Queries or resets options for the legend.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side Effects:
 *	Graph will be redrawn to reflect the new legend attributes.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int flags = TK_CONFIG_ARGV_ONLY;
    Legend *legendPtr;

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

/*
 *----------------------------------------------------------------------
 *
 * Blt_LegendOp --
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side Effects:
 *	Legend is possibly redrawn.
 *
 *----------------------------------------------------------------------
 */

static Blt_OpSpec legendOps[] =
{
    {"activate", 1, (Blt_OpProc)ActivateOp, 3, 0, "?pattern?...",},
    {"bind", 1, (Blt_OpProc)BindOp, 3, 6, "elemName sequence command",},
    {"cget", 2, (Blt_OpProc)CgetOp, 4, 4, "option",},
    {"configure", 2, (Blt_OpProc)ConfigureOp, 3, 0, "?option value?...",},
    {"deactivate", 1, (Blt_OpProc)ActivateOp, 3, 0, "?pattern?...",},
    {"get", 1, (Blt_OpProc)GetOp, 4, 4, "index",},
};
static int nLegendOps = sizeof(legendOps) / sizeof(Blt_OpSpec);

int
Blt_LegendOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;

    opProc = Blt_GetOperation(interp, nLegendOps, legendOps, BLT_OPER_ARG2,
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    result = (*opProc) (graphPtr, interp, argc, argv);
    return result;
}
