// PLplot Tk device driver.
//
// Copyright (C) 2004  Maurice LeBrun
// Copyright (C) 2004  Joao Cardoso
//
// This file is part of PLplot.
//
// PLplot is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as published
// by the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// PLplot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with PLplot; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// This device driver is designed to be used by a PlPlotter, and in fact requires
// the existence of an enclosing PlPlotter.
//
// The idea is that this should develop into a completely cross-platform driver
// for use by the cross platform Tk system.
//
//

#include "plDevs.h"

#define DEBUG

#ifdef PLD_tkwin


#define NEED_PLDEBUG
#include "plplotP.h"
#include "pltkwd.h"
#include "drivers.h"
#include "plevent.h"

#define _TCLINT
#ifdef USE_TCL_STUBS
// Unfortunately, tkInt.h ends up loading Malloc.h under Windows
// So we have to deal with this mess
    #undef malloc
    #undef free
    #undef realloc
    #undef calloc
#if defined ( _WIN32 ) || defined ( MAC_TCL )
#include <tkInt.h>
#else
#include <tk.h>
#endif
    #define malloc     ckalloc
    #define free( m )    ckfree( (char *) m )
    #define realloc    ckrealloc
    #define calloc     ckcalloc
#else
#if defined ( _WIN32 ) || defined ( MAC_TCL )
#include <tkInt.h>
#else
#include <tk.h>
#endif
#endif

#ifdef ckalloc
#undef ckalloc
#define ckalloc    malloc
#endif
#ifdef ckfree
#undef ckfree
#define ckfree    free
#endif
#ifdef free
#undef free
#endif

// Device info
PLDLLIMPEXP_DRIVER const char* plD_DEVICE_INFO_tkwin = "tkwin:New tk driver:1:tkwin:45:tkwin\n";


void *  ckcalloc( size_t nmemb, size_t size );

//
// We want to use the 'pure Tk' interface.  On Unix we can use
// some direct calls to X instead of Tk, if we want, although
// that code hasn't been tested for some time.  So this define
// is required on Windows/MacOS and perhaps optional on Unix.
//
#define USE_TK

#ifdef _WIN32
#define XSynchronize( display, bool )    { display->request++; }
#define XSync( display, bool )           { display->request++; }
#define XFlush( display )
#endif

// Dummy definition of PlPlotter containing first few fields
typedef struct PlPlotter
{
    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.
                        //
} PlPlotter;

void CopyColour( XColor* from, XColor* to );
void Tkw_StoreColor( PLStream* pls, TkwDisplay* tkwd, XColor* col );
static int  pltk_AreWeGrayscale( PlPlotter *plf );
void PlplotterAtEop( Tcl_Interp *interp, register PlPlotter *plPlotterPtr );
void PlplotterAtBop( Tcl_Interp *interp, register PlPlotter *plPlotterPtr );

static int synchronize = 0; // change to 1 for synchronized operation
// for debugging only

// Number of instructions to skip between querying the X server for events

#define MAX_INSTR    20

// Pixels/mm

#define PHYSICAL    0 // Enables physical scaling..

// Set constants for dealing with colormap. In brief:
//
// ccmap  When set, turns on custom color map
//
// XWM_COLORS  Number of low "pixel" values to copy.
// CMAP0_COLORS  Color map 0 entries.
// CMAP1_COLORS  Color map 1 entries.
// MAX_COLORS  Maximum colors period.
//
// See Init_CustomCmap() and Init_DefaultCmap() for more info.
// Set ccmap at your own risk -- still under development.
//

// plplot_tkwin_ccmap is statically defined in pltkwd.h.  Note that
// plplotter.c also includes that header and uses that variable.

#define XWM_COLORS      70
#define CMAP0_COLORS    16
#define CMAP1_COLORS    50
#define MAX_COLORS      256

#ifndef USE_TK
// Variables to hold RGB components of given colormap.
// Used in an ugly hack to get past some X11R5 and TK limitations.

static int    sxwm_colors_set;
static XColor sxwm_colors[MAX_COLORS];
#endif

// Keep pointers to all the displays in use

static TkwDisplay *tkwDisplay[PLTKDISPLAYS];

#if !defined ( MAC_TCL ) && !defined ( _WIN32 )
static unsigned char CreatePixmapStatus;
static int CreatePixmapErrorHandler( Display *display, XErrorEvent *error );
#endif

// Function prototypes
// Initialization

static void Init( PLStream *pls );
static void InitColors( PLStream *pls );
static void AllocCustomMap( PLStream *pls );
static void AllocCmap0( PLStream *pls );
static void AllocCmap1( PLStream *pls );
static void CreatePixmap( PLStream *pls );
static void GetVisual( PLStream *pls );
static void AllocBGFG( PLStream *pls );

// Escape function commands

static void ExposeCmd( PLStream *pls, PLDisplay *ptr );
static void RedrawCmd( PLStream *pls );
static void ResizeCmd( PLStream *pls, PLDisplay *ptr );
#ifndef USE_TK
static void GetCursorCmd( PLStream *pls, PLGraphicsIn *ptr );
#endif
static void FillPolygonCmd( PLStream *pls );
#ifdef USING_PLESC_COPY
static void CopyCommand( PLStream *pls );
#endif

// Miscellaneous

static void StoreCmap0( PLStream *pls );
static void StoreCmap1( PLStream *pls );
static void WaitForPage( PLStream *pls );

void plD_dispatch_init_tkwin( PLDispatchTable *pdt );

void plD_init_tkwin( PLStream * );
void plD_line_tkwin( PLStream *, short, short, short, short );
void plD_polyline_tkwin( PLStream *, short *, short *, PLINT );
void plD_eop_tkwin( PLStream * );
void plD_bop_tkwin( PLStream * );
void plD_tidy_tkwin( PLStream * );
void plD_state_tkwin( PLStream *, PLINT );
void plD_esc_tkwin( PLStream *, PLINT, void * );
void plD_wait_tkwin( PLStream * );
void plD_open_tkwin( PLStream *pls );

void plD_dispatch_init_tkwin( PLDispatchTable *pdt )
{
#ifndef ENABLE_DYNDRIVERS
    pdt->pl_MenuStr = "PLplot Tk plotter";
    pdt->pl_DevName = "tkwin";
#endif
    pdt->pl_type     = plDevType_Interactive;
    pdt->pl_seq      = 45;
    pdt->pl_init     = (plD_init_fp) plD_init_tkwin;
    pdt->pl_line     = (plD_line_fp) plD_line_tkwin;
    pdt->pl_polyline = (plD_polyline_fp) plD_polyline_tkwin;
    pdt->pl_eop      = (plD_eop_fp) plD_eop_tkwin;
    pdt->pl_bop      = (plD_bop_fp) plD_bop_tkwin;
    pdt->pl_tidy     = (plD_tidy_fp) plD_tidy_tkwin;
    pdt->pl_state    = (plD_state_fp) plD_state_tkwin;
    pdt->pl_esc      = (plD_esc_fp) plD_esc_tkwin;
    pdt->pl_wait     = (plD_wait_fp) plD_wait_tkwin;
}

//--------------------------------------------------------------------------
// plD_init_tkwin()
//
// Initialize device.
// Tk-dependent stuff done in plD_open_tkwin() and Init().
//--------------------------------------------------------------------------

