/*
 * bltGrLegd.c --
 *
 *	This module implements a graph widget for
 *	the Tk toolkit.
 *
 * Copyright 1991-1994 by AT&T Bell Laboratories.
 * 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 AT&T Bell Laboratories any of their entities not be used
 * in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * AT&T disclaims all warranties with regard to this software, including
 * all implied warranties of merchantability and fitness.  In no event
 * shall AT&T 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 "blt.h"
#include "bltGraph.h"
#include "bltGrElem.h"

#define DEF_LEGEND_ANCHOR	"nw"
#define DEF_LEGEND_BG_COLOR	BISQUE1
#define DEF_LEGEND_BG_MONO	WHITE
#define DEF_LEGEND_FG_COLOR	BLACK
#define DEF_LEGEND_FG_MONO	BLACK
#define DEF_LEGEND_FONT		"*-Helvetica-Bold-R-Normal-*-120-*"
#define DEF_LEGEND_MAPPED       "1"
#define DEF_LEGEND_RELIEF	"sunken"
#define DEF_LEGEND_BORDER_WIDTH "2"
#define DEF_LEGEND_PAD_X	"4"
#define DEF_LEGEND_PAD_Y	"0"
#define DEF_LEGEND_IPAD_X	"2"
#define DEF_LEGEND_IPAD_Y	"2"

extern int Blt_ParseCoords _ANSI_ARGS_((ClientData, Tcl_Interp *, Tk_Window,
	char *, char *, int));
extern char *Blt_PrintCoords _ANSI_ARGS_((ClientData, Tk_Window, char *, int,
	Tcl_FreeProc **));

static Tk_CustomOption LegendOption =
{
    Blt_ParseCoords, Blt_PrintCoords, (ClientData)0
};

/*
 * -------------------------------------------------------------------
 *
 * Legend --
 *
 * 	Contains information specific to how the legend will be
 *	displayed.
 *
 * -------------------------------------------------------------------
 */


