/*
 * tkStripchart.c --
 *
 *      This module implements "Stripchart" widgets for the Tk
 *      toolkit.
 *
 * Copyright 1992 Regents of the University of Victoria, Wellington, NZ.
 * This code is derived from the tkScale widget.
 * Copyright 1990 Regents of the University of California.
 * 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.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 *
 *  BUGS:
 *   sometimes the callback procedure doesn't work (????)
 *    (it can often be made to work by setting a (different) value and
 *     restarting the callback procedure.)
 *
 *  CHANGES:
 *   would be nicer having tick interval instead of numticks.
 *
 */
static const char rcsid[] =
    "@(#) $Header: /work/projects/tove/cvs/src/testing/vat/tkStripchart.c,v 1.1 1997/12/08 17:22:54 parnanen Exp $ (LBL)";

#include <stdio.h>
#include <string.h>
#include <math.h>
#include "config.h"
#include "tk.h"

#if TK_MINOR_VERSION<1
#define Tk_Cursor Cursor
#endif

#define BLACK		"Black"
#define WHITE		"White"
#define GRAY		"#b0b0b0"
#define BISQUE1		"#ffe4c4"
#define MAROON		"#b03060"

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/*
 *  Default stripchart configuration values
 */

#define DEF_STRIPCHART_BG_COLOR            BISQUE1
#define DEF_STRIPCHART_STRIP_COLOR         BLACK
#define DEF_STRIPCHART_TEXT_COLOR          BLACK
#define DEF_STRIPCHART_TICK_COLOR          GRAY
#define DEF_STRIPCHART_CURSOR              0

#define ALT_STRIPCHART_BG_COLOR            MAROON
#define ALT_STRIPCHART_STRIP_COLOR         WHITE
#define ALT_STRIPCHART_TEXT_COLOR          WHITE
#define ALT_STRIPCHART_TICK_COLOR          GRAY

/*
 * for monochrome displays
 */
#define DEF_STRIPCHART_BG_MONO             WHITE
#define DEF_STRIPCHART_STRIP_MONO          BLACK
#define DEF_STRIPCHART_TEXT_MONO           BLACK
#define DEF_STRIPCHART_TICK_MONO           BLACK

#define ALT_STRIPCHART_BG_MONO             BLACK
#define ALT_STRIPCHART_STRIP_MONO          WHITE
#define ALT_STRIPCHART_TEXT_MONO           WHITE
#define ALT_STRIPCHART_TICK_MONO           WHITE

#define DEF_STRIPCHART_NUMSTRIPS           "40"
#define DEF_STRIPCHART_NUMTICKS            "11"
#define DEF_STRIPCHART_BORDER_WIDTH        "3"
#define DEF_STRIPCHART_FONT                "*-Helvetica-Bold-R-Normal-*-120-*"
#define DEF_STRIPCHART_MINVALUE            "0"
#define DEF_STRIPCHART_MAXVALUE            "1000"
#define DEF_STRIPCHART_CALLBACK_INTERVAL   "500"
#define DEF_STRIPCHART_TITLE               0
#define DEF_STRIPCHART_HEIGHT              "80"
#define DEF_STRIPCHART_RELIEF              "flat"
#define DEF_STRIPCHART_TICK_INTERVAL       "50"
#define DEF_STRIPCHART_WIDTH               "1"
#define DEF_STRIPCHART_STRIPBORDERWIDTH    "0"
#define DEF_STRIPCHART_STRIPRELIEF         "flat"
#define DEF_STRIPCHART_SHOWTICKS           "true"
#define DEF_STRIPCHART_AUTOSCALE           "0"
#define DEF_STRIPCHART_HTICKS              "0"
#define DEF_USERBITS                       "0"
#define DEF_USERDATA                       0
#define DEF_GUARANTEE_DRAW                 "false"

#define  MAX_STRIPS              100
#define  MAX_TICKS               40
#define  PADDING                 2
#define  hasatitle(d)            (((d)->title != NULL) && \
                                  ((d)->title[0] != '\0'))
#ifndef abs
#define  abs(x)                  ( (x) < 0 ? -(x) : (x) )
#endif

/*
 * A data structure of the following type is kept for each
 * Stripchart that currently exists for this process:
 */

typedef struct strip_struct Stripchart;

struct strip_struct {
	Tk_Window tkwin;	/* Window that embodies the Stripchart.  NULL
				 * means that the window has been destroyed
				 * but the data structures haven't yet been
				 * cleaned up.*/
	Tcl_Interp *interp;	/* Interpreter associated with widget.  Used
				 * to delete widget command.  */
	Tk_Uid screenName;	/* If this window isn't a toplevel window
				 * then this is NULL;  otherwise it gives the
				 * name of the screen on which window is
				 * displayed. */
	Tk_3DBorder border;	/* Structure used to draw 3-D border and
				 * background. */
	int borderWidth;	/* Width of 3-D border (if any). */
	int relief;		/* 3-d effect: TK_RELIEF_RAISED etc. */
	Tk_Cursor cursor;	/* Current cursor for window, or None. */
	Tk_3DBorder stripBorder;/* Structure used to draw the strips */
	int stripBorderWidth;
	int stripRelief;
	int strip_width;	/* width of a strip */
	int max_height;		/* maximum height of a strip */
	int num_strips;		/* the number of strips */
	int stripstodisplay;	/* how many of the num_strips should be
				 * displayed. */
	int num_ticks;		/* the number of ticks to display */
	double *value;		/* the data to be displayed in strip form. */
	double min_value;
	double max_value;
	double data_min;
	double data_max;
	int scrollrequired;
	int guarantee_draw;
	int grow_up;
	XFontStruct *fontPtr;	/* Information about text font, or NULL. */
	XColor *textColorPtr;	/* Color for drawing text. */
	GC textGC;		/* GC for drawing text. */
	XColor *tickColorPtr;	/* Color for drawing ticks. */
	GC tickGC;		/* GC for drawing ticks. */
	int showticks;
	int autoscale;
	int hticks;
	int lasthtick;

