/* $Id: plframe.c,v 1.39 1994/07/25 06:44:22 mjl Exp $
 * $Log: plframe.c,v $
 * Revision 1.39  1994/07/25  06:44:22  mjl
 * Wrapped the include of unistd.h in a HAVE_UNISTD_H.
 *
 * Revision 1.38  1994/07/19  22:31:41  mjl
 * All device drivers: enabling macro renamed to PLD_<driver>, where <driver>
 * is xwin, ps, etc.  See plDevs.h for more detail.  All internal header file
 * inclusion changed to /not/ use a search path so that it will work better
 * with makedepend.
 *
 * Revision 1.37  1994/07/01  22:38:48  mjl
 * Minor modification required by new X resource allocation scheme.
 *
 * Revision 1.36  1994/07/01  20:37:21  mjl
 * Force an "update" when widget is initially mapped, but before startup
 * procedure is invoked.  Ensures that "." has been mapped and that X is
 * in a sane state before continuing (otherwise new toplevels containing
 * plframes can cause a core dump).  Also some cruft elimination.
 *
 * Revision 1.35  1994/06/30  18:40:15  mjl
 * Cleaning up to remove gcc -Wall warnings, and other miscellanea.
 *
 * Revision 1.34  1994/06/23  22:32:47  mjl
 * Split off bulk of Tcl API in to separate file, where it can be used
 * more flexibly.
 *
 * Revision 1.33  1994/06/16  19:03:18  mjl
 * Changed "cmd plline" and "cmd plpoin" widget commands to use new Matrix
 * notation/capabilities/etc.
 *
 * Revision 1.32  1994/06/15  17:21:32  furnish
 * Fix cleanup so that killing a plframe doesn't core the app.
 *
 * Revision 1.31  1994/06/10  20:46:10  furnish
 * Mirror plpoin.  More of the API still needs doing.
 *
 * Revision 1.30  1994/06/09  20:15:30  mjl
 * Changed plplot direct widget commands ("<widget> cmd <command> <args>") to
 * begin with a "pl", e.g. scol<?> to plscol<?>, etc.  To make going between
 * the C and Tcl API's as natural as possible.  Added new direct widget
 * commands plenv, plcol, pllab, and plline.  These were formerly in
 * tkshell.c; having them here makes it trivial to associate independent
 * streams with different widgets without user intervention.  The call to
 * plinit() now done automatically when the widget is first mapped, and
 * the "<widget> cmd init" command no longer supported.  Some reorganization.
 */

/*----------------------------------------------------------------------*\
 * 
 * plframe.c --
 *
 *	This module implements "plframe" widgets for the Tk toolkit.
 *	These are frames that have extra logic to allow them to be
 *	interfaced with the PLPlot X driver.  These are then drawn
 *	into and respond to keyboard and mouse events.
 *
 * Maurice LeBrun
 * IFS, University of Texas at Austin
 * 30-Apr-1993
 *
 * Based upon tkFrame.c from the TK 3.2 distribution:
 *
 * 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.
\*----------------------------------------------------------------------*/

#include "plserver.h"
#include "plplotX.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>

extern int plplot_ccmap;

#define NDEV	20		/* Max number of output device types */

/* If set, BUFFER_FIFO causes FIFO i/o to be buffered */

#define BUFFER_FIFO 1

/* If set, causes a file handler to be used with FIFO */

#define FH_FIFO 0

/* A handy command wrapper */

#define plframe_cmd(code) \
    if ((code) == TCL_ERROR) return (TCL_ERROR);

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

typedef struct {

    /* This is stuff taken from tkFrame.c */

    Tk_Window tkwin;		/* Window that embodies the frame.  NULL
				 * means that the window has been destroyed
				 * but the data structures haven't yet been
				 * cleaned up.*/
    Display *display;		/* Display containing widget.  Used, among
				 * other things, so that resources can be
				 * freed even after tkwin has gone away. */
    Tcl_Interp *interp;		/* Interpreter associated with
				 * widget.  Used to delete widget
				 * command.  */
    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. */
    int width;			/* Width to request for window.  <= 0 means
				 * don't request any size. */
    int height;			/* Height to request for window.  <= 0 means
				 * don't request any size. */
    char *geometry;		/* Geometry that user requested.  NULL
				 * means use width and height instead. 
				 * Malloc'ed. */
    Cursor cursor;		/* Current cursor for window, or None. */
    int flags;			/* Various flags;  see below for
				 * definitions. */

    /* These are new to plframe widgets */

    /* control stuff */

    int tkwin_initted;		/* Set first time widget is mapped */
    PLStream *plsc;		/* PLPlot stream pointer */
    PLINT ipls;			/* PLPlot stream number */
    PLINT ipls_save;		/* PLPlot stream number, save files */

    PLRDev *plr;		/* Renderer state information.  Malloc'ed */
    XColor *bgColor;		/* Background color */
    char *plpr_cmd;		/* Holds print command name.  Malloc'ed */

    /* Used to distinguish resize from expose events */

    int prevWidth;		/* Previous window width */
    int prevHeight;		/* Previous window height */

    /* Support for save operations */

    char *SaveFnam;		/* File name we are currently saving to.
				   Malloc'ed. */
    char **devDesc;		/* Descriptive names for file-oriented 
				 * devices.  Malloc'ed. */
    char **devName;		/* Keyword names of file-oriented devices.
				 * Malloc'ed. */

    /* Used in selecting & modifying plot or device area */

    GC xorGC;			/* GC used for rubber-band drawing */
    XPoint pts[5];		/* Points for rubber-band drawing */
    int continue_draw;		/* Set when doing rubber-band draws */
    Cursor draw_cursor;		/* cursor used for drawing */
    PLFLT xl, xr, yl, yr;	/* Bounds on plot viewing area */
    char *yScrollCmd;		/* Command prefix for communicating with
				 * vertical scrollbar.  NULL means no
				 * command to issue.  Malloc'ed. */
    char *xScrollCmd;		/* Command prefix for communicating with
				 * horizontal scrollbar.  NULL means no
				 * command to issue.  Malloc'ed. */

    /* Used for flashing bop or eop condition */

    char *bopCmd;		/* Proc to call at bop */
    char *eopCmd;		/* Proc to call at eop */

} PlFrame;

/*
 * Flag bits for plframes:
 *
 * REFRESH_PENDING:		Non-zero means a DoWhenIdle handler
 *				has already been queued to refresh
 *				this window.
 * CLEAR_NEEDED;		Need to clear the window when redrawing.
 * RESIZE_PENDING;		Used to reschedule resize events
 * REDRAW_PENDING;		Used to redraw contents of plot buffer
 * UPDATE_V_SCROLLBAR:		Non-zero means vertical scrollbar needs
 *				to be updated.
 * UPDATE_H_SCROLLBAR:		Non-zero means horizontal scrollbar needs
 *				to be updated.
 */

#define REFRESH_PENDING		1
#define CLEAR_NEEDED		2
#define RESIZE_PENDING		4
#define REDRAW_PENDING		8
#define UPDATE_V_SCROLLBAR	16
#define UPDATE_H_SCROLLBAR	32

/*
 * Defaults for plframes:
 */

#define DEF_PLFRAME_BG_COLOR		"Black"
#define DEF_PLFRAME_BG_MONO		"White"
#define DEF_PLFRAME_BORDER_WIDTH	"0"
#define DEF_PLFRAME_CURSOR		((char *) NULL)
#define DEF_PLFRAME_GEOMETRY		((char *) NULL)
#define DEF_PLFRAME_HEIGHT		"0"
#define DEF_PLFRAME_RELIEF		"flat"
#define DEF_PLFRAME_WIDTH		"0"

/* Configuration info */

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_PLFRAME_BG_COLOR, Tk_Offset(PlFrame, border),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, Tk_Offset(PlFrame, bgColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_PLFRAME_BG_MONO, Tk_Offset(PlFrame, border),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, Tk_Offset(PlFrame, bgColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
	(char *) NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
	(char *) NULL, 0, 0},
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_PLFRAME_BORDER_WIDTH, Tk_Offset(PlFrame, borderWidth), 0},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	DEF_PLFRAME_CURSOR, Tk_Offset(PlFrame, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-geometry", "geometry", "Geometry",
	DEF_PLFRAME_GEOMETRY, Tk_Offset(PlFrame, geometry), TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-bopcmd", "bopcmd", "PgCommand",
	(char *) NULL, Tk_Offset(PlFrame, bopCmd), TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-eopcmd", "eopcmd", "PgCommand",
	(char *) NULL, Tk_Offset(PlFrame, eopCmd), TK_CONFIG_NULL_OK},
    {TK_CONFIG_PIXELS, "-height", "height", "Height",
	DEF_PLFRAME_HEIGHT, Tk_Offset(PlFrame, height), 0},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_PLFRAME_RELIEF, Tk_Offset(PlFrame, relief), 0},
    {TK_CONFIG_PIXELS, "-width", "width", "Width",
	DEF_PLFRAME_WIDTH, Tk_Offset(PlFrame, width), 0},
    {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
	(char *) NULL, Tk_Offset(PlFrame, xScrollCmd), TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
	(char *) NULL, Tk_Offset(PlFrame, yScrollCmd), TK_CONFIG_NULL_OK},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, 0, 0}
};

