/*
 * rbcGrGrid.c --
 *
 *      This module implements grid lines for the rbc graph widget.
 *
 * Copyright (c) 2001 BLT was created by George Howlett.
 * Copyright (c) 2009 RBC was created by Samuel Green, Nicholas Hudson, Stanton Sievers, Jarrod Stormo
 * Copyright (c) 2018 Rene Zaumseil

 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkoGraph.h"

extern Tk_CustomOption rbcDistanceOption;
extern Tk_CustomOption rbcDashesOption;
extern Tk_CustomOption rbcAnyXAxisOption;
extern Tk_CustomOption rbcAnyYAxisOption;

#define DEF_GRID_DASHES             "dot"
#define DEF_GRID_FOREGROUND         "gray64"
#define DEF_GRID_FG_MONO            "black"
#define DEF_GRID_LINE_WIDTH         "0"
#define DEF_GRID_HIDE_BARCHART      "no"
#define DEF_GRID_HIDE_GRAPH         "yes"
#define DEF_GRID_MINOR              "yes"
#define DEF_GRID_MAP_X_GRAPH        "x"
#define DEF_GRID_MAP_X_BARCHART     (char *)NULL
#define DEF_GRID_MAP_Y              "y"
#define DEF_GRID_POSITION           (char *)NULL

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_COLOR, "-color", "color", "Color", DEF_GRID_FOREGROUND,
            offsetof(RbcGrid, colorPtr),
        TK_CONFIG_COLOR_ONLY | RBC_ALL_GRAPHS},
    {TK_CONFIG_COLOR, "-color", "color", "color", DEF_GRID_FG_MONO,
        offsetof(RbcGrid, colorPtr), TK_CONFIG_MONO_ONLY | RBC_ALL_GRAPHS},
    {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes", DEF_GRID_DASHES,
            offsetof(RbcGrid, dashes), TK_CONFIG_NULL_OK | RBC_ALL_GRAPHS,
        &rbcDashesOption},
    {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide", DEF_GRID_HIDE_BARCHART,
        offsetof(RbcGrid, hidden), RBC_BARCHART},
    {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide", DEF_GRID_HIDE_GRAPH,
        offsetof(RbcGrid, hidden), RBC_GRAPH | RBC_STRIPCHART},
    {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "Linewidth",
            DEF_GRID_LINE_WIDTH, offsetof(RbcGrid, lineWidth),
        TK_CONFIG_DONT_SET_DEFAULT | RBC_ALL_GRAPHS, &rbcDistanceOption},
    {TK_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_GRID_MAP_X_GRAPH,
            offsetof(RbcGrid, axes.x), RBC_GRAPH | RBC_STRIPCHART,
        &rbcAnyXAxisOption},
    {TK_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_GRID_MAP_X_BARCHART,
        offsetof(RbcGrid, axes.x), RBC_BARCHART, &rbcAnyXAxisOption},
    {TK_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_GRID_MAP_Y,
        offsetof(RbcGrid, axes.y), RBC_ALL_GRAPHS, &rbcAnyYAxisOption},
    {TK_CONFIG_BOOLEAN, "-minor", "minor", "Minor", DEF_GRID_MINOR,
            offsetof(RbcGrid, minorGrid),
        TK_CONFIG_DONT_SET_DEFAULT | RBC_ALL_GRAPHS},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

static void ConfigureGrid(
    RbcGraph * graph,
    RbcGrid * gridPtr);
static int CgetOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int ConfigureOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int MapOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int UnmapOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int ToggleOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);

/*
 *----------------------------------------------------------------------
 *
 * ConfigureGrid --
 *
 *      Configures attributes of the grid such as line width,
 *      dashes, and position.  The grid are first turned off
 *      before any of the attributes changes.
 *
 * Results:
 *      None
 *
 * Side Effects:
 *      Crosshair GC is allocated.
 *
 *----------------------------------------------------------------------
 */
static void
ConfigureGrid(
    RbcGraph * graph,
    RbcGrid * gridPtr)
{
XGCValues gcValues;
unsigned long gcMask;
GC  newGC;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return;
	}

    gcValues.background = gcValues.foreground = gridPtr->colorPtr->pixel;
    gcValues.line_width = RbcLineWidth(gridPtr->lineWidth);
    gcMask = (GCForeground | GCBackground | GCLineWidth);
    if(RbcLineIsDashed(gridPtr->dashes)) {
        gcValues.line_style = LineOnOffDash;
        gcMask |= GCLineStyle;
    }
    newGC = RbcGetPrivateGC(tkWin, gcMask, &gcValues);
    if(RbcLineIsDashed(gridPtr->dashes)) {
        RbcSetDashes(graph->widget.display, newGC, &(gridPtr->dashes));
    }
    if(gridPtr->gc != NULL) {
        RbcFreePrivateGC(graph->widget.display, gridPtr->gc);
    }
    gridPtr->gc = newGC;
}