	/*
	 * call back stuff
	 */
	Tk_TimerToken timer;	/* tk timer used by callback */
	char *command;		/* Command used for callback. Malloc'ed. */
	char *rescale_command;	/* Command invoked on autorescale. Malloc'ed. */
	int continue_callback;	/* boolean flag used to terminate the
				 * callback */
	unsigned long interval;	/* interval (mS) between callbacks */
	double (*callback_func)(Tcl_Interp* interp, Stripchart* stripPtr);
			        /* Function to be invoked on callback. */
	char *title;		/* Label to display above of stripchart; NULL
				 * means don't display a title.  Malloc'ed. */
	int displaybits;	/* Various flags;  see below for definitions. */
	int userbits;
	char *userdata;
	/*
	 * Alternative colour scheme
	 */
	Tk_3DBorder altborder;	/* Structure used to draw border  */
	Tk_3DBorder altstripBorder;/* structure used to draw strip  */
	XColor *a_textColor;	/* alternative text color         */
	XColor *a_tickColor;	/* alternative tick color         */

};

/*
 * displaybits for Stripcharts:
 *
 * REDRAW_PENDING:              Non-zero means a DoWhenIdle handler
 *                              has already been queued to redraw
 *                              this window.
 * CLEAR_NEEDED:                Need to clear the window when redrawing.
 * DISPLAY_TITLE:               Draw the title, if there is one.
 * DISPLAY_STRIPS:              Draw the strips.
 * DISPLAY_BORDER:              Draw the border.
 * DISPLAY_TICKS:               Draw the ticks.
 * DISPLAY_ALL:                 Display everything.
 */

#define   REDRAW_PENDING		1
#define   CLEAR_NEEDED	        	2
#define   DISPLAY_TITLE                 4
#define   DISPLAY_STRIPS                8
#define   DISPLAY_BORDER               16
#define   DISPLAY_TICKS                32
#define   DISPLAY_ALL                  (DISPLAY_TITLE | DISPLAY_STRIPS | \
					DISPLAY_BORDER | DISPLAY_TICKS)