/*
 * Forward declarations for procedures defined later in this file:
 */

/* Externals */

int   plFrameCmd     	(ClientData, Tcl_Interp *, int, char **);

/* These are invoked by the TK dispatcher */

static void  DestroyPlFrame	(ClientData);
static void  DisplayPlFrame	(ClientData);
static void  PlFrameInit	(ClientData);
static void  PlFrameEventProc	(ClientData, XEvent *);
static int   PlFrameWidgetCmd	(ClientData, Tcl_Interp *, int, char **);
static int   ReadData		(ClientData, int);
static void  Install_cmap	(PlFrame *plFramePtr);

/* These are invoked by PlFrameWidgetCmd to process widget commands */

static int   Cmd		(Tcl_Interp *, PlFrame *, int, char **);
static int   ConfigurePlFrame	(Tcl_Interp *, PlFrame *, int, char **, int);
static int   Draw		(Tcl_Interp *, PlFrame *, int, char **);
static int   Info		(Tcl_Interp *, PlFrame *, int, char **);
static int   Openlink		(Tcl_Interp *, PlFrame *, int, char **);
static int   Orient		(Tcl_Interp *, PlFrame *, int, char **);
static int   Page		(Tcl_Interp *, PlFrame *, int, char **);
static int   Print		(Tcl_Interp *, PlFrame *, int, char **);
static int   Redraw		(Tcl_Interp *, PlFrame *, int, char **);
static int   Save		(Tcl_Interp *, PlFrame *, int, char **);
static int   View		(Tcl_Interp *, PlFrame *, int, char **);
static int   xScroll		(Tcl_Interp *, PlFrame *, int, char **);
static int   yScroll		(Tcl_Interp *, PlFrame *, int, char **);

/* Utility routines */

static void  gbox		(PLFLT *, PLFLT *, PLFLT *, PLFLT *, char **);
static void  UpdateVScrollbar	(register PlFrame *);
static void  UpdateHScrollbar	(register PlFrame *);

/*
 *---------------------------------------------------------------------------
 *
 * plFrameCmd --
 *
 *	This procedure is invoked to process the "plframe" Tcl
 *	command.  See the user documentation for details on what it
 *	does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *---------------------------------------------------------------------------
 */

int
plFrameCmd(ClientData clientData, Tcl_Interp *interp,
	   int argc, char **argv)
{
    Tk_Window tkwin = (Tk_Window) clientData;
    Tk_Window new;
    register PlFrame *plFramePtr;
    register PLRDev *plr;
    int i, ndev;
    XGCValues gcValues;
    unsigned long mask;

    dbug_enter("plFrameCmd");

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " pathName ?options?\"", (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Create the window.
     */

    new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
    if (new == NULL) {
	return TCL_ERROR;
    }

    plFramePtr = (PlFrame *) ckalloc(sizeof(PlFrame));
    plFramePtr->tkwin = new;
    plFramePtr->display = Tk_Display(new);
    plFramePtr->interp = interp;
    plFramePtr->xorGC = NULL;
    plFramePtr->border = NULL;
    plFramePtr->geometry = NULL;
    plFramePtr->cursor = None;
    plFramePtr->draw_cursor = None;
    plFramePtr->flags = 0;
    plFramePtr->prevWidth = Tk_Width(plFramePtr->tkwin);
    plFramePtr->prevHeight = Tk_Height(plFramePtr->tkwin);
    plFramePtr->continue_draw = 0;
    plFramePtr->ipls = 0;
    plFramePtr->ipls_save = 0;
    plFramePtr->tkwin_initted = 0;
    plFramePtr->bgColor = NULL;
    plFramePtr->plpr_cmd = NULL;
    plFramePtr->bopCmd = NULL;
    plFramePtr->eopCmd = NULL;
    plFramePtr->yScrollCmd = NULL;
    plFramePtr->xScrollCmd = NULL;
    plFramePtr->xl = 0.;
    plFramePtr->yl = 0.;
    plFramePtr->xr = 1.;
    plFramePtr->yr = 1.;
    plFramePtr->SaveFnam = NULL;

    plFramePtr->plr = (PLRDev *) ckalloc(sizeof(PLRDev));
    plr = plFramePtr->plr;
    plr->pdfs = NULL;
    plr->iodev = (PLiodev *) ckalloc(sizeof(PLiodev));
    plr_start(plr);

/* Associate new PLplot stream with this widget */

    plmkstrm(&plFramePtr->ipls);
    plgpls(&plFramePtr->plsc);

/* Set up stuff for rubber-band drawing */

    gcValues.background = 0;
    gcValues.foreground = 1;
    gcValues.function = GXxor;
    mask = GCForeground | GCBackground | GCFunction;
    plFramePtr->xorGC = Tk_GetGC(plFramePtr->tkwin, mask, &gcValues);

    plFramePtr->draw_cursor =
	Tk_GetCursor(plFramePtr->interp, plFramePtr->tkwin, "crosshair");

/* Create list of valid device names and keywords for page dumps */

    plFramePtr->devDesc = (char **) ckalloc(NDEV * sizeof(char **));
    plFramePtr->devName = (char **) ckalloc(NDEV * sizeof(char **));
    for (i = 0; i < NDEV; i++) {
	plFramePtr->devDesc[i] = NULL;
	plFramePtr->devName[i] = NULL;
    }
    plgFileDevs(&plFramePtr->devDesc, &plFramePtr->devName, &ndev);

/* Start up event handlers and other good stuff */

    Tk_SetClass(new, "plframe");
    Tk_CreateEventHandler(plFramePtr->tkwin, ExposureMask|StructureNotifyMask,
			  PlFrameEventProc, (ClientData) plFramePtr);

    Tcl_CreateCommand(interp, Tk_PathName(plFramePtr->tkwin),
	    PlFrameWidgetCmd, (ClientData) plFramePtr, (void (*)()) NULL);

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

    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * PlFrameWidgetCmd --
 *
 *	This procedure is invoked to process the Tcl command that
 *	corresponds to a plframe widget.  See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *---------------------------------------------------------------------------
 */

static int
PlFrameWidgetCmd(ClientData clientData, Tcl_Interp *interp,
		 int argc, char **argv)
{
    register PlFrame *plFramePtr = (PlFrame *) clientData;
    int result = TCL_OK;
    int length;
    char c;

    dbug_enter("PlFrameWidgetCmd");

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " option ?arg arg ...?\"", (char *) NULL);
	return TCL_ERROR;
    }
    Tk_Preserve((ClientData) plFramePtr);
    c = argv[1][0];
    length = strlen(argv[1]);

/* cmd -- issue a command to the PLPlot library */

    if ((c == 'c') && (strncmp(argv[1], "cmd", length) == 0)) {
	result = Cmd(interp, plFramePtr, argc-2, argv+2);
    }

/* configure */

    else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
	if (argc == 2) {
	    result = Tk_ConfigureInfo(interp, plFramePtr->tkwin, configSpecs,
		    (char *) plFramePtr, (char *) NULL, 0);
	}
	else if (argc == 3) {
	    result = Tk_ConfigureInfo(interp, plFramePtr->tkwin, configSpecs,
		    (char *) plFramePtr, argv[2], 0);
	}
	else {
	    result = ConfigurePlFrame(interp, plFramePtr, argc-2, argv+2,
		    TK_CONFIG_ARGV_ONLY);
	}
    }

/* draw -- rubber-band draw used in region selection */

    else if ((c == 'd') && (strncmp(argv[1], "draw", length) == 0)) {
	if (argc == 2) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " draw op ?options?\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	else {
	    result = Draw(interp, plFramePtr, argc-2, argv+2);
	}
    }

/* info -- returns requested info */

    else if ((c == 'i') && (strncmp(argv[1], "info", length) == 0)) {
	result = Info(interp, plFramePtr, argc-2, argv+2);
    }