void
plD_init_tkwin( PLStream *pls )
{
    TkwDev *dev;
    float  pxlx, pxly;
    int    xmin = 0;
    int    xmax = PIXELS_X - 1;
    int    ymin = 0;
    int    ymax = PIXELS_Y - 1;

    dbug_enter( "plD_init_tkw" );

    pls->termin      = 1; // Is an interactive terminal
    pls->dev_flush   = 1; // Handle our own flushes
    pls->dev_fill0   = 1; // Handle solid fills
    pls->plbuf_write = 1; // Activate plot buffer

    // The real meat of the initialization done here

    if ( pls->dev == NULL )
        plD_open_tkwin( pls );

    dev = (TkwDev *) pls->dev;

    Init( pls );

    // Get ready for plotting

    dev->xlen = (short) ( xmax - xmin );
    dev->ylen = (short) ( ymax - ymin );

    dev->xscale_init = (double) dev->init_width / (double) dev->xlen;
    dev->yscale_init = (double) dev->init_height / (double) dev->ylen;

    dev->xscale = dev->xscale_init;
    dev->yscale = dev->yscale_init;

#if PHYSICAL
    pxlx = (PLFLT) ( (double) PIXELS_X / dev->width * DPMM );
    pxly = (PLFLT) ( (double) PIXELS_Y / dev->height * DPMM );
#else
    pxlx = (PLFLT) ( (double) PIXELS_X / LPAGE_X );
    pxly = (PLFLT) ( (double) PIXELS_Y / LPAGE_Y );
#endif

    plP_setpxl( pxlx, pxly );
    plP_setphy( xmin, xmax, ymin, ymax );
}

//--------------------------------------------------------------------------
// plD_open_tkwin()
//
// Performs basic driver initialization, without actually opening or
// modifying a window. May be called by the outside world before plinit
// in case the caller needs early access to the driver internals (not
// very common -- currently only used externally by plplotter).
//--------------------------------------------------------------------------

void
plD_open_tkwin( PLStream *pls )
{
    TkwDev     *dev;
    TkwDisplay *tkwd;
    int        i;

    dbug_enter( "plD_open_tkw" );

    // Allocate and initialize device-specific data

    if ( pls->dev != NULL )
        plwarn( "plD_open_tkw: device pointer is already set" );

    pls->dev = (TkwDev *) calloc( 1, (size_t) sizeof ( TkwDev ) );
    if ( pls->dev == NULL )
        plexit( "plD_init_tkw: Out of memory." );

    dev = (TkwDev *) pls->dev;

    // Variables used in querying the X server for events

    dev->instr     = 0;
    dev->max_instr = MAX_INSTR;

    // See if display matches any already in use, and if so use that

    dev->tkwd = NULL;
    for ( i = 0; i < PLTKDISPLAYS; i++ )
    {
        if ( tkwDisplay[i] == NULL )
        {
            continue;
        }
        else if ( pls->FileName == NULL && tkwDisplay[i]->displayName == NULL )
        {
            dev->tkwd = tkwDisplay[i];
            break;
        }
        else if ( pls->FileName == NULL || tkwDisplay[i]->displayName == NULL )
        {
            continue;
        }
        else if ( strcmp( tkwDisplay[i]->displayName, pls->FileName ) == 0 )
        {
            dev->tkwd = tkwDisplay[i];
            break;
        }
    }

    // If no display matched, create a new one

    if ( dev->tkwd == NULL )
    {
        dev->tkwd = (TkwDisplay *) calloc( 1, (size_t) sizeof ( TkwDisplay ) );
        if ( dev->tkwd == NULL )
            plexit( "Init: Out of memory." );

        for ( i = 0; i < PLTKDISPLAYS; i++ )
        {
            if ( tkwDisplay[i] == NULL )
                break;
        }
        if ( i == PLTKDISPLAYS )
            plexit( "Init: Out of tkwDisplay's." );

        tkwDisplay[i]  = tkwd = (TkwDisplay *) dev->tkwd;
        tkwd->nstreams = 1;

        //
        // If we don't have a tk widget we're being called on, then
        // abort operations now
        //
        if ( pls->plPlotterPtr == NULL )
        {
            plexit( "No tk plframe widget to connect to" );
        }
        // Old version for MacOS Tk8.0
        //
        // char deflt[] = "Macintosh:0";
        // pls->FileName = deflt;
        // tkwd->display = (Display*) TkpOpenDisplay(pls->FileName);
        //

        // Open display
#if defined ( MAC_TCL ) || defined ( _WIN32 )
        if ( !pls->FileName )
        {
            //
            // Need to strdup because Tk has allocated the screen name,
            // but we will actually 'free' it later ourselves, and therefore
            // need to own the memory.
            //
            pls->FileName = plstrdup( TkGetDefaultScreenName( NULL, NULL ) );
        }
        tkwd->display = pls->plPlotterPtr->display;
#else
        tkwd->display = XOpenDisplay( pls->FileName );
#endif
        if ( tkwd->display == NULL )
        {
            plexit( "Can't open display" );
        }
        tkwd->displayName = pls->FileName;
        tkwd->screen      = DefaultScreen( tkwd->display );
        if ( synchronize )
        {
            XSynchronize( tkwd->display, 1 );
        }
        // Get colormap and visual

        tkwd->map = Tk_Colormap( pls->plPlotterPtr->tkwin );
        GetVisual( pls );

        //
        // Figure out if we have a color display or not.
        // Default is color IF the user hasn't specified and IF the output device is
        // not grayscale.
        //

        if ( pls->colorset )
            tkwd->color = pls->color;
        else
        {
            pls->color  = 1;
            tkwd->color = !pltk_AreWeGrayscale( pls->plPlotterPtr );
        }

        // Allocate & set background and foreground colors

        AllocBGFG( pls );
        pltkwin_setBGFG( pls );
    }

    // Display matched, so use existing display data

    else
    {
        tkwd = (TkwDisplay *) dev->tkwd;
        tkwd->nstreams++;
    }
    tkwd->ixwd = i;
}

//--------------------------------------------------------------------------
// plD_line_tkwin()
//
// Draw a line in the current color from (x1,y1) to (x2,y2).
//--------------------------------------------------------------------------

void
plD_line_tkwin( PLStream *pls, short x1a, short y1a, short x2a, short y2a )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    int        x1 = x1a, y1 = y1a, x2 = x2a, y2 = y2a;

    if ( dev->flags & 1 )
        return;

    y1 = dev->ylen - y1;
    y2 = dev->ylen - y2;

    x1 = (int) ( x1 * dev->xscale );
    x2 = (int) ( x2 * dev->xscale );
    y1 = (int) ( y1 * dev->yscale );
    y2 = (int) ( y2 * dev->yscale );

    if ( dev->write_to_window )
        XDrawLine( tkwd->display, dev->window, dev->gc, x1, y1, x2, y2 );

    if ( dev->write_to_pixmap )
        XDrawLine( tkwd->display, dev->pixmap, dev->gc, x1, y1, x2, y2 );
}

//--------------------------------------------------------------------------
// plD_polyline_tkwin()
//
// Draw a polyline in the current color from (x1,y1) to (x2,y2).
//--------------------------------------------------------------------------