static Tk_ConfigSpec configSpecs[] =
{
	{TK_CONFIG_BORDER, "-altbackground", "altbackground", "Background",
	 ALT_STRIPCHART_BG_COLOR, Tk_Offset(Stripchart, altborder),
	 TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_BORDER, "-altbackground", "altbackground", "Background",
	 ALT_STRIPCHART_BG_MONO, Tk_Offset(Stripchart, altborder),
	 TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_BORDER, "-altstripcolor", "altstripcolor",
	 "Foreground", ALT_STRIPCHART_STRIP_COLOR,
	 Tk_Offset(Stripchart, altstripBorder), TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_BORDER, "-altstripcolor", "altstripcolor",
	 "Foreground", ALT_STRIPCHART_STRIP_MONO,
	 Tk_Offset(Stripchart, altstripBorder), TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_COLOR, "-alttextcolor", "textcolor", "Foreground",
	 ALT_STRIPCHART_TEXT_COLOR, Tk_Offset(Stripchart, a_textColor),
	 TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_COLOR, "-alttextcolor", "textcolor", "Foreground",
	 ALT_STRIPCHART_TEXT_MONO, Tk_Offset(Stripchart, a_textColor),
	 TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_COLOR, "-alttickcolor", "tickcolor", "Foreground",
	 ALT_STRIPCHART_TICK_COLOR, Tk_Offset(Stripchart, a_tickColor),
	 TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_COLOR, "-alttickcolor", "tickcolor", "Foreground",
	 ALT_STRIPCHART_TICK_MONO, Tk_Offset(Stripchart, a_tickColor),
	 TK_CONFIG_MONO_ONLY},

	{TK_CONFIG_BORDER, "-background", "background", "Background",
	 DEF_STRIPCHART_BG_COLOR, Tk_Offset(Stripchart, border),
	 TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_BORDER, "-background", "background", "Background",
	 DEF_STRIPCHART_BG_MONO, Tk_Offset(Stripchart, border),
	 TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_SYNONYM, "-bd", "borderWidth", 0, 0, 0, 0},
	{TK_CONFIG_SYNONYM, "-bg", "background", 0, 0, 0, 0},
	{TK_CONFIG_INT, "-borderwidth", "borderWidth", "BorderWidth",
	 DEF_STRIPCHART_BORDER_WIDTH, Tk_Offset(Stripchart, borderWidth), 0},
	{TK_CONFIG_STRING, "-command", "command", "Command",
	 0, Tk_Offset(Stripchart, command), 0},
	{TK_CONFIG_STRING, "-rescale_command", "rescale_command", "RescaleCommand",
	 0, Tk_Offset(Stripchart, rescale_command), 0},
	{TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	 DEF_STRIPCHART_CURSOR, Tk_Offset(Stripchart, cursor),
	 TK_CONFIG_NULL_OK},

	{TK_CONFIG_STRING, "-data", "data", "Data",
	 DEF_USERDATA, Tk_Offset(Stripchart, userdata), 0},
	{TK_CONFIG_SYNONYM, "-fg", "stripcolor", 0,
	 0, 0, 0},
	{TK_CONFIG_FONT, "-font", "font", "Font",
	 DEF_STRIPCHART_FONT, Tk_Offset(Stripchart, fontPtr),
	 0},
	{TK_CONFIG_BOOLEAN, "-guaranteedrawing", "guaranteedrawing",
	 "Guaranteedrawing", DEF_GUARANTEE_DRAW,
	 Tk_Offset(Stripchart, guarantee_draw), 0},
	{TK_CONFIG_INT, "-height", "height", "Height", DEF_STRIPCHART_HEIGHT,
	 Tk_Offset(Stripchart, max_height), 0},
	{TK_CONFIG_INT, "-interval", "interval", "Interval",
      DEF_STRIPCHART_CALLBACK_INTERVAL, Tk_Offset(Stripchart, interval), 0},
	{TK_CONFIG_DOUBLE, "-max", "max", "Max",
	 DEF_STRIPCHART_MAXVALUE, Tk_Offset(Stripchart, max_value), 0},
	{TK_CONFIG_DOUBLE, "-min", "min", "Min",
	 DEF_STRIPCHART_MINVALUE, Tk_Offset(Stripchart, min_value), 0},
	{TK_CONFIG_INT, "-numstrips", "numstrips", "Numstrips",
	 DEF_STRIPCHART_NUMSTRIPS, Tk_Offset(Stripchart, num_strips), 0},
	{TK_CONFIG_INT, "-numticks", "numticks", "Numticks",
	 DEF_STRIPCHART_NUMTICKS, Tk_Offset(Stripchart, num_ticks), 0},
	{TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	 DEF_STRIPCHART_RELIEF, Tk_Offset(Stripchart, relief), 0},
	{TK_CONFIG_BOOLEAN, "-showticks", "showticks", "Showticks",
	 DEF_STRIPCHART_SHOWTICKS, Tk_Offset(Stripchart, showticks), 0},
	{TK_CONFIG_INT, "-autoscale", "autoscale", "Autoscale",
	 DEF_STRIPCHART_AUTOSCALE, Tk_Offset(Stripchart, autoscale), 0},
	{TK_CONFIG_INT, "-hticks", "hticks", "Hticks",
	 DEF_STRIPCHART_HTICKS, Tk_Offset(Stripchart, hticks), 0},
	{TK_CONFIG_INT, "-stripborderwidth", "stripborderwidth",
	 "Stripborderwidth", DEF_STRIPCHART_STRIPBORDERWIDTH,
	 Tk_Offset(Stripchart, stripBorderWidth), 0},
	{TK_CONFIG_BORDER, "-stripcolor", "stripcolor", "Stripcolor",
	 DEF_STRIPCHART_STRIP_COLOR, Tk_Offset(Stripchart, stripBorder),
	 TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_BORDER, "-stripcolor", "stripcolor", "Stripcolor",
	 DEF_STRIPCHART_STRIP_MONO, Tk_Offset(Stripchart, stripBorder),
	 TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_RELIEF, "-striprelief", "striprelief", "Striprelief",
	 DEF_STRIPCHART_STRIPRELIEF, Tk_Offset(Stripchart, stripRelief), 0},
	{TK_CONFIG_INT, "-stripwidth", "stripwidth", "Stripwidth",
	 DEF_STRIPCHART_WIDTH, Tk_Offset(Stripchart, strip_width), 0},
	{TK_CONFIG_COLOR, "-textcolor", "textcolor", "Textcolor",
	 DEF_STRIPCHART_TEXT_COLOR, Tk_Offset(Stripchart, textColorPtr),
	 TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_COLOR, "-textcolor", "textcolor", "Textcolor",
	 DEF_STRIPCHART_TEXT_MONO, Tk_Offset(Stripchart, textColorPtr),
	 TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_COLOR, "-tickcolor", "tickcolor", "Tickcolor",
	 DEF_STRIPCHART_TICK_COLOR, Tk_Offset(Stripchart, tickColorPtr),
	 TK_CONFIG_COLOR_ONLY},
	{TK_CONFIG_COLOR, "-tickcolor", "tickcolor", "Tickcolor",
	 DEF_STRIPCHART_TICK_MONO, Tk_Offset(Stripchart, tickColorPtr),
	 TK_CONFIG_MONO_ONLY},
	{TK_CONFIG_STRING, "-title", "title", "Title",
	 DEF_STRIPCHART_TITLE, Tk_Offset(Stripchart, title), 0},
	{TK_CONFIG_BOOLEAN, "-up", "up", "Up", "true",
	 Tk_Offset(Stripchart, grow_up), 0},
	{TK_CONFIG_INT, "-userbits", "userbits", "Userbits",
	 DEF_USERBITS, Tk_Offset(Stripchart, userbits), 0},
	{TK_CONFIG_INT, "-width", "width", "Width", DEF_STRIPCHART_WIDTH,
	 Tk_Offset(Stripchart, strip_width), 0},
	{TK_CONFIG_END, 0, 0, 0,
	 0, 0, 0}
};

/*
 * Forward declarations for procedures defined later in this file:
 */
static void Callback(Stripchart* StripchartPtr);
static void ComputeStripchartGeometry(Stripchart* StripchartPtr);
static int ConfigureStripchart(Tcl_Interp* interp, Stripchart* StripchartPtr,
				int argc, char** argv, int flags);
static void DestroyStripchart(ClientData clientData);
static void DisplayStripchart(ClientData clientData);
static void DrawStripi(Stripchart* StripchartPtr, int i);
static void EventuallyRedrawStripchart(Stripchart* StripchartPtr,
				       int displaybits);