/* orient -- Set plot orientation */

    else if ((c == 'o') && (strncmp(argv[1], "orient", length) == 0)) {
	result = Orient(interp, plFramePtr, argc-2, argv+2);
    }

/* openlink -- Open the data link */

    else if ((c == 'o') && (strncmp(argv[1], "openlink", length) == 0)) {
	if (argc < 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " option ?arg arg ...?\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	else {
	    result = Openlink(interp, plFramePtr, argc-2, argv+2);
	}
    }

/* page -- change or return output page setup */

    else if ((c == 'p') && (strncmp(argv[1], "page", length) == 0)) {
	result = Page(interp, plFramePtr, argc-2, argv+2);
    }

/* print -- prints plot */

    else if ((c == 'p') && (strncmp(argv[1], "print", length) == 0)) {
	result = Print(interp, plFramePtr, argc-2, argv+2);
    }

/* redraw -- redraw plot */

    else if ((c == 'r') && (strncmp(argv[1], "redraw", length) == 0)) {
	if (argc > 2) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " redraw\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	else {
	    result = Redraw(interp, plFramePtr, argc-2, argv+2);
	}
    }

/* save -- saves plot to the specified plot file type */

    else if ((c == 's') && (strncmp(argv[1], "save", length) == 0)) {
	result = Save(interp, plFramePtr, argc-2, argv+2);
    }

/* view -- change or return window into plot */

    else if ((c == 'v') && (strncmp(argv[1], "view", length) == 0)) {
	result = View(interp, plFramePtr, argc-2, argv+2);
    }

/* xscroll -- horizontally scroll window into plot */

    else if ((c == 'x') && (strncmp(argv[1], "xscroll", length) == 0)) {
	if (argc == 2 || argc > 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " xscroll pixel\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	else {
	    result = xScroll(interp, plFramePtr, argc-2, argv+2);
	}
    }

/* yscroll -- vertically scroll window into plot */

    else if ((c == 'y') && (strncmp(argv[1], "yscroll", length) == 0)) {
	if (argc == 2 || argc > 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " yscroll pixel\"", (char *) NULL);
	    result = TCL_ERROR;
	    goto done;
	}
	else {
	    result = yScroll(interp, plFramePtr, argc-2, argv+2);
	}
    }

/* unrecognized widget command */

    else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
	 "\":  must be cmd, configure, draw, info, ",
	 "openlink, orient, page, print, redraw, save, view, ",
	 "xscroll, or yscroll", (char *) NULL);

	result = TCL_ERROR;
    }

 done:
    Tk_Release((ClientData) plFramePtr);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * DestroyPlFrame --
 *
 *	This procedure is invoked by Tk_EventuallyFree or Tk_Release to
 *	clean up the internal structure of a plframe at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the plframe is freed up.
 *
 *---------------------------------------------------------------------------
 */

static void
DestroyPlFrame(ClientData clientData)
{
    register PlFrame *plFramePtr = (PlFrame *) clientData;
    register PLRDev *plr = plFramePtr->plr;

    dbug_enter("DestroyPlFrame");

    if (plFramePtr->border != NULL) {
	Tk_Free3DBorder(plFramePtr->border);
    }
    if (plFramePtr->bgColor != NULL) {
	Tk_FreeColor(plFramePtr->bgColor);
    }
    if (plFramePtr->plpr_cmd != NULL) {
	ckfree((char *) plFramePtr->plpr_cmd);
    }
    if (plFramePtr->geometry != NULL) {
	ckfree((char *) plFramePtr->geometry);
    }
    if (plFramePtr->cursor != None) {
	Tk_FreeCursor(plFramePtr->display, plFramePtr->cursor);
    }
    if (plFramePtr->draw_cursor != None) {
	Tk_FreeCursor(plFramePtr->display, plFramePtr->draw_cursor);
    }
    if (plFramePtr->xorGC != NULL) {
	Tk_FreeGC(plFramePtr->display, plFramePtr->xorGC);
    }
    if (plFramePtr->yScrollCmd != NULL) {
	ckfree((char *) plFramePtr->yScrollCmd);
    }
    if (plFramePtr->xScrollCmd != NULL) {
	ckfree((char *) plFramePtr->xScrollCmd);
    }
    if (plFramePtr->SaveFnam != NULL) {
	ckfree((char *) plFramePtr->devDesc);
    }
    if (plFramePtr->devDesc != NULL) {
	ckfree((char *) plFramePtr->devDesc);
    }
    if (plFramePtr->devName != NULL) {
	ckfree((char *) plFramePtr->devName);
    }

/* Clean up data connection */

    pdf_close(plr->pdfs);
    ckfree((char *) plFramePtr->plr->iodev);

/* Delete main data structures */

    ckfree((char *) plFramePtr->plr);
    ckfree((char *) plFramePtr);

/* Tell PLPlot to clean up */

    plsstrm( plFramePtr->ipls );
    plend1();
}

/*
 *---------------------------------------------------------------------------
 *
 * PlFrameEventProc --
 *
 *	Invoked by the Tk dispatcher on exposes and structure
 *	changes to a plframe. 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get cleaned up.
 *	When it gets exposed, it is redisplayed.
 *
 *---------------------------------------------------------------------------
 */