/*
 *----------------------------------------------------------------------
 *
 * RbcMapGrid --
 *
 *      Determines the coordinates of the line segments corresponding
 *      to the grid lines for each axis.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
void
RbcMapGrid(
    RbcGraph * graph)
{
RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;
int nSegments;
RbcSegment2D *segments;

    if(gridPtr->x.segments != NULL) {
        ckfree((char *)gridPtr->x.segments);
        gridPtr->x.segments = NULL;
    }
    if(gridPtr->y.segments != NULL) {
        ckfree((char *)gridPtr->y.segments);
        gridPtr->y.segments = NULL;
    }
    gridPtr->x.nSegments = gridPtr->y.nSegments = 0;
    /*
     * Generate line segments to represent the grid.  Line segments
     * are calculated from the major tick intervals of each axis mapped.
     */
    RbcGetAxisSegments(graph, gridPtr->axes.x, &segments, &nSegments);
    if(nSegments > 0) {
        gridPtr->x.nSegments = nSegments;
        gridPtr->x.segments = segments;
    }
    RbcGetAxisSegments(graph, gridPtr->axes.y, &segments, &nSegments);
    if(nSegments > 0) {
        gridPtr->y.nSegments = nSegments;
        gridPtr->y.segments = segments;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RbcDrawGrid --
 *
 *      Draws the grid lines associated with each axis.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
void
RbcDrawGrid(
    RbcGraph * graph,
    Drawable drawable)
{              /* Pixmap or window to draw into */
RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;

    if(gridPtr->hidden) {
        return;
    }
    if(gridPtr->x.nSegments > 0) {
        RbcDraw2DSegments(graph->widget.display, drawable, gridPtr->gc,
            gridPtr->x.segments, gridPtr->x.nSegments);
    }
    if(gridPtr->y.nSegments > 0) {
        RbcDraw2DSegments(graph->widget.display, drawable, gridPtr->gc,
            gridPtr->y.segments, gridPtr->y.nSegments);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RbcGridToPostScript --
 *
 *      Prints the grid lines associated with each axis.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
void
RbcGridToPostScript(
    RbcGraph * graph,
    RbcPsToken * psToken)
{
RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;

    if(gridPtr->hidden) {
        return;
    }
    RbcLineAttributesToPostScript(psToken, gridPtr->colorPtr,
        gridPtr->lineWidth, &(gridPtr->dashes), CapButt, JoinMiter);
    if(gridPtr->x.nSegments > 0) {
        Rbc2DSegmentsToPostScript(psToken, gridPtr->x.segments,
            gridPtr->x.nSegments);
    }
    if(gridPtr->y.nSegments > 0) {
        Rbc2DSegmentsToPostScript(psToken, gridPtr->y.segments,
            gridPtr->y.nSegments);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RbcDestroyGrid --
 *
 *      TODO: Description
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Grid GC is released.
 *
 *----------------------------------------------------------------------
 */
void
RbcDestroyGrid(
    RbcGraph * graph)
{
RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;

    Tk_FreeOptions(configSpecs, (char *)gridPtr, graph->widget.display,
        RbcGraphType(graph));
    if(gridPtr->gc != NULL) {
        RbcFreePrivateGC(graph->widget.display, gridPtr->gc);
    }
    if(gridPtr->x.segments != NULL) {
        ckfree((char *)gridPtr->x.segments);
    }
    if(gridPtr->y.segments != NULL) {
        ckfree((char *)gridPtr->y.segments);
    }
    ckfree((char *)gridPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * RbcCreateGrid --
 *
 *      Creates and initializes a new grid structure.
 *
 * Results:
 *      Returns TCL_ERROR if the configuration failed, otherwise TCL_OK.
 *
 * Side Effects:
 *      Memory for grid structure is allocated.
 *
 *----------------------------------------------------------------------
 */
int
RbcCreateGrid(
    RbcGraph * graph)
{
RbcGrid *gridPtr;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    gridPtr=ckalloc(sizeof(RbcGrid));
    assert(gridPtr);
    memset(gridPtr,0,sizeof(RbcGrid));
    gridPtr->minorGrid = TRUE;
    graph->gridPtr = gridPtr;

    if(RbcConfigureWidgetComponent(graph->widget.interp, tkWin, "grid",
            "Grid", configSpecs, 0, NULL, (char *)gridPtr,
            RbcGraphType(graph)) != TCL_OK) {
        return TCL_ERROR;
    }
    ConfigureGrid(graph, gridPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *      Queries configuration attributes of the grid such as line
 *      width, dashes, and position.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
CgetOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    return Tk_ConfigureValue(interp, tkWin, configSpecs,
        (char *)gridPtr, Tcl_GetString(objv[3]), RbcGraphType(graph));
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 *      Queries or resets configuration attributes of the grid
 *      such as line width, dashes, and position.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side Effects:
 *      Grid attributes are reset.  The graph is redrawn at the
 *      next idle point.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;
    int flags;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    flags = RbcGraphType(graph) | TK_CONFIG_ARGV_ONLY;
    if(objc == 3) {
        return Tk_ConfigureInfo(interp, tkWin, configSpecs,
            (char *)gridPtr, (char *)NULL, flags);
    } else if(objc == 4) {
        return Tk_ConfigureInfo(interp, tkWin, configSpecs,
            (char *)gridPtr, Tcl_GetString(objv[3]), flags);
    }
    if(Tk_ConfigureWidget(graph->widget.interp, tkWin, configSpecs,
            objc - 3, (void *)(objv + 3), (char *)gridPtr, flags|TK_CONFIG_OBJS) != TCL_OK) {
        return TCL_ERROR;
    }
    ConfigureGrid(graph, gridPtr);
    graph->flags |= RBC_REDRAW_BACKING_STORE;
    RbcEventuallyRedrawGraph(graph);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MapOp --
 *
 *      Maps the grid.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side Effects:
 *      Grid attributes are reset and the graph is redrawn if necessary.
 *
 *----------------------------------------------------------------------
 */
static int
MapOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;

    if(gridPtr->hidden) {
        gridPtr->hidden = FALSE;        /* Changes "-hide" configuration option */
        graph->flags |= RBC_REDRAW_BACKING_STORE;
        RbcEventuallyRedrawGraph(graph);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MapOp --
 *
 *      Maps or unmaps the grid (off or on).
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side Effects:
 *      Grid attributes are reset and the graph is redrawn if necessary.
 *
 *----------------------------------------------------------------------
 */
static int
UnmapOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;

    if(!gridPtr->hidden) {
        gridPtr->hidden = TRUE; /* Changes "-hide" configuration option */
        graph->flags |= RBC_REDRAW_BACKING_STORE;
        RbcEventuallyRedrawGraph(graph);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ToggleOp --
 *
 *      Toggles the state of the grid shown/hidden.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side Effects:
 *      Grid is hidden/displayed. The graph is redrawn at the next
 *      idle time.
 *
 *----------------------------------------------------------------------
 */
static int
ToggleOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    RbcGrid *gridPtr = (RbcGrid *) graph->gridPtr;

    gridPtr->hidden = (!gridPtr->hidden);
    graph->flags |= RBC_REDRAW_BACKING_STORE;
    RbcEventuallyRedrawGraph(graph);
    return TCL_OK;
}

static RbcOpSpec gridOps[] = {
    {"cget", 2, (RbcOp) CgetOp, 4, 4, "option",},
    {"configure", 2, (RbcOp) ConfigureOp, 3, 0, "?options...?",},
    {"off", 2, (RbcOp) UnmapOp, 3, 3, "",},
    {"on", 2, (RbcOp) MapOp, 3, 3, "",},
    {"toggle", 1, (RbcOp) ToggleOp, 3, 3, "",},
};

static int nGridOps = sizeof(gridOps) / sizeof(RbcOpSpec);

/*
 *----------------------------------------------------------------------
 *
 * RbcGridOp --
 *
 *      User routine to configure grid lines.  Grids are drawn
 *      at major tick intervals across the graph.
 *
 * Results:
 *      The return value is a standard Tcl result.
 *
 * Side Effects:
 *      Grid may be drawn in the plotting area.
 *
 *----------------------------------------------------------------------
 */
int
RbcGridOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj* const objv[])
{
    RbcOp proc;

    proc = RbcGetOp(interp, nGridOps, gridOps, RBC_OP_ARG2, objc, objv, 0);
    if(proc == NULL) {
        return TCL_ERROR;
    }
    return (*proc) (graph, interp, objc, objv);
}

/* vim: set ts=4 sw=4 sts=4 ff=unix et : */