static void ReplaceColours(Stripchart* StripchartPtr, int argc, char** argv);
static void ScrollStrips(Stripchart* StripchartPtr);
static void StripchartEventProc(ClientData clientData, XEvent* eventPtr);
static int StripchartWidgetCmd(ClientData clientData, Tcl_Interp* interp,
				int argc, char** argv);
static void SetStripchartValue(Stripchart* StripchartPtr, double value);
static void SwapColours(Stripchart* StripchartPtr);

/*
 *--------------------------------------------------------------
 *
 * Tk_StripchartCmd --
 *
 *      This procedure is invoked to process the "Stripchart" and
 *      "toplevel" Tcl commands.  See the user documentation for
 *      details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *--------------------------------------------------------------
 */
int
Tk_StripchartCmd(ClientData clientData, Tcl_Interp *interp, int argc,
		 char **argv)
 {
	Tk_Window tkwin = (Tk_Window) clientData;
	Tk_Window new;
	register Stripchart *StripchartPtr;

	if (argc < 2) {
		Tcl_AppendResult(interp, "wrong # args: should be \"",
			    argv[0], " pathName ?options?\"", 0);
		return TCL_ERROR;
	}
	/*
	 * Create the window.
	 */
	new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], 0);
	if (new == NULL)
		return TCL_ERROR;

	Tk_SetClass(new, "Stripchart");
	StripchartPtr = (Stripchart*)calloc(1, sizeof(Stripchart));
	StripchartPtr->tkwin = new;
	StripchartPtr->interp = interp;

	Tk_CreateEventHandler(StripchartPtr->tkwin,
			      ExposureMask | StructureNotifyMask,
			      StripchartEventProc, (ClientData) StripchartPtr);
	Tcl_CreateCommand(interp, Tk_PathName(StripchartPtr->tkwin),
			  StripchartWidgetCmd, (ClientData)StripchartPtr,
			  0);

	if (ConfigureStripchart(interp, StripchartPtr, argc - 2, argv + 2, 0)
	    != TCL_OK) {
		Tk_DestroyWindow(StripchartPtr->tkwin);
		return TCL_ERROR;
	}
	interp->result = Tk_PathName(StripchartPtr->tkwin);
	return TCL_OK;
}

/*
 * StripchartWidgetCmd --
 *
 *      This procedure is invoked to process the Tcl command
 *      that corresponds to a Stripchart widget.  See the user
 *      documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 */
static int
StripchartWidgetCmd(ClientData clientData, Tcl_Interp *interp,
		    int argc, char **argv)
{
	register Stripchart *StripchartPtr = (Stripchart*)clientData;
	int result = TCL_OK;
	int length;
	char c;

	if (argc < 2) {
		Tcl_AppendResult(interp, "wrong # args: should be \"",
			  argv[0], " option ?arg arg ...?\"", 0);
		return TCL_ERROR;
	}
	Tk_Preserve((ClientData) StripchartPtr);
	c = argv[1][0];
	length = strlen(argv[1]);
	if (strncmp(argv[1], "configure", length) == 0) {
		if (argc == 2) {
			result = Tk_ConfigureInfo(interp, StripchartPtr->tkwin,
						  configSpecs,
						  (char*)StripchartPtr,
						  (char*)NULL, 0);
		} else if (argc == 3) {
			result = Tk_ConfigureInfo(interp, StripchartPtr->tkwin,
						  configSpecs,
						  (char*)StripchartPtr,
						  argv[2], 0);
		} else {
			result = ConfigureStripchart(interp, StripchartPtr,
						     argc - 2, argv + 2,
						     TK_CONFIG_ARGV_ONLY);
		}
	} else if (strncmp(argv[1], "get", length) == 0) {
		if (argc != 2) {
			Tcl_AppendResult(interp, "wrong # args: should be \"",
					 argv[0], " get\"", 0);
			result = TCL_ERROR;
		} else
			sprintf(interp->result, "%g",
			  StripchartPtr->value[
				  StripchartPtr->stripstodisplay - 1]);
	} else if (strncmp(argv[1], "set", length) == 0) {
		double value;

		if (argc != 3) {
			Tcl_AppendResult(interp, "wrong # args: should be \"",
				     argv[0], " set value\"", 0);
			result = TCL_ERROR;
		} else if (Tcl_GetDouble(interp, argv[2], &value) != TCL_OK)
			result = TCL_ERROR;
		 else
			SetStripchartValue(StripchartPtr, value);
	} else if (strncmp(argv[1], "start", length) == 0) {
		if (!StripchartPtr->continue_callback) {
			StripchartPtr->continue_callback = TRUE;
			Callback(StripchartPtr);
		}
	} else if (strncmp(argv[1], "stop", length) == 0) {
		StripchartPtr->continue_callback = FALSE;
	} else if (strncmp(argv[1], "reset", length) == 0) {
		StripchartPtr->stripstodisplay = 0;
		StripchartPtr->scrollrequired = FALSE;
		EventuallyRedrawStripchart(StripchartPtr,
					   DISPLAY_ALL | CLEAR_NEEDED);
	} else if (strncmp(argv[1], "swapcolours", length) == 0) {
		SwapColours(StripchartPtr);
	} else if (strncmp(argv[1], "replacecolours", length) == 0) {
		ReplaceColours(StripchartPtr, argc - 2, argv + 2);
	} else {
		Tcl_AppendResult(interp, "bad option \"", argv[1],
		   "\":  must be configure, get, set, reset, start or stop",
				 0);
		result = TCL_ERROR;
	}
	Tk_Release((ClientData) StripchartPtr);
	return result;
}