static void
PlFrameEventProc(ClientData clientData, register XEvent *eventPtr)
{
    register PlFrame *plFramePtr = (PlFrame *) clientData;

    dbug_enter("PlFrameEventProc");

    switch (eventPtr->type) {
    case Expose:
	if (eventPtr->xexpose.count == 0) {
	    if ((plFramePtr->tkwin != NULL) &&
		!(plFramePtr->flags & REFRESH_PENDING)) {

		Tk_DoWhenIdle(DisplayPlFrame, (ClientData) plFramePtr);
		plFramePtr->flags |= REFRESH_PENDING;
	    }
	}
	break;

    case ConfigureNotify:
	plFramePtr->flags |= RESIZE_PENDING;
	if ((plFramePtr->tkwin != NULL) &&
	    !(plFramePtr->flags & REFRESH_PENDING)) {

	    Tk_DoWhenIdle(DisplayPlFrame, (ClientData) plFramePtr);
	    plFramePtr->flags |= REFRESH_PENDING;
	    plFramePtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
	}
	break;

    case DestroyNotify:
	Tcl_DeleteCommand(plFramePtr->interp, Tk_PathName(plFramePtr->tkwin));
	plFramePtr->tkwin = NULL;
	if (plFramePtr->flags & REFRESH_PENDING) {
	    Tk_CancelIdleCall(DisplayPlFrame, (ClientData) plFramePtr);
	}
	Tk_EventuallyFree((ClientData) plFramePtr, DestroyPlFrame);
	break;

    case MapNotify:
	if (plFramePtr->flags & REFRESH_PENDING) {
	    Tk_CancelIdleCall(DisplayPlFrame, (ClientData) plFramePtr);
	}

/* For some strange reason, "." must be mapped or PlFrameInit will die */
/* (Note: mapped & withdrawn or mapped in the withdrawn state is OK */
/* Issuing an update fixes this.  I'd love to know why this occurs. */

	if ( ! plFramePtr->tkwin_initted) {
	    Tcl_VarEval(plFramePtr->interp, "update", (char *) NULL);
	}
	Tk_DoWhenIdle(PlFrameInit, (ClientData) plFramePtr);
	break;
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * PlFrameInit --
 *
 *	Invoked to handle miscellaneous initialization after window gets
 *	mapped.  
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	PLPlot internal parameters and device driver are initialized.
 *
 *---------------------------------------------------------------------------
 */

static void
PlFrameInit(ClientData clientData)
{
    register PlFrame *plFramePtr = (PlFrame *) clientData;
    register Tk_Window tkwin = plFramePtr->tkwin;

/* Set up window parameters and arrange for window to be refreshed */

    plFramePtr->flags &= REFRESH_PENDING;
    plFramePtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;

/* First-time initialization */

    if ( ! plFramePtr->tkwin_initted) {
	plsstrm(plFramePtr->ipls);
	plsdev("xwin");
	plsxwin(Tk_WindowId(tkwin));
	plspause(0);
	plinit();
	if (plplot_ccmap)
	    Install_cmap(plFramePtr);

	pladv(0);

	plFramePtr->tkwin_initted = 1;
	plFramePtr->prevWidth = Tk_Width(tkwin);
	plFramePtr->prevHeight = Tk_Height(tkwin);
    }
    else {
	plFramePtr->flags |= RESIZE_PENDING;
    }

/* Draw plframe */

    DisplayPlFrame(clientData);
}

/*
 *---------------------------------------------------------------------------
 *
 * Install_cmap --
 *
 *	Installs X driver color map as necessary when custom color maps
 *	are used. 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Parent color maps may get changed.
 *
 *---------------------------------------------------------------------------
 */

static void
Install_cmap(PlFrame *plFramePtr)
{
    XwDev *dev;

#define INSTALL_COLORMAP_IN_TK
#ifdef  INSTALL_COLORMAP_IN_TK
    dev = (XwDev *) plFramePtr->plsc->dev;
    Tk_SetWindowColormap(Tk_MainWindow(plFramePtr->interp), dev->xwd->map);

/*
 * If the colormap is local to this widget, the WM must be informed that
 * it should be installed when the widget gets the focus.  The top level
 * window must be added to the end of its own list, because otherwise the
 * window manager adds it to the front (as required by the ICCCM).  Thanks
 * to Paul Mackerras for providing this info in his TK photo widget.
 */

#else
    int count = 0;
    Window top, colormap_windows[5];

    top = Tk_WindowId(Tk_MainWindow(plFramePtr->interp));

    colormap_windows[count++] = Tk_WindowId(plFramePtr->tkwin);
    colormap_windows[count++] = top;

    if ( ! XSetWMColormapWindows(plFramePtr->display,
				 top, colormap_windows, count))
      fprintf(stderr, "Unable to set color map property!\n");
#endif
}

/*
 *---------------------------------------------------------------------------
 *
 * DisplayPlFrame --
 *
 *	This procedure is invoked to display a plframe widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are output to X to display the plframe in its
 *	current mode.
 *
 *---------------------------------------------------------------------------
 */

static void
DisplayPlFrame(ClientData clientData)
{
    register PlFrame *plFramePtr = (PlFrame *) clientData;
    register Tk_Window tkwin = plFramePtr->tkwin;
    PLWindow window;

    dbug_enter("DisplayPlFrame");

/* Update scrollbars if needed */

    if (plFramePtr->flags & UPDATE_V_SCROLLBAR) {
	UpdateVScrollbar(plFramePtr);
    }
    if (plFramePtr->flags & UPDATE_H_SCROLLBAR) {
	UpdateHScrollbar(plFramePtr);
    }
    plFramePtr->flags &= ~(UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR);

/* If not mapped yet, return and cancel pending refresh */

    if ((plFramePtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
	plFramePtr->flags &= ~REFRESH_PENDING;
	return;
    }

/* Clear window if needed */

    if (plFramePtr->flags & CLEAR_NEEDED) {
	XClearWindow(plFramePtr->display, Tk_WindowId(tkwin));
	plFramePtr->flags &= ~CLEAR_NEEDED;
    }

/* Redraw border if necessary */

    if ((plFramePtr->border != NULL)
	    && (plFramePtr->relief != TK_RELIEF_FLAT)) {
	Tk_Draw3DRectangle(plFramePtr->display, Tk_WindowId(tkwin),
		plFramePtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
		plFramePtr->borderWidth, plFramePtr->relief);
    }

/* All refresh events */

    if (plFramePtr->flags & REFRESH_PENDING) {
	plFramePtr->flags &= ~REFRESH_PENDING;

/* Reschedule resizes to avoid occasional ordering conflicts with */
/* the packer's resize of the window (this call must come last). */

	if (plFramePtr->flags & RESIZE_PENDING) {
	    plFramePtr->flags |= REFRESH_PENDING;
	    plFramePtr->flags &= ~RESIZE_PENDING;
	    Tk_DoWhenIdle(DisplayPlFrame, clientData);
	    return;
	}

/* Redraw -- replay contents of plot buffer */

	if (plFramePtr->flags & REDRAW_PENDING) {
	    plFramePtr->flags &= ~REDRAW_PENDING;
	    plsstrm(plFramePtr->ipls);
	    pl_cmd(PLESC_REDRAW, (void *) NULL);
	    return;
	}

/* Resize -- if window bounds have changed */

	if ((plFramePtr->prevWidth != Tk_Width(tkwin)) ||
	    (plFramePtr->prevHeight != Tk_Height(tkwin))) {

	    window.width = Tk_Width(tkwin);
	    window.height = Tk_Height(tkwin);

	    plsstrm(plFramePtr->ipls);
	    pl_cmd(PLESC_RESIZE, (void *) &window);
	    plFramePtr->prevWidth = Tk_Width(tkwin);
	    plFramePtr->prevHeight = Tk_Height(tkwin);
	}

/* Expose -- if window bounds are unchanged */

	else {
	    plsstrm(plFramePtr->ipls);
	    pl_cmd(PLESC_EXPOSE, NULL);
	}
    }
}

/*----------------------------------------------------------------------*\
 * Routines to process widget commands.
\*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*\
 * Cmd
 *
 * Processes "cmd" widget command.
 * Handles commands that go more or less directly to the PLPlot library.
 * Most of these come out of the PLplot Tcl API support file.
\*----------------------------------------------------------------------*/

static int
Cmd(Tcl_Interp *interp, register PlFrame *plFramePtr,
    int argc, char **argv)
{
    PLStream *plsc = plFramePtr->plsc;
    int length;
    char c3;
    int result = TCL_OK;
    char cmdlist[] = "plgcmap0 plgcmap1 plscmap0 plscmap1 plscol0 plscol1";

/* no option -- return list of available PLPlot commands */

    if (argc == 0) 
	return plTclCmd(cmdlist, interp, argc, argv);

    c3 = argv[0][2];
    length = strlen(argv[0]);
    plsstrm(plFramePtr->ipls);

/* plgcmap0 -- get color map 0 */
/* first arg is number of colors, the rest are hex number specifications */

    if ((c3 == 'g') && (strncmp(argv[0], "plgcmap0", length) == 0)) {
	int i;
	unsigned long plcolor;
	char str[10];

	sprintf(str, "%d", (int) plsc->ncol0);
	Tcl_AppendElement(interp, str);
	for (i = 0; i < plsc->ncol0; i++) {
	    plcolor = ((plsc->cmap0[i].r << 16) | 
		       (plsc->cmap0[i].g << 8) |
		       (plsc->cmap0[i].b));

	    sprintf(str, "#%06lx", (plcolor & 0xFFFFFF));
	    Tcl_AppendElement(interp, str);
	}
	result = TCL_OK;
    }

/* plgcmap1 -- get color map 1 */
/* first arg is number of control points */
/* the rest are hex number specifications followed by positions (0-100) */

    else if ((c3 == 'g') && (strncmp(argv[0], "plgcmap1", length) == 0)) {
	int i;
	unsigned long plcolor;
	char str[10];
	PLFLT h, l, s, r, g, b;
	int r1, g1, b1;

	sprintf(str, "%d", (int) plsc->ncp1);
	Tcl_AppendElement(interp, str);
	for (i = 0; i < plsc->ncp1; i++) {
	    h = plsc->cmap1cp[i].h;
	    l = plsc->cmap1cp[i].l;
	    s = plsc->cmap1cp[i].s;

	    plHLS_RGB(h, l, s, &r, &g, &b);

	    r1 = MAX(0, MIN(255, (int) (256. * r)));
	    g1 = MAX(0, MIN(255, (int) (256. * g)));
	    b1 = MAX(0, MIN(255, (int) (256. * b)));

	    plcolor = ((r1 << 16) | (g1 << 8) | (b1));

	    sprintf(str, "#%06lx", (plcolor & 0xFFFFFF));
	    Tcl_AppendElement(interp, str);

	    sprintf(str, "%02d", (int) (100*plsc->cmap1cp[i].p));
	    Tcl_AppendElement(interp, str);
	}
	result = TCL_OK;
    }

/* plscmap0 -- set color map 0 */
/* first arg is number of colors, the rest are hex number specifications */

    else if ((c3 == 's') && (strncmp(argv[0], "plscmap0", length) == 0)) {
	int i, j = 1, status, ncol0 = atoi(argv[j]);
	char *color;
	XColor xcolor;

	if (ncol0 > 16 || ncol0 < 1) {
	    Tcl_AppendResult(interp, "illegal number of colors in cmap0: ",
			     argv[1], (char *) NULL);
	    return TCL_ERROR;
	}

	plsc->ncol0 = ncol0;
	color = strtok(argv[2], " ");
	for (i = 0; i < plsc->ncol0; i++) {
	    if ( color == NULL )
		break;

	    status = XParseColor(plFramePtr->display,
				 Tk_Colormap(plFramePtr->tkwin),
				 color, &xcolor);

	    if ( ! status) {
		fprintf(stderr, "Couldn't parse color %s\n", color);
		break;
	    }

	    plsc->cmap0[i].r = (unsigned) (xcolor.red   & 0xFF00) >> 8;
	    plsc->cmap0[i].g = (unsigned) (xcolor.green & 0xFF00) >> 8;
	    plsc->cmap0[i].b = (unsigned) (xcolor.blue  & 0xFF00) >> 8;

	    color = strtok(NULL, " ");
	}

	plP_state(PLSTATE_CMAP0);
    }

/* plscmap1 -- set color map 1 */
/* first arg is number of colors, the rest are hex number specifications */

    else if ((c3 == 's') && (strncmp(argv[0], "plscmap1", length) == 0)) {
	int i, j = 1, status, ncp1 = atoi(argv[j]);
	char *color, *pos;
	XColor xcolor;
	PLFLT r[32], g[32], b[32], l[32];

	if (ncp1 > 32 || ncp1 < 1) {
	    Tcl_AppendResult(interp,
			     "illegal number of control points in cmap1: ",
			     argv[1], (char *) NULL);
	    return TCL_ERROR;
	}

	color = strtok(argv[2], " ");
	pos = strtok(NULL, " ");
	for (i = 0; i < ncp1; i++) {
	    if ( color == NULL )
		break;

	    status = XParseColor(plFramePtr->display,
				 Tk_Colormap(plFramePtr->tkwin),
				 color, &xcolor);

	    if ( ! status) {
		Tcl_AppendResult(interp, "Couldn't parse color ", color,
				 (char *) NULL);
		return TCL_ERROR;
	    }

	    r[i] = ((PLFLT) ((unsigned) (xcolor.red   & 0xFF00) >> 8)) / 255.0;
	    g[i] = ((PLFLT) ((unsigned) (xcolor.green & 0xFF00) >> 8)) / 255.0;
	    b[i] = ((PLFLT) ((unsigned) (xcolor.blue  & 0xFF00) >> 8)) / 255.0;
	    l[i] = atof(pos) / 100.0;

	    color = strtok(NULL, " ");
	    pos = strtok(NULL, " ");
	}

	plscmap1l(1, ncp1, l, r, g, b);
    }

/* plscol0 -- set single color in cmap0 */
/* first arg is the color number, the next is the color in hex */

    else if ((c3 == 's') && (strncmp(argv[0], "plscol0", length) == 0)) {
	int i = atoi(argv[1]), status;
	char *color = argv[2];
	XColor xcolor;
	PLINT r, g, b;

	if (i > plsc->ncol0 || i < 0) {
	    Tcl_AppendResult(interp, "illegal color number in cmap0: ",
			     argv[1], (char *) NULL);
	    return TCL_ERROR;
	}

	if ( color == NULL ) {
	    Tcl_AppendResult(interp, "color value not specified",
			     (char *) NULL);
	    return TCL_ERROR;
	}

	status = XParseColor(plFramePtr->display,
			     Tk_Colormap(plFramePtr->tkwin),
			     color, &xcolor);

	if ( ! status) {
	    Tcl_AppendResult(interp, "Couldn't parse color ", color,
			     (char *) NULL);
	    return TCL_ERROR;
	}

	r = (unsigned) (xcolor.red   & 0xFF00) >> 8;
	g = (unsigned) (xcolor.green & 0xFF00) >> 8;
	b = (unsigned) (xcolor.blue  & 0xFF00) >> 8;

	if ( (plsc->cmap0[i].r != r) ||
	     (plsc->cmap0[i].g != g) ||
	     (plsc->cmap0[i].b != b) ) {

	    plsc->cmap0[i].r = r;
	    plsc->cmap0[i].g = g;
	    plsc->cmap0[i].b = b;

	    plP_state(PLSTATE_CMAP0);
	}
    }

/* plscol1 -- set color of control point in cmap1 */
/* first arg is the color number, the next two are the color in hex and pos */

    else if ((c3 == 's') && (strncmp(argv[0], "plscol1", length) == 0)) {
	int i = atoi(argv[1]), status;
	char *color, *pos;
	XColor xcolor;
	PLFLT h, l, s, r, g, b, p;

	if (i > plsc->ncp1 || i < 0) {
	    Tcl_AppendResult(interp, "illegal control point number in cmap1: ",
			     argv[1], (char *) NULL);
	    return TCL_ERROR;
	}

	if ( (color = argv[2]) == NULL ) {
	    Tcl_AppendResult(interp, "color value not specified",
			     (char *) NULL);
	    return TCL_ERROR;
	}

	if ( (pos = argv[3]) == NULL ) {
	    Tcl_AppendResult(interp, "control point position not specified",
			     (char *) NULL);
	    return TCL_ERROR;
	}

	status = XParseColor(plFramePtr->display,
			     Tk_Colormap(plFramePtr->tkwin),
			     color, &xcolor);

	if ( ! status) {
	    Tcl_AppendResult(interp, "Couldn't parse color ", color,
			     (char *) NULL);
	    return TCL_ERROR;
	}

	r = ((unsigned) (xcolor.red   & 0xFF00) >> 8) / 255.0;
	g = ((unsigned) (xcolor.green & 0xFF00) >> 8) / 255.0;
	b = ((unsigned) (xcolor.blue  & 0xFF00) >> 8) / 255.0;

	plRGB_HLS(r, g, b, &h, &l, &s);

	p = atof(pos) / 100.0;

	if ( (plsc->cmap1cp[i].h != h) ||
	     (plsc->cmap1cp[i].l != l) ||
	     (plsc->cmap1cp[i].s != s) ||
	     (plsc->cmap1cp[i].p != p) ) {
	     
	    plsc->cmap1cp[i].h = h;
	    plsc->cmap1cp[i].l = l;
	    plsc->cmap1cp[i].s = s;
	    plsc->cmap1cp[i].p = p;

	    plcmap1_calc();
	    plP_state(PLSTATE_CMAP0);
	}
    }

/* unrecognized, give it to plTclCmd to take care of */

    else 
	result = plTclCmd(cmdlist, interp, argc, argv);

    plflush();
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * ConfigurePlFrame --
 *
 *	This procedure is called to process an argv/argc list, plus the Tk
 *	option database, in order to configure (or reconfigure) a 
 *	plframe 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 plFramePtr; old resources get freed, if there were
 *	any.
 *
 *---------------------------------------------------------------------------
 */

static int
ConfigurePlFrame(Tcl_Interp *interp, register PlFrame *plFramePtr,
		 int argc, char **argv, int flags)
{
    PLColor plbg;

#ifdef DEBUG
    int i;
    fprintf(stderr, "Arguments to configure are:");
    for (i = 0; i < argc; i++) {
	fprintf(stderr, " %s", argv[i]);
    }
    fprintf(stderr, "\n");
#endif

    dbug_enter("ConfigurePlFrame");

    if (Tk_ConfigureWidget(interp, plFramePtr->tkwin, configSpecs,
	    argc, argv, (char *) plFramePtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }

/* Set background color.  Need to store in the PLPlot stream structure */
/* since redraws are handled from the PLPlot X driver. */

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

    plsstrm(plFramePtr->ipls);
    PLColor_from_XColor(&plbg, plFramePtr->bgColor);
    plscolbg(plbg.r, plbg.g, plbg.b);

    Tk_SetInternalBorder(plFramePtr->tkwin, plFramePtr->borderWidth);
    if (plFramePtr->geometry != NULL) {
	int height, width;

	if (sscanf(plFramePtr->geometry, "%dx%d", &width, &height) != 2) {
	    Tcl_AppendResult(interp, "bad geometry \"", plFramePtr->geometry,
		    "\": expected widthxheight", (char *) NULL);
	    return TCL_ERROR;
	}
	Tk_GeometryRequest(plFramePtr->tkwin, width, height);
    }
    else if ((plFramePtr->width > 0) && (plFramePtr->height > 0)) {
	Tk_GeometryRequest(plFramePtr->tkwin, plFramePtr->width,
		plFramePtr->height);
    }

    if (Tk_IsMapped(plFramePtr->tkwin)
	    && !(plFramePtr->flags & REFRESH_PENDING)) {
	Tk_DoWhenIdle(DisplayPlFrame, (ClientData) plFramePtr);
	plFramePtr->flags |= REFRESH_PENDING|CLEAR_NEEDED;
	plFramePtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
    }
    return TCL_OK;
}

/*----------------------------------------------------------------------*\
 * Draw
 *
 * Processes "draw" widget command.
 * Handles rubber-band drawing.
\*----------------------------------------------------------------------*/

static int
Draw(Tcl_Interp *interp, register PlFrame *plFramePtr,
     int argc, char **argv)
{
    register Tk_Window tkwin = plFramePtr->tkwin;
    int result = TCL_OK;
    char c = argv[0][0];
    int length = strlen(argv[0]);

/* init -- sets up for rubber-band drawing */

    if ((c == 'i') && (strncmp(argv[0], "init", length) == 0)) {
	Tk_DefineCursor(tkwin, plFramePtr->draw_cursor);
    }

/* end -- ends rubber-band drawing */

    else if ((c == 'e') && (strncmp(argv[0], "end", length) == 0)) {

	Tk_DefineCursor(tkwin, plFramePtr->cursor);
	plFramePtr->continue_draw = 0;
    }

/* rect -- draw a rectangle, used to select rectangular areas */
/* first draw erases old outline */

    else if ((c == 'r') && (strncmp(argv[0], "rect", length) == 0)) {
	if (argc < 5) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
			     " draw rect x0 y0 x1 y1\"", (char *) NULL);
	    result = TCL_ERROR;
	}
	else {
	    int x0, y0, x1, y1;
	    int xmin = 0, xmax = Tk_Width(tkwin) - 1;
	    int ymin = 0, ymax = Tk_Height(tkwin) - 1;

	    x0 = atoi(argv[1]);
	    y0 = atoi(argv[2]);
	    x1 = atoi(argv[3]);
	    y1 = atoi(argv[4]);

	    x0 = MAX(xmin, MIN(xmax, x0));
	    y0 = MAX(ymin, MIN(ymax, y0));
	    x1 = MAX(xmin, MIN(xmax, x1));
	    y1 = MAX(ymin, MIN(ymax, y1));

	    if (plFramePtr->continue_draw) {
		XDrawLines(Tk_Display(tkwin), Tk_WindowId(tkwin),
			   plFramePtr->xorGC, plFramePtr->pts, 5,
			   CoordModeOrigin);
		XSync(Tk_Display(tkwin), 0);
	    }

	    plFramePtr->pts[0].x = x0; plFramePtr->pts[0].y = y0;
	    plFramePtr->pts[1].x = x1; plFramePtr->pts[1].y = y0;
	    plFramePtr->pts[2].x = x1; plFramePtr->pts[2].y = y1;
	    plFramePtr->pts[3].x = x0; plFramePtr->pts[3].y = y1;
	    plFramePtr->pts[4].x = x0; plFramePtr->pts[4].y = y0;

	    XDrawLines(Tk_Display(tkwin), Tk_WindowId(tkwin),
		       plFramePtr->xorGC, plFramePtr->pts, 5,
		       CoordModeOrigin);
	    XSync(Tk_Display(tkwin), 0);

	    plFramePtr->continue_draw = 1;
	}
    }

    return result;
}

/*----------------------------------------------------------------------*\
 * Info
 *
 * Processes "info" widget command.
 * Returns requested info.
\*----------------------------------------------------------------------*/

static int
Info(Tcl_Interp *interp, register PlFrame *plFramePtr,
     int argc, char **argv)
{
    int length;
    char c;
    int result = TCL_OK;

/* no option -- return list of available info commands */

    if (argc == 0) {
	Tcl_SetResult(interp, "devices", TCL_STATIC);
	return TCL_OK;
    }

    c = argv[0][0];
    length = strlen(argv[0]);

/* devices -- return list of supported device types */

    if ((c == 'd') && (strncmp(argv[0], "devices", length) == 0)) {
	int i = 0;
	while (plFramePtr->devDesc[i] != NULL) 
	    Tcl_AppendElement(interp, plFramePtr->devDesc[i++]);

	result = TCL_OK;
    }

/* unrecognized */

    else {
	Tcl_AppendResult(interp, "bad option to \"info\": must be ", 
	 "devices", (char *) NULL);

	result = TCL_ERROR;
    }

    return result;
}

/*----------------------------------------------------------------------*\
 * Openlink
 *
 * Processes "openlink" widget command.
 * Opens channel (FIFO or socket) for data transfer between client and
 * server.
\*----------------------------------------------------------------------*/

static int
Openlink(Tcl_Interp *interp, register PlFrame *plFramePtr,
	 int argc, char **argv)
{
    register PLRDev *plr = plFramePtr->plr;
    register PLiodev *iodev = plr->iodev;

    char c = argv[0][0];
    int length = strlen(argv[0]);

    dbug_enter("Openlink");

/* Open fifo */

    if ((c == 'f') && (strncmp(argv[0], "fifo", length) == 0)) {

	if (argc < 1) {
	    Tcl_AppendResult(interp, "bad command -- must be: ",
			     "openlink fifo <pathname>",
			     (char *) NULL);
	    return TCL_ERROR;
	}
	if ((iodev->fd = open (argv[1], O_RDONLY)) == -1) {
	    Tcl_AppendResult(interp, "cannot open fifo ", argv[1],
			     " for read", (char *) NULL);
	    return TCL_ERROR;
	}
	iodev->type = 0;
	iodev->typename = "fifo";
	iodev->file = fdopen(iodev->fd, "rb");
    }

/* Open socket */

    else if ((c == 's') && (strncmp(argv[0], "socket", length) == 0)) {

	if (argc < 1) {
	    Tcl_AppendResult(interp, "bad command -- must be: ",
			     "openlink socket <sock-id>",
			     (char *) NULL);
	    return TCL_ERROR;
	}
	iodev->type = 1;
	iodev->typename = "socket";
	iodev->filehandle = argv[1];
	if (Tcl_GetOpenFile(interp, iodev->filehandle,
			    0, 1, &iodev->file) != TCL_OK) {
	    return TCL_ERROR;
	}
	iodev->fd = fileno(iodev->file);
    }

/* unrecognized */

    else {
	Tcl_AppendResult(interp, "bad option to \"openlink\": must be ", 
	 "fifo or socket", (char *) NULL);

	return TCL_ERROR;
    }

    plr->pdfs = pdf_bopen( NULL, 4200 );
    Tk_CreateFileHandler(iodev->fd, TK_READABLE, (Tk_FileProc *) ReadData,
			 (ClientData) plFramePtr);

    return TCL_OK;
}

/*----------------------------------------------------------------------*\
 * process_data
 *
 * Utility function for processing data and other housekeeping.
\*----------------------------------------------------------------------*/

static int
process_data(Tcl_Interp *interp, register PlFrame *plFramePtr)
{
    register PLRDev *plr = plFramePtr->plr;
    register PLiodev *iodev = plr->iodev;
    int result = TCL_OK;

/* Process data */

    if (plr_process(plr) == -1) {
	Tcl_AppendResult(interp, "unable to read from ", iodev->typename,
			 (char *) NULL);
	result = TCL_ERROR;
    }

/* Signal bop if necessary */

    if (plr->at_bop && plFramePtr->bopCmd != NULL) {
	plr->at_bop = 0;
	if (Tcl_Eval(interp, plFramePtr->bopCmd) != TCL_OK)
	    fprintf(stderr, "Command \"%s\" failed:\n\t %s\n",
		    plFramePtr->bopCmd, interp->result);
    }

/* Signal eop if necessary */

    if (plr->at_eop && plFramePtr->eopCmd != NULL) {
	plr->at_eop = 0;
	if (Tcl_Eval(interp, plFramePtr->eopCmd) != TCL_OK)
	    fprintf(stderr, "Command \"%s\" failed:\n\t %s\n",
		    plFramePtr->eopCmd, interp->result);
    }

    return result;
}

/*----------------------------------------------------------------------*\
 * ReadData
 *
 * Reads & processes data.
 * Intended to be installed as a filehandler command.
\*----------------------------------------------------------------------*/

static int
ReadData(ClientData clientData, int mask)
{
    register PlFrame *plFramePtr = (PlFrame *) clientData;
    register Tcl_Interp *interp = plFramePtr->interp;

    register PLRDev *plr = plFramePtr->plr;
    register PLiodev *iodev = plr->iodev;
    register PDFstrm *pdfs = plr->pdfs;
    int result = TCL_OK;

    if (mask & TK_READABLE) {

/* Read from FIFO or socket */

	plsstrm(plFramePtr->ipls);
	if (pl_PacketReceive(interp, iodev, pdfs)) {
	    Tcl_AppendResult(interp, "Packet receive failed:\n\t %s\n",
			     interp->result, (char *) NULL);
	    return TCL_ERROR;
	}

/* If the packet isn't complete it will be put back and we just return.
 * Otherwise, the buffer pointer is saved and then cleared so that reads
 * from the buffer start at the beginning.
 */
	if (pdfs->bp == 0)
	    return TCL_OK;

	plr->nbytes = pdfs->bp;
	pdfs->bp = 0;
	result = process_data(interp, plFramePtr);
    }

    return result;
}

/*----------------------------------------------------------------------*\
 * Orient
 *
 * Processes "orient" widget command.
 * Handles orientation of plot.
\*----------------------------------------------------------------------*/

static int
Orient(Tcl_Interp *interp, register PlFrame *plFramePtr,
       int argc, char **argv)
{
    int result = TCL_OK;

/* orient -- return orientation of current plot window */

    plsstrm(plFramePtr->ipls);

    if (argc == 0) {
	PLFLT rot;
	char result_str[128];
	plgdiori(&rot);
	sprintf(result_str, "%f", rot);
	Tcl_SetResult(interp, result_str, TCL_VOLATILE);
    }

/* orient <rot> -- Set orientation to <rot> */

    else {
	plsdiori(atof(argv[0]));
	result = Redraw(interp, plFramePtr, argc-1, argv+1);
    }

    return result;
}

/*----------------------------------------------------------------------*\
 * Print
 *
 * Processes "print" widget command.
 * Handles printing of plot, duh.
 *
 * Creates a temporary file, dumps the current plot to it in metafile
 * form, and then execs the "plpr" script to actually print it.  Since we
 * output it in metafile form here, plpr must invoke plrender to drive the
 * output to the appropriate file type.  The script is responsible for the
 * deletion of the plot metafile.
 *
 * This way of printing is reasonably flexible while retaining a measure
 * of security.  Methods involving setting up the command to print from
 * the Tcl side are not recommended at present since the print command
 * could be changed to something dangerous (like an rm *).
\*----------------------------------------------------------------------*/

static int
Print(Tcl_Interp *interp, register PlFrame *plFramePtr,
       int argc, char **argv)
{
    PLINT ipls;
    int result = TCL_OK;
    char *sfnam;
    FILE *sfile;
    pid_t pid;

/* Create stream for save */

    plmkstrm(&ipls);
    if (ipls < 0) {
	Tcl_AppendResult(interp, "Error -- cannot create stream", 
			 (char *) NULL);
	return TCL_ERROR;
    }

/* Open file for writes */

    sfnam = (char *) tmpnam(NULL);

    if ((sfile = fopen(sfnam, "wb+")) == NULL) {
	Tcl_AppendResult(interp, 
			 "Error -- cannot open plot file for writing",
			 (char *) NULL);
	plend1();
	return TCL_ERROR;
    }

/* Initialize stream */

    plsdev("plmeta");
    plsfile(sfile);
    plcpstrm(plFramePtr->ipls, 0);
    pladv(0);

/* Remake current plot, close file, and switch back to original stream */

    plreplot();
    plend1();
    plsstrm(plFramePtr->ipls);

/* So far so good.  Time to exec the print script. */

    if (plFramePtr->plpr_cmd == NULL)
	plFramePtr->plpr_cmd = plFindCommand("plpr");

    if ((plFramePtr->plpr_cmd == NULL) || (pid = fork()) < 0) {
	Tcl_AppendResult(interp, 
			 "Error -- cannot fork print process",
			 (char *) NULL);
	result = TCL_ERROR;
    }
    else if (pid == 0) {
	if (execl(plFramePtr->plpr_cmd, plFramePtr->plpr_cmd, sfnam, 
		  (char *) 0)) {
	    fprintf(stderr, "Unable to exec print command.\n");
	    _exit(1);
	}
    }

    return result;
}

/*----------------------------------------------------------------------*\
 * Page
 *
 * Processes "page" widget command.
 * Handles parameters such as margin, aspect ratio, and justification
 * of final plot.
\*----------------------------------------------------------------------*/

static int
Page(Tcl_Interp *interp, register PlFrame *plFramePtr,
     int argc, char **argv)
{

/* page -- return current device window parameters */

    plsstrm(plFramePtr->ipls);

    if (argc == 0) {
	PLFLT mar, aspect, jx, jy;
	char result_str[128];

	plgdidev(&mar, &aspect, &jx, &jy);
	sprintf(result_str, "%g %g %g %g", mar, aspect, jx, jy);
	Tcl_SetResult(interp, result_str, TCL_VOLATILE);
	return TCL_OK;
    }

/* page <mar> <aspect> <jx> <jy> -- set up page */

    if (argc < 4) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
			 " page mar aspect jx jy\"", (char *) NULL);
	return TCL_ERROR;
    } 

    plsdidev(atof(argv[0]), atof(argv[1]), atof(argv[2]), atof(argv[3]));
    return(Redraw(interp, plFramePtr, argc-1, argv+1));
}

/*----------------------------------------------------------------------*\
 * Redraw
 *
 * Processes "redraw" widget command.
 * Turns loose a DoWhenIdle command to redraw plot by replaying contents
 * of plot buffer.
\*----------------------------------------------------------------------*/

static int
Redraw(Tcl_Interp *interp, register PlFrame *plFramePtr,
       int argc, char **argv)
{
    dbug_enter("Redraw");

    plFramePtr->flags |= REDRAW_PENDING;
    if ((plFramePtr->tkwin != NULL) &&
	!(plFramePtr->flags & REFRESH_PENDING)) {

	Tk_DoWhenIdle(DisplayPlFrame, (ClientData) plFramePtr);
	plFramePtr->flags |= REFRESH_PENDING;
    }

    return TCL_OK;
}

/*----------------------------------------------------------------------*\
 * Save
 *
 * Processes "save" widget command.
 * Saves plot to a file.
\*----------------------------------------------------------------------*/

static int
Save(Tcl_Interp *interp, register PlFrame *plFramePtr,
     int argc, char **argv)
{
    int length, idev;
    char c;
    FILE *sfile;

/* save -- save to already open file */

    if (argc == 0) {
	if ( ! plFramePtr->ipls_save) {
	    Tcl_AppendResult(interp, "Error -- no current save file", 
			     (char *) NULL);
	    return TCL_ERROR;
	}
	plsstrm(plFramePtr->ipls_save);
	plcpstrm(plFramePtr->ipls, 0);
	pladv(0);
	plreplot();
	plflush();
	plsstrm(plFramePtr->ipls);
	return TCL_OK;
    }

    c = argv[0][0];
    length = strlen(argv[0]);

/* save to specified device & file */

    if ((c == 'a') && (strncmp(argv[0], "as", length) == 0)) {
	if (argc < 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
			     " save as device file\"", (char *) NULL);
	    return TCL_ERROR;
	} 
	idev = atoi(argv[1]);

/* If save previously in effect, delete old stream */

	if (plFramePtr->ipls_save) {
	    plsstrm(plFramePtr->ipls_save);
	    plend1();
	}

/* Create stream for saves to selected device & file */

	plmkstrm(&plFramePtr->ipls_save);
	if (plFramePtr->ipls_save < 0) {
	    Tcl_AppendResult(interp, "Error -- cannot create stream", 
			     (char *) NULL);
	    plFramePtr->ipls_save = 0;
	    return TCL_ERROR;
	}

/* Open file for writes */

	if ((sfile = fopen(argv[2], "wb+")) == NULL) {
	    Tcl_AppendResult(interp, "Error -- cannot open file ", argv[2],
			     " for writing", (char *) NULL);
	    plFramePtr->ipls_save = 0;
	    plend1();
	    return TCL_ERROR;
	}

/* Initialize stream */

	plsdev(plFramePtr->devName[idev]);
	plsfile(sfile);
	plcpstrm(plFramePtr->ipls, 0);
	pladv(0);

/* Remake current plot and then switch back to original stream */

	plreplot();
	plflush();
	plsstrm(plFramePtr->ipls);
    }

/* close save file */

    else if ((c == 'c') && (strncmp(argv[0], "close", length) == 0)) {

	if ( ! plFramePtr->ipls_save) {
	    Tcl_AppendResult(interp, "Error -- no current save file", 
			     (char *) NULL);
	    return TCL_ERROR;
	}
	else {
	    plsstrm(plFramePtr->ipls_save);
	    plend1();
	    plFramePtr->ipls_save = 0;
	}
    }

/* unrecognized */

    else {
	Tcl_AppendResult(interp, "bad option to \"save\": must be ", 
	 "as or close", (char *) NULL);

	return TCL_ERROR;
    }

    return TCL_OK;
}