typedef struct {
    int mapped;			/* Requested state of the legend, If non-zero,
				 * legend is displayed */
    unsigned int width, height;	/* Dimensions of the legend */
    XPoint anchorPos;		/* Window coordinates of legend positioning
				 * point. Used in conjunction with the anchor
				 * to determine the location of the legend. If
				 * x or y are DEF_POSITION the legend is set
				 * in the right margin */
    int useDefault;		/* Use the default legend position */

    LegendDisplayProc *displayProc;
    LegendPrintProc *printProc;
    LegendDestroyProc *destroyProc;
    LegendGeometryProc *geomProc;

    int ipadX, ipadY;		/* # of pixels padding of legend components */
    int padX, padY;		/* # of pixels padding exterior to legend */
    int numLabels;		/* Number of labels (and symbols) to display */
    int maxSymSize;		/* Size of largest symbol to be displayed.
				 * Used to calculate size of legend */
    Tk_3DBorder border;		/* 3-D border and background color legend. */
    int borderWidth;		/* Width of legend 3-D border */
    int relief;			/* 3-d effect: TK_RELIEF_RAISED etc. */
    Tk_Anchor anchor;		/* Anchor of legend. Used to interpret the
				 * positioning point of the legend in the
				 * graph*/
    XFontStruct *fontPtr;	/* Font for legend text */
    XColor *fgColorPtr;		/* Foreground color for legend text. Symbols
				 * retain the color specified for the element*/
    GC gc;			/* Possibly shared graphics context */

} Legend;

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_ANCHOR, "-anchor", "legendAnchor", "Anchor",
	DEF_LEGEND_ANCHOR, Tk_Offset(Legend, anchor),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_SYNONYM, "-bg", "legendBackground", (char *)NULL,
	(char *)NULL, 0, 0},
    {TK_CONFIG_BORDER, "-background", "legendBackground", "Background",
	DEF_LEGEND_BG_MONO, Tk_Offset(Legend, border),
	TK_CONFIG_MONO_ONLY | TK_CONFIG_NULL_OK},
    {TK_CONFIG_BORDER, "-background", "legendBackground", "Background",
	DEF_LEGEND_BG_COLOR, Tk_Offset(Legend, border),
	TK_CONFIG_COLOR_ONLY | TK_CONFIG_NULL_OK},
    {TK_CONFIG_INT, "-borderwidth", "legendBorderWidth", "BorderWidth",
	DEF_LEGEND_BORDER_WIDTH, Tk_Offset(Legend, borderWidth),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_SYNONYM, "-bd", "legendBorderWidth", (char *)NULL,
	(char *)NULL, 0, 0},
    {TK_CONFIG_FONT, "-font", "legendFont", "Font",
	DEF_LEGEND_FONT, Tk_Offset(Legend, fontPtr), 0},
    {TK_CONFIG_SYNONYM, "-fg", "legendForeground", (char *)NULL, (char *)NULL,
	0, 0},
    {TK_CONFIG_COLOR, "-foreground", "legendForeground", "Foreground",
	DEF_LEGEND_FG_COLOR, Tk_Offset(Legend, fgColorPtr),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "legendForeground", "Foreground",
	DEF_LEGEND_FG_MONO, Tk_Offset(Legend, fgColorPtr),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_PIXELS, "-ipadx", "legendIPadX", "Pad",
	DEF_LEGEND_IPAD_X, Tk_Offset(Legend, ipadX),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_PIXELS, "-ipady", "legendIPadY", "Pad",
	DEF_LEGEND_IPAD_Y, Tk_Offset(Legend, ipadY),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BOOLEAN, "-mapped", "legendMapped", "Mapped",
	DEF_LEGEND_MAPPED, Tk_Offset(Legend, mapped),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_PIXELS, "-padx", "legendPadX", "Pad",
	DEF_LEGEND_PAD_X, Tk_Offset(Legend, padX), TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_PIXELS, "-pady", "legendPadY", "Pad",
	DEF_LEGEND_PAD_Y, Tk_Offset(Legend, padY), TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-position", "legendPosition", "Position",
	(char *)NULL, Tk_Offset(Legend, anchorPos),
	TK_CONFIG_NULL_OK, &LegendOption},
    {TK_CONFIG_RELIEF, "-relief", "legendRelief", "Relief",
	DEF_LEGEND_RELIEF, Tk_Offset(Legend, relief),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

/*
 * -----------------------------------------------------------------
 *
 * ComputeLegendGeometry --
 *
 * 	Calculates the width and height needed for the legend
 *
 * Returns:
 *      Width of the longest label in pixels.
 *
 * Side effects:
 *   	The size of each element's symbol is calculated and set.
 *
 * -----------------------------------------------------------------
 */
static int
ComputeLegendGeometry(graphPtr)
    Graph *graphPtr;
{
    Blt_ListEntry *entryPtr;
    Legend *legendPtr = (Legend *)graphPtr->legendPtr;
    Element *elemPtr;
    int maxWidth;
    int numLabels;

    legendPtr->width = legendPtr->height = 0;
    numLabels = maxWidth = 0;

    for (entryPtr = Blt_FirstListEntry(&(graphPtr->elemList));
	entryPtr != NULL; entryPtr = Blt_NextListEntry(entryPtr)) {
	elemPtr = (Element *)Blt_GetListValue(entryPtr);

	if ((elemPtr->x.length < 1) || (elemPtr->y.length < 1)) {
	    continue;		/* Ignore elements with incomplete data */
	}
	if (elemPtr->label != NULL) {
	    int width;

	    width = Blt_TextStringWidth(legendPtr->fontPtr, elemPtr->label);
	    if (width > maxWidth) {
		maxWidth = width;
	    }
	    numLabels++;
	}
    }
    legendPtr->numLabels = numLabels;
    if (legendPtr->mapped) {
	int textheight = TEXTHEIGHT(legendPtr->fontPtr);

	legendPtr->width = maxWidth + textheight + legendPtr->ipadX +
	    2 * (legendPtr->borderWidth + legendPtr->padX + legendPtr->ipadX);
	legendPtr->height = (legendPtr->ipadY + textheight) * numLabels +
	    legendPtr->ipadY + 2 * (legendPtr->padY + legendPtr->borderWidth);
    }
    return (maxWidth);
}

/*
 * -----------------------------------------------------------------
 *
 * DisplayLegend --
 *
 * -----------------------------------------------------------------
 */
static void
DisplayLegend(graphPtr)
    Graph *graphPtr;
{
    Legend *legendPtr = (Legend *)graphPtr->legendPtr;
    int x, y;
    int labelX;
    register Element *elemPtr;
    Blt_ListEntry *entryPtr;
    XPoint anchorPos, point;
    Tk_Anchor anchor;		/* Anchor of legend */
    int lineHeight;
    int symbolSize, midPoint;
    TextAttr textAttr;

    if ((!legendPtr->mapped) || (legendPtr->numLabels == 0)) {
	return;
    }
    symbolSize = legendPtr->fontPtr->ascent;
    midPoint = (symbolSize / 2) + 1;
    if (!legendPtr->useDefault) {
	x = legendPtr->anchorPos.x;
	y = legendPtr->anchorPos.y;
	anchor = legendPtr->anchor;
    } else {
	x = graphPtr->width - graphPtr->extBWidth;
	y = graphPtr->extreme.y - graphPtr->intBWidth;
	anchor = TK_ANCHOR_NE;
    }
    anchorPos = Blt_TranslateBoxCoords(x, y, legendPtr->width,
	legendPtr->height, anchor);
    x = anchorPos.x + legendPtr->padX;
    y = anchorPos.y + legendPtr->padY;
    if (legendPtr->border != NULL) {
	int width, height;

	width = legendPtr->width - (2 * legendPtr->padX);
	height = legendPtr->height - (2 * legendPtr->padY);
	Tk_Fill3DRectangle(graphPtr->display, graphPtr->canvas,
	    legendPtr->border, x, y, width, height, legendPtr->borderWidth,
	    legendPtr->relief);
    }
    lineHeight = TEXTHEIGHT(legendPtr->fontPtr);
    point.y = midPoint + y + legendPtr->borderWidth + legendPtr->ipadY;
    point.x = midPoint + x + legendPtr->borderWidth + legendPtr->ipadX;
    labelX = point.x + midPoint + legendPtr->ipadX;

    textAttr.fontPtr = legendPtr->fontPtr;
    textAttr.anchor = TK_ANCHOR_W;
    textAttr.theta = 0.0;
    textAttr.bgColorPtr = (XColor *)NULL;
    textAttr.gc = legendPtr->gc;

    for (entryPtr = Blt_FirstListEntry(&(graphPtr->elemList));
	entryPtr != NULL; entryPtr = Blt_NextListEntry(entryPtr)) {
	elemPtr = (Element *)Blt_GetListValue(entryPtr);
	if ((elemPtr->x.length < 1) || (elemPtr->y.length < 1)) {
	    continue;
	}
	if (elemPtr->label != NULL) {
	    (*elemPtr->drawSymbolsProc) (graphPtr, elemPtr, symbolSize,
		&point, 1);
	    Blt_DrawText(graphPtr->display, graphPtr->canvas, elemPtr->label,
		&textAttr, labelX, point.y);
	    point.y += lineHeight + legendPtr->ipadY;
	}
    }
}

/*
 * -----------------------------------------------------------------
 *
 * PrintLegend --
 *
 * -----------------------------------------------------------------
 */
static void
PrintLegend(graphPtr)
    Graph *graphPtr;
{
    Legend *legendPtr = (Legend *)graphPtr->legendPtr;
    Blt_ListEntry *entryPtr;
    Element *elemPtr;
    int x, y;
    int labelX;
    XPoint anchorPos, point;
    Tk_Anchor anchor;
    int lineHeight;
    int symbolSize, midPoint;
    TextAttr textAttr;

    if ((!legendPtr->mapped) || (legendPtr->numLabels == 0)) {
	return;
    }
    symbolSize = legendPtr->fontPtr->ascent;
    midPoint = symbolSize / 2;
    if (!legendPtr->useDefault) {
	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 = ROUND(legendPtr->anchorPos.x * scale);
	scale = (double)graphPtr->height / Tk_Height(graphPtr->tkwin);
	y = ROUND(legendPtr->anchorPos.y * scale);
	anchor = legendPtr->anchor;
    } else {
	x = graphPtr->width - graphPtr->extBWidth;
	y = graphPtr->extreme.y - graphPtr->intBWidth;
	anchor = TK_ANCHOR_NE;
    }
    anchorPos = Blt_TranslateBoxCoords(x, y, legendPtr->width,
	legendPtr->height, anchor);
    x = anchorPos.x + legendPtr->padX;
    y = anchorPos.y + legendPtr->padY;
    if (legendPtr->border != NULL) {
	int width, height;

	width = legendPtr->width - (2 * legendPtr->padX);
	height = legendPtr->height - (2 * legendPtr->padY);
	Blt_3DRectangleToPostScript(graphPtr, legendPtr->border, x, y, width,
	    height, legendPtr->borderWidth, legendPtr->relief);
    }
    lineHeight = TEXTHEIGHT(legendPtr->fontPtr);
    point.y = midPoint + y + legendPtr->borderWidth + legendPtr->ipadY;
    point.x = midPoint + x + legendPtr->borderWidth + legendPtr->ipadX;
    labelX = point.x + midPoint + legendPtr->ipadX;

    textAttr.fontPtr = legendPtr->fontPtr;
    textAttr.anchor = TK_ANCHOR_W;
    textAttr.theta = 0.0;
    textAttr.fgColorPtr = graphPtr->fgColorPtr;
    textAttr.bgColorPtr = (XColor *)NULL;

    for (entryPtr = Blt_FirstListEntry(&(graphPtr->elemList));
	entryPtr != NULL; entryPtr = Blt_NextListEntry(entryPtr)) {
	elemPtr = (Element *)Blt_GetListValue(entryPtr);
	if ((elemPtr->x.length < 1) || (elemPtr->y.length < 1)) {
	    continue;
	}
	if (elemPtr->label != NULL) {
	    (*elemPtr->printSymbolsProc) (graphPtr, elemPtr, symbolSize,
		&point, 1);
	    Blt_TextToPostScript(graphPtr, elemPtr->label, &textAttr, labelX,
		point.y);
	    point.y += lineHeight + PADY;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureLegend --
 *
 * 	Routine to configure the legend.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * Side Effects:
 *	Graph will be redrawn to reflect the new legend attributes.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureLegend(graphPtr, legendPtr, argc, argv, flags)
    Graph *graphPtr;
    Legend *legendPtr;
    int argc;
    char *argv[];
    int flags;
{
    GC newGC;
    XGCValues gcValues;
    unsigned long gcMask;

    if (Tk_ConfigureWidget(graphPtr->interp, graphPtr->tkwin,
	    configSpecs, argc, argv, (char *)legendPtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }
    legendPtr->useDefault = (legendPtr->anchorPos.x == DEF_POSITION);
    gcMask = GCForeground | GCFont;
    gcValues.font = legendPtr->fontPtr->fid;
    gcValues.foreground = legendPtr->fgColorPtr->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (legendPtr->gc != NULL) {
	Tk_FreeGC(graphPtr->display, legendPtr->gc);
    }
    legendPtr->gc = newGC;
    graphPtr->flags |= (REDRAW_ALL | LAYOUT_PENDING | LAYOUT_ALL);
    Blt_EventuallyRedraw(graphPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyLegend --
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resources associated with the legend are freed.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyLegend(graphPtr)
    Graph *graphPtr;
{
    Legend *legendPtr = (Legend *)graphPtr->legendPtr;

    Tk_FreeOptions(configSpecs, (char *)legendPtr, graphPtr->display, 0);
    if (legendPtr->gc != NULL) {
	Tk_FreeGC(graphPtr->display, legendPtr->gc);
    }
    free((char *)legendPtr);
}

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

    legendPtr = (Legend *)calloc(1, sizeof(Legend));
    if (legendPtr == NULL) {
	graphPtr->interp->result = "can't allocate legend structure";
	return TCL_ERROR;
    }
    legendPtr->mapped = TRUE;
    legendPtr->anchorPos.x = legendPtr->anchorPos.y = DEF_POSITION;
    legendPtr->useDefault = 1;
    legendPtr->relief = TK_RELIEF_SUNKEN;
    legendPtr->borderWidth = 2;
    legendPtr->ipadX = legendPtr->ipadY = 2;
    legendPtr->padX = 4;
    legendPtr->padY = 0;
    legendPtr->anchor = TK_ANCHOR_NW;
    legendPtr->displayProc = DisplayLegend;
    legendPtr->printProc = PrintLegend;
    legendPtr->destroyProc = DestroyLegend;
    legendPtr->geomProc = ComputeLegendGeometry;
    graphPtr->legendPtr = (GraphLegend *)legendPtr;
    return (ConfigureLegend(graphPtr, legendPtr, 0, (char **)NULL, 0));
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_LegendCmd --
 *
 *	User routine to configure crosshair simulation.  Legend
 *	are simulated by drawing line segments parallel to both axes
 *	using the XOR drawing function. The allows the lines to be
 *	erased (by drawing them again) without redrawing the entire
 *	graph.  Care must be taken to erase legend before redrawing
 *	the graph and redraw them after the graph is redraw.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * Side Effects:
 *	Legend are drawn in the plotting area.
 *
 *----------------------------------------------------------------------
 */
/* ARGSUSED */
int
Blt_LegendCmd(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char **argv;
{
    char c;
    int length;
    Tcl_Interp *interp = graphPtr->interp;
    Legend *legendPtr = (Legend *)graphPtr->legendPtr;

    /* Initialize the crosshairs on first call */

    if (argc < 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	    " legend option ?args?\"", NULL);
	return TCL_ERROR;
    }
    c = argv[2][0];
    length = strlen(argv[2]);

    if ((c == 'c') && (strncmp(argv[2], "configure", length) == 0)) {
	int flags = TK_CONFIG_ARGV_ONLY;

	if (argc == 3) {
	    return (Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin,
		    configSpecs, (char *)legendPtr, (char *)NULL, flags));
	} else if (argc == 4) {
	    return (Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin,
		    configSpecs, (char *)legendPtr, argv[3], flags));
	}
	if (ConfigureLegend(graphPtr, legendPtr, argc - 3,
		argv + 3, flags) != TCL_OK) {
	    return TCL_ERROR;
	}
    } else {
	Tcl_AppendResult(interp, "bad legend option \"", argv[2],
	    "\": should be configure", (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}