/*
 *  Callback --
 *      Execute a Tcl command repeatedly until told to stop.  Involked
 *      with the start command and stopped with the stop command.
 *
 *  Results:
 *      None.
 *
 *  Side Effects:
 *      Timer queue is changed with the addition of a command to be
 *      executed periodically.
 */
static void 
Callback(Stripchart *StripchartPtr)
{
	int result;

	StripchartPtr->timer = 0;
	if (StripchartPtr->callback_func != NULL)
		SetStripchartValue(StripchartPtr,
				   (*(StripchartPtr->callback_func))
					(StripchartPtr->interp, StripchartPtr));
	if (StripchartPtr->command != NULL &&
	    StripchartPtr->command[0] != '\0') {
		result = Tcl_Eval(StripchartPtr->interp,
				  StripchartPtr->command);
		if (result == TCL_OK)
			if (StripchartPtr->continue_callback)
				StripchartPtr->timer =
				   Tk_CreateTimerHandler(
						StripchartPtr->interval,
						(void *)Callback,
						(ClientData)StripchartPtr);
	}
}

/*
 * DestroyStripchart --
 *
 *      This procedure is invoked by Tk_EventuallyFree or Tk_Release
 *      to clean up the internal structure of a Stripchart at a safe time
 *      (when no-one is using it anymore).
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Everything associated with the Stripchart is freed up.
 */
static void
DestroyStripchart(ClientData clientData)
{
	Stripchart* StripchartPtr = (Stripchart*)clientData;

	if (StripchartPtr->timer)
		Tk_DeleteTimerHandler(StripchartPtr->timer);

	if (StripchartPtr->border != NULL)
		Tk_Free3DBorder(StripchartPtr->border);

	if (StripchartPtr->altborder != NULL)
		Tk_Free3DBorder(StripchartPtr->altborder);

	if (StripchartPtr->stripBorder != NULL)
		Tk_Free3DBorder(StripchartPtr->stripBorder);

	if (StripchartPtr->altstripBorder != NULL)
		Tk_Free3DBorder(StripchartPtr->altstripBorder);

	if (StripchartPtr->title != NULL)
		ckfree(StripchartPtr->title);

	if (StripchartPtr->command != NULL)
		ckfree(StripchartPtr->command);

	if (StripchartPtr->rescale_command != NULL)
		ckfree(StripchartPtr->rescale_command);

	if (StripchartPtr->value != NULL)
		free(StripchartPtr->value);

	if (StripchartPtr->fontPtr != NULL)
		Tk_FreeFontStruct(StripchartPtr->fontPtr);

	if (StripchartPtr->textColorPtr != NULL)
		Tk_FreeColor(StripchartPtr->textColorPtr);

	if (StripchartPtr->textGC != None && StripchartPtr->tkwin)
		Tk_FreeGC(Tk_Display(StripchartPtr->tkwin),
			  StripchartPtr->textGC);

	if (StripchartPtr->tickColorPtr != NULL)
		Tk_FreeColor(StripchartPtr->tickColorPtr);

	if (StripchartPtr->tickGC != None && StripchartPtr->tkwin)
		Tk_FreeGC(Tk_Display(StripchartPtr->tkwin),
			  StripchartPtr->tickGC);

	if (StripchartPtr->a_textColor != NULL)
		Tk_FreeColor(StripchartPtr->a_textColor);

	if (StripchartPtr->a_tickColor != NULL)
		Tk_FreeColor(StripchartPtr->a_tickColor);

	if (StripchartPtr->cursor != None && StripchartPtr->tkwin) {
		Tk_FreeCursor(Tk_Display(StripchartPtr->tkwin),
			      StripchartPtr->cursor);
	}
	ckfree((char *)StripchartPtr);
}

/*
 * ConfigureStripchart --
 *
 *      This procedure is called to process an argv/argc list, plus
 *      the Tk option database, in order to configure (or
 *      reconfigure) a Stripchart widget.
 *
 * Results:
 *      The return value is a standard Tcl result.  If TCL_ERROR is
 *      returned, then interp->result contains an error message.
 *
 * Side effects:
 *      Configuration information, such as text string, colors, font,
 *      etc. get set for StripchartPtr;  old resources get freed, if there
 *      were any.
 */
static int
ConfigureStripchart(Tcl_Interp *interp, Stripchart *StripchartPtr,
		    int argc, char** argv, int flags)
{
	XGCValues gcValues;
	GC newGC;
	int result;

	result = Tk_ConfigureWidget(interp, StripchartPtr->tkwin, configSpecs,
				    argc, argv, (char *)StripchartPtr, flags);
	if (result != TCL_OK)
		return result;

	Tk_SetBackgroundFromBorder(StripchartPtr->tkwin, StripchartPtr->border);

	gcValues.font = StripchartPtr->fontPtr->fid;
	gcValues.foreground = StripchartPtr->textColorPtr->pixel;
	newGC = Tk_GetGC(StripchartPtr->tkwin, GCForeground|GCFont, &gcValues);
	if (StripchartPtr->textGC != None && StripchartPtr->tkwin) {
		Tk_FreeGC(Tk_Display(StripchartPtr->tkwin),
			  StripchartPtr->textGC);
	}
	StripchartPtr->textGC = newGC;

	gcValues.foreground = StripchartPtr->tickColorPtr->pixel;
	newGC = Tk_GetGC(StripchartPtr->tkwin, GCForeground, &gcValues);
	if (StripchartPtr->tickGC != None && StripchartPtr->tkwin) {
		Tk_FreeGC(Tk_Display(StripchartPtr->tkwin),
			  StripchartPtr->tickGC);
	}
	StripchartPtr->tickGC = newGC;

	if (StripchartPtr->value == 0)
		StripchartPtr->value = (double*)calloc(
						StripchartPtr->num_strips,
						sizeof(*StripchartPtr->value));
	else
		StripchartPtr->value = (double*)realloc(
						StripchartPtr->value,
						StripchartPtr->num_strips *
						sizeof(*StripchartPtr->value));
	if (StripchartPtr->num_ticks > MAX_TICKS)
		StripchartPtr->num_ticks = MAX_TICKS;

	if (StripchartPtr->min_value > StripchartPtr->max_value) {
		double temp = StripchartPtr->min_value;
		StripchartPtr->min_value = StripchartPtr->max_value;
		StripchartPtr->max_value = temp;
	}
	/*
	 * Recompute display-related information, and let the geometry
	 * manager know how much space is needed now.
	 */
	ComputeStripchartGeometry(StripchartPtr);
	EventuallyRedrawStripchart(StripchartPtr, DISPLAY_ALL | CLEAR_NEEDED);
	return TCL_OK;
}