void
plD_polyline_tkwin( PLStream *pls, short *xa, short *ya, PLINT npts )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    PLINT      i;
    XPoint     _pts[PL_MAXPOLY];
    XPoint     *pts;

    if ( dev->flags & 1 )
        return;

    if ( npts > PL_MAXPOLY )
    {
        pts = (XPoint *) malloc( sizeof ( XPoint ) * (size_t) npts );
    }
    else
    {
        pts = _pts;
    }

    for ( i = 0; i < npts; i++ )
    {
        pts[i].x = (short) ( dev->xscale * xa[i] );
        pts[i].y = (short) ( dev->yscale * ( dev->ylen - ya[i] ) );
    }

    if ( dev->write_to_window )
        XDrawLines( tkwd->display, dev->window, dev->gc, pts, npts,
            CoordModeOrigin );

    if ( dev->write_to_pixmap )
        XDrawLines( tkwd->display, dev->pixmap, dev->gc, pts, npts,
            CoordModeOrigin );

    if ( npts > PL_MAXPOLY )
    {
        free( pts );
    }
}

//--------------------------------------------------------------------------
// plD_eop_tkwin()
//
// End of page.
//--------------------------------------------------------------------------

void
plD_eop_tkwin( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    dbug_enter( "plD_eop_tkw" );
    if ( dev->flags & 1 )
        return;

    XFlush( tkwd->display );
    if ( pls->db )
        ExposeCmd( pls, NULL );
}

//--------------------------------------------------------------------------
// plD_wait_tkwin()
//
// User must hit return (or third mouse button) to continue.
//--------------------------------------------------------------------------

void
plD_wait_tkwin( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    dbug_enter( "plD_wait_tkw" );
    if ( dev->flags & 1 )
        return;

    WaitForPage( pls );
}

//--------------------------------------------------------------------------
// WaitForPage()
//
// This routine waits for the user to advance the plot, while handling
// all other events.
//--------------------------------------------------------------------------

static void
WaitForPage( PLStream *pls )
{
    PlPlotter *plf = pls->plPlotterPtr;
    TkwDev    *dev = (TkwDev *) pls->dev;

    dbug_enter( "WaitForPage" );

    dev->flags &= 1;
    if ( plf == NULL )
    {
        plwarn( "WaitForPage: Illegal call --- driver can't find enclosing PlPlotter" );
        return;
    }
    PlplotterAtEop( plf->interp, plf );

    while ( !( dev->flags ) && !Tcl_InterpDeleted( plf->interp ) && ( Tk_GetNumMainWindows() > 0 ) )
    {
        Tcl_DoOneEvent( 0 );
    }

    if ( Tcl_InterpDeleted( plf->interp ) || ( Tk_GetNumMainWindows() <= 0 ) )
    {
        dev->flags |= 1;
    }

    dev->flags &= 1;
}

//--------------------------------------------------------------------------
// plD_bop_tkwin()
//
// Set up for the next page.
//--------------------------------------------------------------------------

void
plD_bop_tkwin( PLStream *pls )
{
    PlPlotter  *plf  = pls->plPlotterPtr;
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    XRectangle xrect;
    xrect.x      = 0; xrect.y = 0;
    xrect.width  = (short unsigned) dev->width;
    xrect.height = (short unsigned) dev->height;

    dbug_enter( "plD_bop_tkw" );
    if ( dev->flags & 1 )
        return;

    if ( dev->write_to_window )
    {
#ifdef MAC_TCL
        // MacTk only has these X calls
        XSetForeground( tkwd->display, dev->gc, tkwd->cmap0[0].pixel );
        XFillRectangles( tkwd->display, dev->window, dev->gc, &xrect, 1 );
        XSetForeground( tkwd->display, dev->gc, dev->curcolor.pixel );
#else
        XClearWindow( tkwd->display, dev->window );
#endif
    }
    if ( dev->write_to_pixmap )
    {
        XSetForeground( tkwd->display, dev->gc, tkwd->cmap0[0].pixel );
        XFillRectangles( tkwd->display, dev->pixmap, dev->gc, &xrect, 1 );
        XSetForeground( tkwd->display, dev->gc, dev->curcolor.pixel );
    }
    XSync( tkwd->display, 0 );
    pls->page++;
    PlplotterAtBop( plf->interp, plf );
}

//--------------------------------------------------------------------------
// plD_tidy_tkwin()
//
// Close graphics file
//--------------------------------------------------------------------------

void
plD_tidy_tkwin( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    dbug_enter( "plD_tidy_tkw" );

    tkwd->nstreams--;
    if ( tkwd->nstreams == 0 )
    {
        int ixwd = tkwd->ixwd;
        XFreeGC( tkwd->display, dev->gc );
#if !defined ( MAC_TCL ) && !defined ( _WIN32 )
        XCloseDisplay( tkwd->display );
#endif
        free_mem( tkwDisplay[ixwd] );
    }
    //
    // Vince removed this November 1999.  It seems as if a simple
    // 'plframe .p ; destroy .p' leaves a temporary buf file open
    // if we clear this flag here.  It should be checked and then
    // cleared by whoever called us.  An alternative fix would
    // be to carry out the check/tidy here.  The plframe widget
    // handles this stuff for us.
    //
    // pls->plbuf_write = 0;
}

//--------------------------------------------------------------------------
// plD_state_tkwin()
//
// Handle change in PLStream state (color, pen width, fill attribute, etc).
//--------------------------------------------------------------------------

void
plD_state_tkwin( PLStream *pls, PLINT op )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;
    dbug_enter( "plD_state_tkw" );
    if ( dev->flags & 1 )
        return;

    switch ( op )
    {
    case PLSTATE_WIDTH:
        break;

    case PLSTATE_COLOR0: {
        int icol0 = pls->icol0;
        if ( tkwd->color )
        {
            if ( icol0 == PL_RGB_COLOR )
            {
                PLColor_to_TkColor( &pls->curcolor, &dev->curcolor );
                Tkw_StoreColor( pls, tkwd, &dev->curcolor );
            }
            else
            {
                dev->curcolor = tkwd->cmap0[icol0];
            }
            XSetForeground( tkwd->display, dev->gc, dev->curcolor.pixel );
        }
        else
        {
            dev->curcolor = tkwd->fgcolor;
            XSetForeground( tkwd->display, dev->gc, dev->curcolor.pixel );
        }
        break;
    }

    case PLSTATE_COLOR1: {
        int icol1;

        if ( tkwd->ncol1 == 0 )
            AllocCmap1( pls );

        if ( tkwd->ncol1 < 2 )
            break;

        icol1 = ( pls->icol1 * ( tkwd->ncol1 - 1 ) ) / ( pls->ncol1 - 1 );
        if ( tkwd->color )
            dev->curcolor = tkwd->cmap1[icol1];
        else
            dev->curcolor = tkwd->fgcolor;

        XSetForeground( tkwd->display, dev->gc, dev->curcolor.pixel );
        break;
    }

    case PLSTATE_CMAP0:
        pltkwin_setBGFG( pls );
        StoreCmap0( pls );
        break;

    case PLSTATE_CMAP1:
        StoreCmap1( pls );
        break;
    }
}