/*----------------------------------------------------------------------*\
 * View
 *
 * Processes "view" widget command.
 * Handles translation & scaling of view into plot.
\*----------------------------------------------------------------------*/

static int
View(Tcl_Interp *interp, register PlFrame *plFramePtr,
     int argc, char **argv)
{
    int length;
    char c;
    PLFLT xl, xr, yl, yr;

/* view -- return current relative plot window coordinates */

    plsstrm(plFramePtr->ipls);

    if (argc == 0) {
	char result_str[128];
	plgdiplt(&xl, &yl, &xr, &yr);
	sprintf(result_str, "%g %g %g %g", xl, yl, xr, yr);
	Tcl_SetResult(interp, result_str, TCL_VOLATILE);
	return TCL_OK;
    }

    c = argv[0][0];
    length = strlen(argv[0]);

/* view bounds -- return relative device coordinates of bounds on current */
/* plot window */

    if ((c == 'b') && (strncmp(argv[0], "bounds", length) == 0)) {
	char result_str[128];
	xl = 0.; yl = 0.;
	xr = 1.; yr = 1.;
	pldip2dc(&xl, &yl, &xr, &yr);
	sprintf(result_str, "%g %g %g %g", xl, yl, xr, yr);
	Tcl_SetResult(interp, result_str, TCL_VOLATILE);
	return TCL_OK;
    }

/* view reset -- Resets plot */

    if ((c == 'r') && (strncmp(argv[0], "reset", length) == 0)) {
	xl = 0.; yl = 0.;
	xr = 1.; yr = 1.;
	plsdiplt(xl, yl, xr, yr);
    }

/* view select -- set window into plot space */
/* Specifies in terms of plot window coordinates, not device coordinates */

    else if ((c == 's') && (strncmp(argv[0], "select", length) == 0)) {
	if (argc < 5) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
			     " view select xmin ymin xmax ymax\"",
			     (char *) NULL);
	    return TCL_ERROR;
	}
	else {
	    gbox(&xl, &yl, &xr, &yr, argv+1);
	    plsdiplt(xl, yl, xr, yr);
	}
    }