/*
 * ComputeStripchartGeometry --
 *
 *      This procedure is called to compute various geometrical
 *      information for a stripchart, such as where various things get
 *      displayed.  It's called when the window is reconfigured.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 */
static void
ComputeStripchartGeometry(Stripchart* StripchartPtr)
 {
	int tt = hasatitle(StripchartPtr);
	int bd = StripchartPtr->borderWidth;
	int lineHeight = StripchartPtr->fontPtr->ascent +
	StripchartPtr->fontPtr->descent;

	Tk_GeometryRequest(StripchartPtr->tkwin,
			   2 * (bd + PADDING) + StripchartPtr->num_strips *
			   StripchartPtr->strip_width,
			   2 * (bd + PADDING) + tt * (lineHeight + PADDING) +
			   StripchartPtr->max_height);
	Tk_SetInternalBorder(StripchartPtr->tkwin, StripchartPtr->borderWidth);
}

/*
 * DisplayStripchart --
 *
 *      This procedure is invoked to display a Stripchart widget.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Commands are output to X to display the Stripchart in its
 *      current mode.
 */
static void
DisplayStripchart(ClientData clientData)
 {
	Stripchart *StripchartPtr = (Stripchart *) clientData;
	Tk_Window tkwin = StripchartPtr->tkwin;
	int bd = StripchartPtr->borderWidth;
	int i, tt = hasatitle(StripchartPtr);

	/*
	 * Variable declarations used in the title drawing routines
	 */
	XFontStruct *fp = StripchartPtr->fontPtr;
	XCharStruct bbox;
	int x, dummy;
	int lineHeight = StripchartPtr->fontPtr->ascent +
	StripchartPtr->fontPtr->descent;

	StripchartPtr->displaybits &= ~REDRAW_PENDING;
	if ((StripchartPtr->tkwin == NULL) || !Tk_IsMapped(tkwin))
		return;

	/*
	 * Clear the window if necessary
	 */
	if (StripchartPtr->displaybits & CLEAR_NEEDED)
		XClearWindow(Tk_Display(tkwin), Tk_WindowId(tkwin));

	/*
	 * Display the title, centered in the window if there is enough
	 * space. Otherwise left justified and clipped on the right.
	 */
	if (tt && StripchartPtr->displaybits & DISPLAY_TITLE) {
		XTextExtents(fp, StripchartPtr->title,
			     strlen(StripchartPtr->title),
			     &dummy, &dummy, &dummy, &bbox);
		if (bbox.lbearing + bbox.rbearing < Tk_Width(tkwin) - 2 * bd)
			x = (Tk_Width(tkwin) - bbox.lbearing - bbox.rbearing)/2;
		else
			x = bd + PADDING;

		XClearArea(Tk_Display(tkwin), Tk_WindowId(tkwin), bd, bd,
		     Tk_Width(tkwin) - 2 * bd, lineHeight + PADDING, False);
		XDrawString(Tk_Display(tkwin), Tk_WindowId(tkwin),
		       StripchartPtr->textGC, x, fp->max_bounds.ascent + bd,
			StripchartPtr->title, strlen(StripchartPtr->title));
	}
	/*
	 * draw the strips
	 */
	if (StripchartPtr->displaybits & CLEAR_NEEDED) {
		StripchartPtr->displaybits &= ~CLEAR_NEEDED;
		StripchartPtr->lasthtick = 0;
		for (i = 1; i <= StripchartPtr->stripstodisplay; i++)
			DrawStripi(StripchartPtr, i);
	} else {
		if (StripchartPtr->stripstodisplay == StripchartPtr->num_strips) {
			if (!StripchartPtr->scrollrequired)
				StripchartPtr->scrollrequired = TRUE;
			else
				ScrollStrips(StripchartPtr);
		}
		DrawStripi(StripchartPtr, StripchartPtr->stripstodisplay);
	}

	/*
	 * Display the border
	 */
	if (StripchartPtr->displaybits & DISPLAY_BORDER) {
		Tk_Draw3DRectangle(tkwin, Tk_WindowId(tkwin),
			       StripchartPtr->border, 0, 0, Tk_Width(tkwin),
			       Tk_Height(tkwin), StripchartPtr->borderWidth,
				   StripchartPtr->relief);
	}
}

/*
 * StripchartEventProc --
 *
 *      This procedure is invoked by the Tk dispatcher on
 *      structure changes to a Stripchart.  For Stripcharts with 3D
 *      borders, this procedure is also invoked for exposures.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      When the window gets deleted, internal structures get
 *      cleaned up.  When it gets exposed, it is redisplayed.
 */