//--------------------------------------------------------------------------
// plD_esc_tkwin()
//
// Escape function.
//
// Functions:
//
// PLESC_EH Handle pending events
// PLESC_EXPOSE Force an expose
// PLESC_FILL Fill polygon
// PLESC_FLUSH Flush X event buffer
// PLESC_GETC Get coordinates upon mouse click
// PLESC_REDRAW Force a redraw
// PLESC_RESIZE Force a resize
//--------------------------------------------------------------------------

void
plD_esc_tkwin( PLStream *pls, PLINT op, void *ptr )
{
    TkwDev     *dev = (TkwDev *) pls->dev;
#ifndef USE_TK
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;
#endif
    dbug_enter( "plD_esc_tkw" );
    if ( dev->flags & 1 )
        return;

    switch ( op )
    {
    case PLESC_EH:
#ifndef USE_TK
        HandleEvents( pls );
#endif
        break;

    case PLESC_EXPOSE:
        ExposeCmd( pls, (PLDisplay *) ptr );
        break;

    case PLESC_FILL:
        FillPolygonCmd( pls );
        break;

    case PLESC_FLUSH:
#ifndef USE_TK
        HandleEvents( pls );
        XFlush( tkwd->display );
#endif
        break;

    case PLESC_GETC:
#ifndef USE_TK
        GetCursorCmd( pls, (PLGraphicsIn *) ptr );
#endif
        break;

    case PLESC_REDRAW:
        RedrawCmd( pls );
        break;

    case PLESC_RESIZE:
        ResizeCmd( pls, (PLDisplay *) ptr );
        break;

// Added by Vince, disabled by default since we want a minimal patch
#ifdef USING_PLESC_COPY
    case PLESC_COPY:
        CopyCommand( pls );
        break;
#endif
    }
}

#ifdef USING_PLESC_COPY
//--------------------------------------------------------------------------
// CopyCommand()
//
// Copy a rectangle to a new part of the image.
// Points described in first 3 elements of pls->dev_x[] and pls->dev_y[].
//--------------------------------------------------------------------------

static void
CopyCommand( PLStream *pls )
{
    int        x0, w, x1, y0, h, y1;
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    x0 = (int) ( dev->xscale * pls->dev_x[0] );
    x1 = (int) ( dev->xscale * pls->dev_x[2] );
    y0 = (int) ( dev->yscale * ( dev->ylen - pls->dev_y[0] ) );
    y1 = (int) ( dev->yscale * ( dev->ylen - pls->dev_y[2] ) );
    w  = (int) ( dev->xscale * ( pls->dev_x[1] - pls->dev_x[0] ) );
    h  = (int) ( -dev->yscale * ( pls->dev_y[1] - pls->dev_y[0] ) );

    if ( dev->write_to_window )
        XCopyArea( tkwd->display, dev->window, dev->window, dev->gc,
            x0, y0, w, h, x1, y1 );

    if ( dev->write_to_pixmap )
        XCopyArea( tkwd->display, dev->pixmap, dev->pixmap, dev->gc,
            x0, y0, w, h, x1, y1 );
}
#endif

//--------------------------------------------------------------------------
// FillPolygonCmd()
//
// Fill polygon described in points pls->dev_x[] and pls->dev_y[].
// Only solid color fill supported.
//--------------------------------------------------------------------------

static void
FillPolygonCmd( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;
    XPoint     _pts[PL_MAXPOLY];
    XPoint     *pts;
    int        i;

    if ( pls->dev_npts > PL_MAXPOLY )
    {
        pts = (XPoint *) malloc( sizeof ( XPoint ) * (size_t) ( pls->dev_npts ) );
    }
    else
    {
        pts = _pts;
    }

    for ( i = 0; i < pls->dev_npts; i++ )
    {
        pts[i].x = (short) ( dev->xscale * pls->dev_x[i] );
        pts[i].y = (short) ( dev->yscale * ( dev->ylen - pls->dev_y[i] ) );
    }

// Fill polygons

    if ( dev->write_to_window )
        XFillPolygon( tkwd->display, dev->window, dev->gc,
            pts, pls->dev_npts, Nonconvex, CoordModeOrigin );

    if ( dev->write_to_pixmap )
        XFillPolygon( tkwd->display, dev->pixmap, dev->gc,
            pts, pls->dev_npts, Nonconvex, CoordModeOrigin );

// If in debug mode, draw outline of boxes being filled

#ifdef DEBUG
    if ( pls->debug )
    {
        XSetForeground( tkwd->display, dev->gc, tkwd->fgcolor.pixel );
        if ( dev->write_to_window )
            XDrawLines( tkwd->display, dev->window, dev->gc, pts, pls->dev_npts,
                CoordModeOrigin );

        if ( dev->write_to_pixmap )
            XDrawLines( tkwd->display, dev->pixmap, dev->gc, pts, pls->dev_npts,
                CoordModeOrigin );

        XSetForeground( tkwd->display, dev->gc, dev->curcolor.pixel );
    }
#endif

    if ( pls->dev_npts > PL_MAXPOLY )
    {
        free( pts );
    }
}

//--------------------------------------------------------------------------
// Init()
//
// Xlib initialization routine.
//
// Controlling routine for X window creation and/or initialization.
// The user may customize the window in the following ways:
//
// display: pls->OutFile (use plsfnam() or -display option)
// size: pls->xlength, pls->ylength (use plspage() or -geo option)
// bg color: pls->cmap0[0] (use plscolbg() or -bg option)
//--------------------------------------------------------------------------

static void
Init( PLStream *pls )
{
    PlPlotter  *plf;
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    dbug_enter( "Init" );

    dev->window = (Window) pls->window_id;

    plf = pls->plPlotterPtr;
    if ( plf == NULL )
    {
        plwarn( "Init: Illegal call --- driver can't find enclosing PlPlotter" );
        return;
    }

// Initialize colors
    InitColors( pls );
#ifndef MAC_TCL
    XSetWindowColormap( tkwd->display, dev->window, tkwd->map );
#else
#endif

// Set up GC for ordinary draws
    if ( !dev->gc )
        dev->gc = XCreateGC( tkwd->display, dev->window, 0, 0 );

// Set up GC for rubber-band draws
    if ( !tkwd->gcXor )
    {
        XGCValues     gcValues;
        unsigned long mask;

        gcValues.background = tkwd->cmap0[0].pixel;
        gcValues.foreground = 0xFF;
        gcValues.function   = GXxor;
        mask = GCForeground | GCBackground | GCFunction;

        tkwd->gcXor = XCreateGC( tkwd->display, dev->window, mask, &gcValues );
    }

// Get initial drawing area dimensions
    dev->width  = (unsigned int) Tk_Width( plf->tkwin );
    dev->height = (unsigned int) Tk_Height( plf->tkwin );
    dev->border = (unsigned int) Tk_InternalBorderWidth( plf->tkwin );
    tkwd->depth = (unsigned int) Tk_Depth( plf->tkwin );

    dev->init_width  = dev->width;
    dev->init_height = dev->height;

    // Set up flags that determine what we are writing to
    // If nopixmap is set, ignore db

    if ( pls->nopixmap )
    {
        dev->write_to_pixmap = 0;
        pls->db = 0;
    }
    else
    {
        dev->write_to_pixmap = 1;
    }
    dev->write_to_window = !pls->db;

    // Create pixmap for holding plot image (for expose events).

    if ( dev->write_to_pixmap )
        CreatePixmap( pls );

    // Set drawing color

    plD_state_tkwin( pls, PLSTATE_COLOR0 );

    XSetWindowBackground( tkwd->display, dev->window, tkwd->cmap0[0].pixel );
    XSetBackground( tkwd->display, dev->gc, tkwd->cmap0[0].pixel );
}