/* view zoom -- set window into plot space incrementally (zoom) */
/* Here we need to take the page (device) offsets into account */

    else if ((c == 'z') && (strncmp(argv[0], "zoom", length) == 0)) {
	if (argc < 5) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
			     " view zoom xmin ymin xmax ymax\"",
			     (char *) NULL);
	    return TCL_ERROR;
	}
	else {
	    gbox(&xl, &yl, &xr, &yr, argv+1);
	    pldid2pc(&xl, &yl, &xr, &yr);
	    plsdiplz(xl, yl, xr, yr);
	}
    }

/* unrecognized */

    else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
	 "\":  options to \"view\" are: bounds, reset, select, or zoom",
	 (char *) NULL);

	return TCL_ERROR;
    }

/* Update plot window bounds and arrange for plot to be updated */

    plgdiplt(&xl, &yl, &xr, &yr);
    plFramePtr->xl = xl;
    plFramePtr->yl = yl;
    plFramePtr->xr = xr;
    plFramePtr->yr = yr;
    plFramePtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;

    return(Redraw(interp, plFramePtr, argc, argv));
}

/*----------------------------------------------------------------------*\
 * xScroll
 *
 * Processes "xscroll" widget command.
 * Handles horizontal scroll-bar invoked translation of view into plot.
\*----------------------------------------------------------------------*/