static void
StripchartEventProc(ClientData clientData, XEvent *eventPtr)
 {
	Stripchart *StripchartPtr = (Stripchart*)clientData;

	if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
		if ((StripchartPtr->tkwin != NULL) &&
		    !(StripchartPtr->displaybits & REDRAW_PENDING)) {
			EventuallyRedrawStripchart(StripchartPtr,
						   DISPLAY_ALL|CLEAR_NEEDED);
			/*
			 * A clear isn't technically needed as the bitmap is
			 * clear when the window is exposed.  This flag is
			 * used to indicate that all strips need to be drawn,
			 * not just the most recent one.
			 */
		}
	} else if (eventPtr->type == DestroyNotify) {
		Tcl_DeleteCommand(StripchartPtr->interp,
				  Tk_PathName(StripchartPtr->tkwin));
		StripchartPtr->tkwin = NULL;
		if (StripchartPtr->displaybits & REDRAW_PENDING) {
			Tk_CancelIdleCall(DisplayStripchart,
					  (ClientData)StripchartPtr);
		}
		Tk_EventuallyFree((ClientData)StripchartPtr, DestroyStripchart);
	} else if (eventPtr->type == ConfigureNotify) {
		int n = eventPtr->xconfigure.width;
		n -= 2 * (StripchartPtr->borderWidth + PADDING);
		n /= StripchartPtr->strip_width;
		if (n != StripchartPtr->num_strips) {
			StripchartPtr->num_strips = n;
			if (StripchartPtr->value == 0)
				StripchartPtr->value =
					(double*)calloc(n, sizeof(double));
			else
				StripchartPtr->value =
					(double*)realloc(StripchartPtr->value,
							 n * sizeof(double));
		}
	}
}

static double chose_range(const double* lim, int n, double range)
{
	double lr = pow(10., floor(log10(range)));
	double b = range / lr - 0.002;
	int i;
	for (i = 0; i < n; ++i)
		if (b < lim[i])
			break;
	return ((lim[i]) * lr);
}

/*
 * choose a 'nice' lower limit for v (one on a 2/5/10 boundary)
 */
static int chose_scale(Stripchart* sp, double min, double max)
{
	const static double rlim[] = {
		1.0, 2.0, 5.0, 10.0
	};
	const static double blim[] = {
		1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0
	};
	double range = max - min;
	if (range < 1e-9)
		return (0);

	range = chose_range(blim, 9, range);
	min = (double)((int)(min / range)) * range;
	max = chose_range(rlim, 3, max - min) + min;
	if (max != sp->max_value || min != sp->min_value) {
		sp->min_value = min;
		sp->max_value = max;
		if (sp->rescale_command) {
			char scmd[1024];
			double n = sp->num_ticks - 1;
			if (n <= 0.)
				n = 1.;
			sprintf(scmd, "%s %g %g %g", sp->rescale_command,
				min, max, (max - min) / n);
			(void)Tcl_Eval(sp->interp, scmd);
		}
		return (1);
	}
	return (0);
}

/*
 * SetStripchartValue --
 *
 *      This procedure changes the value of a Stripchart and invokes
 *      a Tcl command to reflect the current position of a Stripchart
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      (Re) Sets the scrollrequired flag.
 */
static void
SetStripchartValue(Stripchart *StripchartPtr, double value)
{
	int std = StripchartPtr->stripstodisplay;
	int maxvalues = StripchartPtr->num_strips;
	int flags = DISPLAY_STRIPS;

	if (std != maxvalues) {
		StripchartPtr->value[std] = value;
		StripchartPtr->stripstodisplay = ++std;
	} else {
		int n = maxvalues - 1;
		memcpy(StripchartPtr->value, StripchartPtr->value + 1,
		       n * sizeof(*StripchartPtr->value)); 
		StripchartPtr->value[n] = value;
	}
	if (StripchartPtr->autoscale) {
		double min = value; double max = value;
		double* vp = StripchartPtr->value;
		int i;
		for (i = 0; i < std; ++i) {
			double v = vp[i];
			if (v < min)
				min = v;
			if (v > max)
				max = v;
		}
		if (StripchartPtr->autoscale == 1)
			/* only adjust max */
			min = StripchartPtr->min_value;

		if (max != StripchartPtr->data_max  ||
		    min != StripchartPtr->data_min) {
			StripchartPtr->data_min = min;
			StripchartPtr->data_max = max;
			if (chose_scale(StripchartPtr, min, max))
				flags |= DISPLAY_ALL|CLEAR_NEEDED;
		}
	}
	EventuallyRedrawStripchart(StripchartPtr, flags);
}

/*
 * EventuallyRedrawStripchart --
 *
 *      Arrange for part or all of a stripchart widget to redrawn at
 *      the next convenient time in the future.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 */
static void
EventuallyRedrawStripchart(Stripchart *StripchartPtr, int displaybits)
{

	if (StripchartPtr->tkwin == NULL
	    || !Tk_IsMapped(StripchartPtr->tkwin)
	    || (StripchartPtr->displaybits & REDRAW_PENDING))
		return;

	StripchartPtr->displaybits = displaybits | REDRAW_PENDING;
	if (StripchartPtr->guarantee_draw)
		DisplayStripchart((ClientData) StripchartPtr);
	else
		Tk_DoWhenIdle(DisplayStripchart, (ClientData)StripchartPtr);
}

/*
 * SwapColours --
 *
 *     Save the current colour scheme so that it may be
 *     restored at some later stage.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     Colour scheme is swapped and Stripchart is redisplayed with
 *     the new colour scheme.
 */