//--------------------------------------------------------------------------
// ExposeCmd()
//
// Event handler routine for expose events.
// These are "pure" exposures (no resize), so don't need to clear window.
//--------------------------------------------------------------------------

static void
ExposeCmd( PLStream *pls, PLDisplay *pldis )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;
    int        x, y, width, height;

    dbug_enter( "ExposeCmd" );

    // Return if plD_init_tkw hasn't been called yet

    if ( dev == NULL )
    {
        plwarn( "ExposeCmd: Illegal call -- driver uninitialized" );
        return;
    }

    // Exposed area. If unspecified, the entire window is used.

    if ( pldis == NULL )
    {
        x      = 0;
        y      = 0;
        width  = (int) dev->width;
        height = (int) dev->height;
    }
    else
    {
        x      = (int) pldis->x;
        y      = (int) pldis->y;
        width  = (int) pldis->width;
        height = (int) pldis->height;
    }

    // Usual case: refresh window from pixmap
    // DEBUG option: draws rectangle around refreshed region

    XSync( tkwd->display, 0 );
    if ( dev->write_to_pixmap )
    {
        XCopyArea( tkwd->display, dev->pixmap, dev->window, dev->gc,
            x, y, (unsigned int) width, (unsigned int) height, x, y );
        XSync( tkwd->display, 0 );
#ifdef DEBUG
        if ( pls->debug )
        {
            XPoint pts[5];
            int    x0 = x, x1 = x + width, y0 = y, y1 = y + height;
            pts[0].x = (short) x0; pts[0].y = (short) y0;
            pts[1].x = (short) x1; pts[1].y = (short) y0;
            pts[2].x = (short) x1; pts[2].y = (short) y1;
            pts[3].x = (short) x0; pts[3].y = (short) y1;
            pts[4].x = (short) x0; pts[4].y = (short) y0;

            XDrawLines( tkwd->display, dev->window, dev->gc, pts, 5,
                CoordModeOrigin );
        }
#endif
    }
    else
    {
        plRemakePlot( pls );
        XFlush( tkwd->display );
    }
}

//--------------------------------------------------------------------------
// ResizeCmd()
//
// Event handler routine for resize events.
//--------------------------------------------------------------------------

static void
ResizeCmd( PLStream *pls, PLDisplay *pldis )
{
    TkwDev     *dev            = (TkwDev *) pls->dev;
    TkwDisplay *tkwd           = (TkwDisplay *) dev->tkwd;
    int        write_to_window = dev->write_to_window;

    dbug_enter( "ResizeCmd" );

    // Return if plD_init_tkw hasn't been called yet

    if ( dev == NULL )
    {
        plwarn( "ResizeCmd: Illegal call -- driver uninitialized" );
        return;
    }

    // Return if pointer to window not specified.

    if ( pldis == NULL )
    {
        plwarn( "ResizeCmd: Illegal call -- window pointer uninitialized" );
        return;
    }

    // Reset current window bounds

    dev->width  = pldis->width;
    dev->height = pldis->height;

    dev->xscale = dev->width / (double) dev->init_width;
    dev->yscale = dev->height / (double) dev->init_height;

    dev->xscale = dev->xscale * dev->xscale_init;
    dev->yscale = dev->yscale * dev->yscale_init;

#if PHYSICAL
    {
        float pxlx = (double) PIXELS_X / dev->width * DPMM;
        float pxly = (double) PIXELS_Y / dev->height * DPMM;
        plP_setpxl( pxlx, pxly );
    }
#endif

    // Note: the following order MUST be obeyed -- if you instead redraw into
    // the window and then copy it to the pixmap, off-screen parts of the window
    // may contain garbage which is then transferred to the pixmap (and thus
    // will not go away after an expose).
    //

    // Resize pixmap using new dimensions

    if ( dev->write_to_pixmap )
    {
        dev->write_to_window = 0;
#if defined ( _WIN32 ) || defined ( MAC_TCL )
        Tk_FreePixmap( tkwd->display, dev->pixmap );
#else
        // Vince's original driver code used
        // Tk_FreePixmap(tkwd->display, dev->pixmap);
        //which is defined in tk-8.3 (and 8.2?) source as
        //void
        // Tk_FreePixmap(display, pixmap)
        //     Display *display;
        //     Pixmap pixmap;
        // {
        //    XFreePixmap(display, pixmap);
        //    Tk_FreeXId(display, (XID) pixmap);
        // }
        // But that bombed under Linux and tcl/tk8.2 so now just call
        // XFreePixmap directly.  (Not recommended as permanent solution
        // because you eventually run out of resources according to man
        // page if you don't call Tk_FreeXId.)  Vince is still looking into
        // how to resolve this problem.
        //
        XFreePixmap( tkwd->display, dev->pixmap );
#endif
        CreatePixmap( pls );
    }

    // Initialize & redraw (to pixmap, if available).

    plD_bop_tkwin( pls );
    plRemakePlot( pls );
    XSync( tkwd->display, 0 );

    // If pixmap available, fake an expose

    if ( dev->write_to_pixmap )
    {
        dev->write_to_window = write_to_window;
        XCopyArea( tkwd->display, dev->pixmap, dev->window, dev->gc, 0, 0,
            dev->width, dev->height, 0, 0 );
        XSync( tkwd->display, 0 );
    }
}

//--------------------------------------------------------------------------
// RedrawCmd()
//
// Handles page redraw without resize (pixmap does not get reallocated).
// Calling this makes sure all necessary housekeeping gets done.
//--------------------------------------------------------------------------

static void
RedrawCmd( PLStream *pls )
{
    TkwDev     *dev            = (TkwDev *) pls->dev;
    TkwDisplay *tkwd           = (TkwDisplay *) dev->tkwd;
    int        write_to_window = dev->write_to_window;

    dbug_enter( "RedrawCmd" );

    // Return if plD_init_tkw hasn't been called yet

    if ( dev == NULL )
    {
        plwarn( "RedrawCmd: Illegal call -- driver uninitialized" );
        return;
    }

    // Initialize & redraw (to pixmap, if available).

    if ( dev->write_to_pixmap )
        dev->write_to_window = 0;

    plD_bop_tkwin( pls );
    plRemakePlot( pls );
    XSync( tkwd->display, 0 );

    dev->write_to_window = write_to_window;

    // If pixmap available, fake an expose

    if ( dev->write_to_pixmap )
    {
        XCopyArea( tkwd->display, dev->pixmap, dev->window, dev->gc, 0, 0,
            dev->width, dev->height, 0, 0 );
        XSync( tkwd->display, 0 );
    }
}

//--------------------------------------------------------------------------
// CreatePixmap()
//
// This routine creates a pixmap, doing error trapping in case there
// isn't enough memory on the server.
//--------------------------------------------------------------------------