static int
xScroll(Tcl_Interp *interp, register PlFrame *plFramePtr,
	int argc, char **argv)
{
    int x0, width = Tk_Width(plFramePtr->tkwin);
    PLFLT xl, xr, yl, yr, xlen;

    plsstrm(plFramePtr->ipls);

    xlen = plFramePtr->xr - plFramePtr->xl;
    x0 = atoi(argv[0]);
    xl = x0 / (double) width;
    xl = MAX( 0., MIN((1. - xlen), xl));
    xr = xl + xlen;

    yl = plFramePtr->yl;
    yr = plFramePtr->yr;

    plFramePtr->xl = xl;
    plFramePtr->xr = xr;

    plsdiplt(xl, yl, xr, yr);

    plFramePtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
    return(Redraw(interp, plFramePtr, argc, argv));
}

/*----------------------------------------------------------------------*\
 * yScroll
 *
 * Processes "yscroll" widget command.
 * Handles vertical scroll-bar invoked translation of view into plot.
\*----------------------------------------------------------------------*/

static int
yScroll(Tcl_Interp *interp, register PlFrame *plFramePtr,
	int argc, char **argv)
{
    int y0, height = Tk_Height(plFramePtr->tkwin);
    PLFLT xl, xr, yl, yr, ylen;

    plsstrm(plFramePtr->ipls);

    ylen = plFramePtr->yr - plFramePtr->yl;
    y0 = atoi(argv[0]);
    yr = 1. - y0 / (double) height;
    yr = MAX( 0.+ylen, MIN(1., yr));
    yl = yr - ylen;

    xl = plFramePtr->xl;
    xr = plFramePtr->xr;

    plFramePtr->yl = yl;
    plFramePtr->yr = yr;

    plsdiplt(xl, yl, xr, yr);

    plFramePtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
    return(Redraw(interp, plFramePtr, argc, argv));
}