static void 
SwapColours(Stripchart * StripchartPtr)
{
	Tk_3DBorder tempb;
	XColor *tempc;

#define SWAP(a,b,c) { (c) = (a); (a) = (b); (b) = (c); }

	SWAP(StripchartPtr->altborder, StripchartPtr->border, tempb);
	SWAP(StripchartPtr->altstripBorder, StripchartPtr->stripBorder, tempb);

	SWAP(StripchartPtr->a_textColor, StripchartPtr->textColorPtr, tempc);
	SWAP(StripchartPtr->a_tickColor, StripchartPtr->tickColorPtr, tempc);

#undef SWAP

	ConfigureStripchart(StripchartPtr->interp, StripchartPtr, 0, NULL,
			    TK_CONFIG_ARGV_ONLY);
}

/*
 * ReplaceColours --
 *
 *     Store the current colour scheme and replace it with
 *     the new one.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     Stripchart is displayed with the new colour scheme.
 */
static void 
ReplaceColours(Stripchart* StripchartPtr, int argc, char** argv)
{
	StripchartPtr->altborder =
		Tk_Get3DBorder(StripchartPtr->interp, StripchartPtr->tkwin,
			       Tk_NameOf3DBorder(StripchartPtr->border));
	StripchartPtr->altstripBorder =
		Tk_Get3DBorder(StripchartPtr->interp, StripchartPtr->tkwin,
			       Tk_NameOf3DBorder(StripchartPtr->stripBorder));
	StripchartPtr->a_textColor =
		Tk_GetColorByValue(StripchartPtr->tkwin,
				   StripchartPtr->textColorPtr);
	StripchartPtr->a_tickColor =
		Tk_GetColorByValue(StripchartPtr->tkwin,
				   StripchartPtr->tickColorPtr);

	ConfigureStripchart(StripchartPtr->interp, StripchartPtr, argc, argv,
			    TK_CONFIG_ARGV_ONLY);
}

/*
 * DrawStripi --
 *
 *     Draw the i-th strip of the stripchart.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     A new strip is drawn.
 */
static void 
DrawStripi(Stripchart* SPtr, int i)
{
	Tk_Window tkwin = SPtr->tkwin;
	int lineHeight = SPtr->fontPtr->ascent + SPtr->fontPtr->descent;
	int x = SPtr->borderWidth + PADDING + (i - 1) * SPtr->strip_width;
	int y = SPtr->borderWidth + PADDING +
		hasatitle(SPtr) * (lineHeight + PADDING);
	int w = SPtr->strip_width;
	int h;
	double maxv = SPtr->max_value;
	double minv = SPtr->min_value;

	if (i < 1 || i > SPtr->num_strips)
		return;

	/* Clear any strip that might be below this one */
	XClearArea(Tk_Display(tkwin), Tk_WindowId(tkwin), x, y, w,
		   SPtr->max_height, FALSE);

	/* Calculate the height of the bar */
	if (maxv == minv)
		h = 0;
	else
		h = (int)((double)SPtr->max_height * (SPtr->value[i - 1] - minv)
		          / (maxv - minv));
	if (h > SPtr->max_height)
		h = SPtr->max_height;
	if (h == 0)
		h = 1;

	/* Adject the origin of the bars rectangle depending on its origin */
	if (SPtr->grow_up)
		y += (SPtr->max_height - h);

	/* draw x axis tick mark, if any */
	if (SPtr->hticks > 0 && ++SPtr->lasthtick >= SPtr->hticks) {
		SPtr->lasthtick = 0;
		XDrawLine(Tk_Display(tkwin), Tk_WindowId(tkwin), SPtr->tickGC,
			  x, SPtr->borderWidth + PADDING + 
			     hasatitle(SPtr) * (lineHeight + PADDING),
			  x, SPtr->max_height);
	}

	/* Draw the y axis ticks, if any */
	if (SPtr->showticks) {
		XSegment ticks[MAX_TICKS];

		for (i = 0; i < SPtr->num_ticks; i++) {
			ticks[i].x1 = x;
			ticks[i].x2 = x + SPtr->strip_width - 1;
			ticks[i].y1 = ticks[i].y2 = SPtr->borderWidth +
					PADDING + hasatitle(SPtr) *
					(lineHeight + PADDING) + i *
					SPtr->max_height / (SPtr->num_ticks-1);
		}
		XDrawSegments(Tk_Display(tkwin), Tk_WindowId(tkwin),
			      SPtr->tickGC, ticks, SPtr->num_ticks);
	}

	/* Draw the bar */
	Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin),
			   SPtr->stripBorder, x, y, w, h,
			   SPtr->stripBorderWidth,
			   SPtr->stripRelief);
}

/*
 * ScrollStrips --
 *
 *     Scroll the strips in the stripchart region to the left
 *     by the width of a strip pixels.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     Display changes.
 */
static void 
ScrollStrips(Stripchart* SPtr)
{
	Tk_Window tkwin = SPtr->tkwin;
	int lineHeight = SPtr->fontPtr->ascent + SPtr->fontPtr->descent;
	int src_x = SPtr->borderWidth + PADDING + SPtr->strip_width;
	int src_y = SPtr->borderWidth + PADDING +
		    hasatitle(SPtr) * (lineHeight + PADDING);
	int dest_x = src_x - SPtr->strip_width;
	int dest_y = src_y;
	int w = (SPtr->num_strips - 1) * SPtr->strip_width;
	int h = SPtr->max_height;

	XCopyArea(Tk_Display(tkwin), Tk_WindowId(tkwin), Tk_WindowId(tkwin),
	          Tk_GetGC(tkwin, 0, NULL), src_x, src_y, w, h, dest_x, dest_y);
}