static void
CreatePixmap( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;
    Tk_Window  tkwin = pls->plPlotterPtr->tkwin;

#if !defined ( MAC_TCL ) && !defined ( _WIN32 )
    int ( *oldErrorHandler )( Display *, XErrorEvent * );
    oldErrorHandler    = XSetErrorHandler( CreatePixmapErrorHandler );
    CreatePixmapStatus = Success;
#endif

#ifdef MAC_TCL
    // MAC_TCL's version of XCreatePixmap doesn't like 0 by 0 maps
    if ( dev->width == 0 )
    {
        dev->width = 10;
    }
    if ( dev->height == 0 )
    {
        dev->height = 10;
    }
#endif
    pldebug( "CreatePixmap",
        "creating pixmap: width = %d, height = %d, depth = %d\n",
        dev->width, dev->height, tkwd->depth );
//
// dev->pixmap = Tk_GetPixmap(tkwd->display, dev->window,
//                             dev->width, dev->height, tkwd->depth);
//
//
// Vince's original driver code used Tk_Display(tkwin) for first argument,
// but that bombed on an Linux tcl/tk 8.2 machine.  Something was wrong
// with that value.  Thus, we now use tkwd->display, and that works well.
// Vince is looking into why Tk_Display(tkwin) is badly defined under 8.2.
// old code:
//
//  dev->pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
//          Tk_Width(tkwin), Tk_Height(tkwin),
//          DefaultDepthOfScreen(Tk_Screen(tkwin)));
//
    dev->pixmap = Tk_GetPixmap( tkwd->display, Tk_WindowId( tkwin ),
        Tk_Width( tkwin ), Tk_Height( tkwin ),
        DefaultDepthOfScreen( Tk_Screen( tkwin ) ) );
    XSync( tkwd->display, 0 );
#if !defined ( MAC_TCL ) && !defined ( _WIN32 )
    if ( CreatePixmapStatus != Success )
    {
        dev->write_to_pixmap = 0;
        dev->write_to_window = 1;
        pls->db = 0;
        fprintf( stderr, "\n\
      Warning: pixmap could not be allocated (insufficient memory on server).\n\
      Driver will redraw the entire plot to handle expose events.\n" );
    }

    XSetErrorHandler( oldErrorHandler );
#endif
}

//--------------------------------------------------------------------------
// GetVisual()
//
// Get visual info. In order to safely use a visual other than that of
// the parent (which hopefully is that returned by DefaultVisual), you
// must first find (using XGetRGBColormaps) or create a colormap matching
// this visual and then set the colormap window attribute in the
// XCreateWindow attributes and valuemask arguments. I don't do this
// right now, so this is turned off by default.
//--------------------------------------------------------------------------

static void
GetVisual( PLStream *pls )
{
    int        depth;
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    dbug_enter( "GetVisual" );

    tkwd->visual = Tk_GetVisual( pls->plPlotterPtr->interp,
        pls->plPlotterPtr->tkwin,
        "best",
        &depth, NULL );
    tkwd->depth = (unsigned int) depth;
}

//--------------------------------------------------------------------------
// AllocBGFG()
//
// Allocate background & foreground colors. If possible, I choose pixel
// values such that the fg pixel is the xor of the bg pixel, to make
// rubber-banding easy to see.
//--------------------------------------------------------------------------

static void
AllocBGFG( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

#ifndef USE_TK
    int           i, j, npixels;
    unsigned long plane_masks[1], pixels[MAX_COLORS];
#endif

    dbug_enter( "AllocBGFG" );

    // If not on a color system, just return

    if ( !tkwd->color )
        return;
#ifndef USE_TK
    // Allocate r/w color cell for background

    if ( XAllocColorCells( tkwd->display, tkwd->map, False,
             plane_masks, 0, pixels, 1 ) )
    {
        tkwd->cmap0[0].pixel = pixels[0];
    }
    else
    {
        plexit( "couldn't allocate background color cell" );
    }

    // Allocate as many colors as we can

    npixels = MAX_COLORS;
    for (;; )
    {
        if ( XAllocColorCells( tkwd->display, tkwd->map, False,
                 plane_masks, 0, pixels, npixels ) )
            break;
        npixels--;
        if ( npixels == 0 )
            break;
    }

    // Find the color with pixel = xor of the bg color pixel.
    // If a match isn't found, the last pixel allocated is used.

    for ( i = 0; i < npixels - 1; i++ )
    {
        if ( pixels[i] == ( ~tkwd->cmap0[0].pixel & 0xFF ) )
            break;
    }

    // Use this color cell for our foreground color. Then free the rest.

    tkwd->fgcolor.pixel = pixels[i];
    for ( j = 0; j < npixels; j++ )
    {
        if ( j != i )
            XFreeColors( tkwd->display, tkwd->map, &pixels[j], 1, 0 );
    }
#endif
}

//--------------------------------------------------------------------------
// pltkwin_setBGFG()
//
// Set background & foreground colors. Foreground over background should
// have high contrast.
//--------------------------------------------------------------------------

void
pltkwin_setBGFG( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;
    PLColor    fgcolor;
    int        gslevbg, gslevfg;

    dbug_enter( "pltkwin_setBGFG" );

    //
    // Set background color.
    //
    // Background defaults to black on color screens, white on grayscale (many
    // grayscale monitors have poor contrast, and black-on-white looks better).
    //

    if ( !tkwd->color )
    {
        pls->cmap0[0].r = pls->cmap0[0].g = pls->cmap0[0].b = 0xFF;
    }
    gslevbg = (int) ( ( (long) pls->cmap0[0].r +
                        (long) pls->cmap0[0].g +
                        (long) pls->cmap0[0].b ) / 3 );

    PLColor_to_TkColor( &pls->cmap0[0], &tkwd->cmap0[0] );

    //
    // Set foreground color.
    //
    // Used for grayscale output, since otherwise the plots can become nearly
    // unreadable (i.e. if colors get mapped onto grayscale values). In this
    // case it becomes the grayscale level for all draws, and is taken to be
    // black if the background is light, and white if the background is dark.
    // Note that white/black allocations never fail.
    //

    if ( gslevbg > 0x7F )
        gslevfg = 0;
    else
        gslevfg = 0xFF;

    fgcolor.r = fgcolor.g = fgcolor.b = (unsigned char) gslevfg;

    PLColor_to_TkColor( &fgcolor, &tkwd->fgcolor );

    // Now store
#ifndef USE_TK
    if ( tkwd->color )
    {
        XStoreColor( tkwd->display, tkwd->map, &tkwd->fgcolor );
        XStoreColor( tkwd->display, tkwd->map, &tkwd->cmap0[0] );
    }
    else
    {
        XAllocColor( tkwd->display, tkwd->map, &tkwd->cmap0[0] );
        XAllocColor( tkwd->display, tkwd->map, &tkwd->fgcolor );
    }
#else
    Tkw_StoreColor( pls, tkwd, &tkwd->cmap0[0] );
    Tkw_StoreColor( pls, tkwd, &tkwd->fgcolor );
#endif
}

//--------------------------------------------------------------------------
// InitColors()
//
// Does all color initialization.
//--------------------------------------------------------------------------