/*----------------------------------------------------------------------*\
 * Utility routines
\*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*\
 * UpdateVScrollbar
 *
 * Updates vertical scrollbar if needed.
\*----------------------------------------------------------------------*/

static void
UpdateVScrollbar(register PlFrame *plFramePtr)
{
    int height = Tk_Height(plFramePtr->tkwin);
    char string[60];
    int totalUnits, windowUnits, firstUnit, lastUnit, result;

    if (plFramePtr->yScrollCmd == NULL)
	return;

    totalUnits  = height;
    firstUnit   = 0.5 + (float) height * (1. - plFramePtr->yr);
    lastUnit    = 0.5 + (float) height * (1. - plFramePtr->yl);
    windowUnits = lastUnit - firstUnit;
    sprintf(string, " %d %d %d %d",
	    totalUnits, windowUnits, firstUnit, lastUnit);

    result = Tcl_VarEval(plFramePtr->interp, plFramePtr->yScrollCmd, string,
			 (char *) NULL);

    if (result != TCL_OK) {
	Tk_BackgroundError(plFramePtr->interp);
    }
}

/*----------------------------------------------------------------------*\
 * UpdateHScrollbar
 *
 * Updates horizontal scrollbar if needed.
\*----------------------------------------------------------------------*/

static void
UpdateHScrollbar(register PlFrame *plFramePtr)
{
    int width = Tk_Width(plFramePtr->tkwin);
    char string[60];
    int totalUnits, windowUnits, firstUnit, lastUnit, result;

    if (plFramePtr->xScrollCmd == NULL)
	return;

    totalUnits  = width;
    firstUnit   = 0.5 + (float) width * plFramePtr->xl;
    lastUnit    = 0.5 + (float) width * plFramePtr->xr;
    windowUnits = lastUnit - firstUnit;
    sprintf(string, " %d %d %d %d",
	    totalUnits, windowUnits, firstUnit, lastUnit);

    result = Tcl_VarEval(plFramePtr->interp, plFramePtr->xScrollCmd, string,
			 (char *) NULL);

    if (result != TCL_OK) {
	Tk_BackgroundError(plFramePtr->interp);
    }
}

/*----------------------------------------------------------------------*\
 * gbox
 *
 * Returns selection box coordinates.  It's best if the TCL script does
 * bounds checking on the input but I do it here as well just to be safe.
\*----------------------------------------------------------------------*/

static void
gbox(PLFLT *xl, PLFLT *yl, PLFLT *xr, PLFLT *yr, char **argv)
{
    float x0, y0, x1, y1;

    x0 = atof(argv[0]);
    y0 = atof(argv[1]);
    x1 = atof(argv[2]);
    y1 = atof(argv[3]);

    x0 = MAX(0., MIN(1., x0));
    y0 = MAX(0., MIN(1., y0));
    x1 = MAX(0., MIN(1., x1));
    y1 = MAX(0., MIN(1., y1));

/* Only need two vertices, pick the lower left and upper right */

    *xl = MIN(x0, x1);
    *yl = MIN(y0, y1);
    *xr = MAX(x0, x1);
    *yr = MAX(y0, y1);
}