static void
InitColors( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    dbug_enter( "InitColors" );

    // Allocate and initialize color maps.
    // Defer cmap1 allocation until it's actually used

    if ( tkwd->color )
    {
        if ( plplot_tkwin_ccmap )
        {
            AllocCustomMap( pls );
        }
        else
        {
            AllocCmap0( pls );
        }
    }
}

//--------------------------------------------------------------------------
// AllocCustomMap()
//
// Initializes custom color map and all the cruft that goes with it.
//
// Assuming all color X displays do 256 colors, the breakdown is as follows:
//
// XWM_COLORS Number of low "pixel" values to copy. These are typically
//  allocated first, thus are in use by the window manager. I
//  copy them to reduce flicker.
//
// CMAP0_COLORS Color map 0 entries. I allocate these both in the default
//  colormap and the custom colormap to reduce flicker.
//
// CMAP1_COLORS Color map 1 entries. There should be as many as practical
//  available for smooth shading. On the order of 50-100 is
//  pretty reasonable. You don't really need all 256,
//  especially if all you're going to do is to print it to
//  postscript (which doesn't have any intrinsic limitation on
//  the number of colors).
//
// It's important to leave some extra colors unallocated for Tk. In
// particular the palette tools require a fair amount. I recommend leaving
// at least 40 or so free.
//--------------------------------------------------------------------------

static void
AllocCustomMap( PLStream *pls )
{
    TkwDev        *dev  = (TkwDev *) pls->dev;
    TkwDisplay    *tkwd = (TkwDisplay *) dev->tkwd;

    XColor        xwm_colors[MAX_COLORS];
    int           i;
#ifndef USE_TK
    int           npixels;
    unsigned long plane_masks[1], pixels[MAX_COLORS];
#endif

    dbug_enter( "AllocCustomMap" );

    // Determine current default colors

    for ( i = 0; i < MAX_COLORS; i++ )
    {
        xwm_colors[i].pixel = (long unsigned) i;
    }
#ifndef MAC_TCL
    XQueryColors( tkwd->display, tkwd->map, xwm_colors, MAX_COLORS );
#endif

    // Allocate cmap0 colors in the default colormap.
    // The custom cmap0 colors are later stored at the same pixel values.
    // This is a really cool trick to reduce the flicker when changing colormaps.
    //

    AllocCmap0( pls );
    XAllocColor( tkwd->display, tkwd->map, &tkwd->fgcolor );

    // Create new color map

    tkwd->map = XCreateColormap( tkwd->display, DefaultRootWindow( tkwd->display ),
        tkwd->visual, AllocNone );

    // Now allocate all colors so we can fill the ones we want to copy

#ifndef USE_TK
    npixels = MAX_COLORS;
    for (;; )
    {
        if ( XAllocColorCells( tkwd->display, tkwd->map, False,
                 plane_masks, 0, pixels, npixels ) )
            break;
        npixels--;
        if ( npixels == 0 )
            plexit( "couldn't allocate any colors" );
    }

    // Fill the low colors since those are in use by the window manager

    for ( i = 0; i < XWM_COLORS; i++ )
    {
        XStoreColor( tkwd->display, tkwd->map, &xwm_colors[i] );
        pixels[xwm_colors[i].pixel] = 0;
    }

    // Fill the ones we will use in cmap0

    for ( i = 0; i < tkwd->ncol0; i++ )
    {
        XStoreColor( tkwd->display, tkwd->map, &tkwd->cmap0[i] );
        pixels[tkwd->cmap0[i].pixel] = 0;
    }

    // Finally, if the colormap was saved by an external agent, see if there are
    // any differences from the current default map and save those! A very cool
    // (or sick, depending on how you look at it) trick to get over some X and
    // Tk limitations.
    //

    if ( sxwm_colors_set )
    {
        for ( i = 0; i < MAX_COLORS; i++ )
        {
            if ( ( xwm_colors[i].red != sxwm_colors[i].red ) ||
                 ( xwm_colors[i].green != sxwm_colors[i].green ) ||
                 ( xwm_colors[i].blue != sxwm_colors[i].blue ) )
            {
                if ( pixels[i] != 0 )
                {
                    XStoreColor( tkwd->display, tkwd->map, &xwm_colors[i] );
                    pixels[i] = 0;
                }
            }
        }
    }

    // Now free the ones we're not interested in

    for ( i = 0; i < npixels; i++ )
    {
        if ( pixels[i] != 0 )
            XFreeColors( tkwd->display, tkwd->map, &pixels[i], 1, 0 );
    }
#endif
    // Allocate colors in cmap 1

    AllocCmap1( pls );
}

//--------------------------------------------------------------------------
// AllocCmap0()
//
// Allocate & initialize cmap0 entries.
//--------------------------------------------------------------------------

static void
AllocCmap0( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

#ifndef USE_TK
    int           npixels;
    int           i;
    unsigned long plane_masks[1], pixels[MAX_COLORS];
#endif

    dbug_enter( "AllocCmap0" );

    // Allocate and assign colors in cmap 0

#ifndef USE_TK
    npixels = pls->ncol0 - 1;
    for (;; )
    {
        if ( XAllocColorCells( tkwd->display, tkwd->map, False,
                 plane_masks, 0, &pixels[1], npixels ) )
            break;
        npixels--;
        if ( npixels == 0 )
            plexit( "couldn't allocate any colors" );
    }

    tkwd->ncol0 = npixels + 1;
    for ( i = 1; i < tkwd->ncol0; i++ )
    {
        tkwd->cmap0[i].pixel = pixels[i];
    }
#else
    // We use the Tk color scheme
    tkwd->ncol0 = pls->ncol0;
#endif
    StoreCmap0( pls );
}

//--------------------------------------------------------------------------
// AllocCmap1()
//
// Allocate & initialize cmap1 entries. If using the default color map,
// must severely limit number of colors otherwise TK won't have enough.
//--------------------------------------------------------------------------

static void
AllocCmap1( PLStream *pls )
{
    TkwDev        *dev  = (TkwDev *) pls->dev;
    TkwDisplay    *tkwd = (TkwDisplay *) dev->tkwd;

    int           npixels;
#ifndef USE_TK
    int           i, j;
    unsigned long plane_masks[1], pixels[MAX_COLORS];
#endif

    dbug_enter( "AllocCmap1" );

    // Allocate colors in cmap 1

    npixels = MAX( 2, MIN( CMAP1_COLORS, pls->ncol1 ) );
#ifndef USE_TK
    for (;; )
    {
        if ( XAllocColorCells( tkwd->display, tkwd->map, False,
                 plane_masks, 0, pixels, npixels ) )
            break;
        npixels--;
        if ( npixels == 0 )
            break;
    }

    if ( npixels < 2 )
    {
        tkwd->ncol1 = -1;
        fprintf( stderr,
            "Warning: unable to allocate sufficient colors in cmap1\n" );
        return;
    }
    else
    {
        tkwd->ncol1 = npixels;
        if ( pls->verbose )
            fprintf( stderr, "AllocCmap1 (xwin.c): Allocated %d colors in cmap1\n", npixels );
    }

    // Don't assign pixels sequentially, to avoid strange problems with xor GC's
    // Skipping by 2 seems to do the job best

    for ( j = i = 0; i < tkwd->ncol1; i++ )
    {
        while ( pixels[j] == 0 )
            j++;

        tkwd->cmap1[i].pixel = pixels[j];
        pixels[j]            = 0;

        j += 2;
        if ( j >= tkwd->ncol1 )
            j = 0;
    }
#else
    tkwd->ncol1 = npixels;
#endif
    StoreCmap1( pls );
}

//--------------------------------------------------------------------------
// StoreCmap0()
//
// Stores cmap 0 entries in X-server colormap.
//--------------------------------------------------------------------------

static void
StoreCmap0( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;
    int        i;

    if ( !tkwd->color )
        return;

    for ( i = 1; i < tkwd->ncol0; i++ )
    {
        PLColor_to_TkColor( &pls->cmap0[i], &tkwd->cmap0[i] );
#ifndef USE_TK
        XStoreColor( tkwd->display, tkwd->map, &tkwd->cmap0[i] );
#else
        Tkw_StoreColor( pls, tkwd, &tkwd->cmap0[i] );
#endif
    }
}

void CopyColour( XColor* from, XColor* to )
{
    to->pixel = from->pixel;
    to->red   = from->red;
    to->blue  = from->blue;
    to->green = from->green;
    to->flags = from->flags;
}

//--------------------------------------------------------------------------
// StoreCmap1()
//
// Stores cmap 1 entries in X-server colormap.
//--------------------------------------------------------------------------

static void
StoreCmap1( PLStream *pls )
{
    TkwDev     *dev  = (TkwDev *) pls->dev;
    TkwDisplay *tkwd = (TkwDisplay *) dev->tkwd;

    PLColor    cmap1color;
    int        i;

    if ( !tkwd->color )
        return;

    for ( i = 0; i < tkwd->ncol1; i++ )
    {
        plcol_interp( pls, &cmap1color, i, tkwd->ncol1 );
        PLColor_to_TkColor( &cmap1color, &tkwd->cmap1[i] );
#ifndef USE_TK
        XStoreColor( tkwd->display, tkwd->map, &tkwd->cmap1[i] );
#else
        Tkw_StoreColor( pls, tkwd, &tkwd->cmap1[i] );
#endif
    }
}

void Tkw_StoreColor( PLStream* pls, TkwDisplay* tkwd, XColor* col )
{
    XColor *xc;
#ifndef USE_TK
    XStoreColor( tkwd->display, tkwd->map, col );
#else
    (void) tkwd;  // tkwd unused in this case
    // We're probably losing memory here
    xc = Tk_GetColorByValue( pls->plPlotterPtr->tkwin, col );
    CopyColour( xc, col );
#endif
}

//--------------------------------------------------------------------------
// void PLColor_to_TkColor()
//
// Copies the supplied PLColor to an XColor, padding with bits as necessary
// (a PLColor uses 8 bits for color storage, while an XColor uses 16 bits).
// The argument types follow the same order as in the function name.
//--------------------------------------------------------------------------

#define ToXColor( a )     ( ( ( 0xFF & ( a ) ) << 8 ) | ( a ) )
#define ToPLColor( a )    ( ( (U_LONG) a ) >> 8 )

void
PLColor_to_TkColor( PLColor *plcolor, XColor *xcolor )
{
    xcolor->red   = (short unsigned) ToXColor( plcolor->r );
    xcolor->green = (short unsigned) ToXColor( plcolor->g );
    xcolor->blue  = (short unsigned) ToXColor( plcolor->b );
    xcolor->flags = DoRed | DoGreen | DoBlue;
}

//--------------------------------------------------------------------------
// void PLColor_from_TkColor()
//
// Copies the supplied XColor to a PLColor, stripping off bits as
// necessary. See the previous routine for more info.
//--------------------------------------------------------------------------

void
PLColor_from_TkColor( PLColor *plcolor, XColor *xcolor )
{
    plcolor->r = (unsigned char) ToPLColor( xcolor->red );
    plcolor->g = (unsigned char) ToPLColor( xcolor->green );
    plcolor->b = (unsigned char) ToPLColor( xcolor->blue );
}

//--------------------------------------------------------------------------
// void PLColor_from_TkColor_Changed()
//
// Copies the supplied XColor to a PLColor, stripping off bits as
// necessary. See the previous routine for more info.
//
// Returns 1 if the color was different from the old one.
//--------------------------------------------------------------------------

int
PLColor_from_TkColor_Changed( PLColor *plcolor, XColor *xcolor )
{
    int changed = 0;
    int color;
    color = ToPLColor( xcolor->red );

    if ( plcolor->r != color )
    {
        changed    = 1;
        plcolor->r = (unsigned char) color;
    }
    color = ToPLColor( xcolor->green );
    if ( plcolor->g != color )
    {
        changed    = 1;
        plcolor->g = (unsigned char) color;
    }
    color = ToPLColor( xcolor->blue );
    if ( plcolor->b != color )
    {
        changed    = 1;
        plcolor->b = (unsigned char) color;
    }
    return changed;
}

//--------------------------------------------------------------------------
// int pltk_AreWeGrayscale(PlPlotter *plf)
//
// Determines if we're using a monochrome or grayscale device.
// gmf 11-8-91; Courtesy of Paul Martz of Evans and Sutherland.
// Changed July 1996 by Vince: now uses Tk to check the enclosing PlPlotter
//--------------------------------------------------------------------------

static int
pltk_AreWeGrayscale( PlPlotter *plf )
{
#if defined ( __cplusplus ) || defined ( c_plusplus )
#define THING    c_class
#else
#define THING    class
#endif

    Visual* visual;
    // get the window's Visual
    visual = Tk_Visual( plf->tkwin );
    if ( ( visual->THING != GrayScale ) && ( visual->THING != StaticGray ) )
        return ( 0 );
    // if we got this far, only StaticGray and GrayScale classes available
    return ( 1 );
}

#if !defined ( MAC_TCL ) && !defined ( _WIN32 )
//--------------------------------------------------------------------------
// CreatePixmapErrorHandler()
//
// Error handler used in CreatePixmap() to catch errors in allocating
// storage for pixmap. This way we can nicely substitute redraws for
// pixmap copies if the server has insufficient memory.
//--------------------------------------------------------------------------

static int
CreatePixmapErrorHandler( Display *display, XErrorEvent *error )
{
    if ( error->error_code == BadAlloc )
    {
        CreatePixmapStatus = error->error_code;
    }
    else
    {
        char buffer[256];
        XGetErrorText( display, error->error_code, buffer, 256 );
        fprintf( stderr, "Error in XCreatePixmap: %s.\n", buffer );
    }
    return 1;
}
#endif

#else
int
pldummy_tkwin()
{
    return 0;
}

#endif    // PLD_tkwin

void *  ckcalloc( size_t nmemb, size_t size )
{
    long *ptr;
    long *p;
    size *= nmemb;
    ptr   = (long *) malloc( size );
    if ( !ptr )
        return ( 0 );

#if !__POWERPC__

    for ( size = ( size / sizeof ( long ) ) + 1, p = ptr; --size; )
        *p++ = 0;

#else

    for ( size = ( size / sizeof ( long ) ) + 1, p = ptr - 1; --size; )
        *++p = 0;

#endif

    return ( ptr );
}
