/* grTOGL1.c
 *
 * Copyright (C) 2003 Open Circuit Design, Inc., for MultiGiG Ltd.
 *
 * This file contains primitive functions for OpenGL running under
 * an X window system in a Tcl/Tk interpreter environment
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include <GL/gl.h>
#include <GL/glx.h>

#include "tcltk/tclmagic.h"
#include "utils/main.h"
#include "utils/magic.h"
#include "utils/malloc.h"
#include "utils/geometry.h"
#include "windows/windows.h"
#include "graphics/graphics.h"
#include "graphics/graphicsInt.h"
#include "textio/textio.h"
#include "textio/txcommands.h"
#include "utils/signals.h"
#include "utils/utils.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "drc/drc.h"
#include "utils/macros.h"
#include "graphics/grTOGLInt.h"
#include "utils/paths.h"
#include "graphics/grTkCommon.h"

/* C99 compat */
#include "dbwind/dbwind.h"

GLubyte		**grTOGLStipples;
HashTable	grTOGLWindowTable;
GLXContext	grXcontext;
XVisualInfo	*grVisualInfo;

TOGL_CURRENT toglCurrent= {(Tk_Font)0, 0, 0, 0, 0,
	(Tk_Window)0, (Window)0, (MagWindow *)NULL};

/* This is kind of a long story, and very kludgy, but the following
 * things need to be defined as externals because of the way lint
 * libraries are made by taking this module and changing all procedures
 * names "Xxxx" to "Grxxx".  The change is only done at the declaration
 * of the procedure, so we need these declarations to handle uses
 * of those names, which don't get modified.  Check out the Makefile
 * for details on this.
 */
extern void GrTOGLClose(), GrTOGLFlush();
extern void GrTOGLDelete(), GrTOGLConfigure(), GrTOGLRaise(), GrTOGLLower();
extern void GrTOGLLock(), GrTOGLUnlock(), GrTOGLIconUpdate();
extern bool GrTOGLInit();
extern bool GrTOGLEventPending(), GrTOGLCreate(), grtoglGetCursorPos();
extern int  GrTOGLWindowId();
extern char *GrTkWindowName();

extern void toglSetProjection();


/*---------------------------------------------------------
 * grtoglSetWMandC:
 *	This is a local routine that resets the value of the current
 *	write alpha (mask) and color, if necessary.
 *
 * Results:	None.
 *
 * Side Effects:    None.
 *
 * Errors:		None.
 *---------------------------------------------------------
 */

void
grtoglSetWMandC (mask, c)
    int mask;			/* New value for write mask */
    int c;			/* New value for current color */
{
    static int oldColor = -1;
    static int oldMask = -1;

    int lr, lb, lg;
    GLfloat fr, fb, fg;
    GLfloat aval; 	 /* Alpha default value was 0.75 */

    if (mask == -65) mask = 127;	/* All planes */
    if (mask == oldMask && c == oldColor) return;

    GR_TOGL_FLUSH_BATCH();

    GrGetColor(c, &lr, &lb, &lg);

    fr = ((GLfloat)lr / 255);
    fg = ((GLfloat)lg / 255);
    fb = ((GLfloat)lb / 255);

    if (mask == 127)
    {
	glDisable(GL_BLEND);
	aval = 1.0;
    }
    else
    {
	/* Calculate a "supercolor", outside of the normal color range, */
	/* but which results in the desired color after a blend with	*/
	/* the background color.					*/

	fr = fr * 2 - 0.8;
	fg = fg * 2 - 0.8;
	fb = fb * 2 - 0.8;

	aval = (GLfloat)mask / 127.0;	/* mask translates to alpha in	*/
					/* the OpenGL version.		*/

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }
    glColor4f(fr, fb, fg, aval);

    oldColor = c;
    oldMask = mask;
}


/*---------------------------------------------------------
 * grtoglSetLineStyle:
 *	This local routine sets the current line style.
 *
 * Results:	None.
 *
 * Side Effects:
 *	A new line style is output to the display.
 *
 *---------------------------------------------------------
 */

void
grtoglSetLineStyle (style)
    int style;			/* New stipple pattern for lines. */
{
    static int oldStyle = -1;
    GLushort glstyle;

    style &= 0xFF;
    if (style == oldStyle) return;
    oldStyle = style;
    GR_TOGL_FLUSH_BATCH();

    switch (style) {
	case 0xFF:
	case 0x00:
	    glDisable(GL_LINE_STIPPLE);
	    break;
	default:
	    glstyle = style | (style << 8);
	    glEnable(GL_LINE_STIPPLE);
	    glLineStipple(1, glstyle);
	    break;
    }
}


/*---------------------------------------------------------
 * grtoglSetSPattern:
 *	toglSetSPattern associates a stipple pattern with a given
 *	stipple number.  This is a local routine called from
 *	grStyle.c .
 *
 * Results:	None.
 *
 * Side Effects:    None.
 *---------------------------------------------------------
 */

void
grtoglSetSPattern (sttable, numstipples)
    int **sttable;			/* The table of patterns */
    int numstipples;			/* Number of stipples */
{
    int i, j, k, n;
    GLubyte *pdata;

    grTOGLStipples = (GLubyte **)mallocMagic(numstipples * sizeof(GLubyte *));
    for (k = 0; k < numstipples; k++)
    {
	pdata = (GLubyte *)mallocMagic(128 * sizeof(GLubyte));
	n = 0;

	/* expand magic's default 8x8 stipple to OpenGL's 32x32 */

	for (i = 0; i < 32; i++)
	    for (j = 0; j < 4; j++)
		pdata[n++] = (GLubyte)sttable[k][i % 8];

	grTOGLStipples[k] = pdata;
    }
}


/*---------------------------------------------------------
 * grtoglSetStipple:
 *	This routine sets the Xs current stipple number.
 *
 * Results: None.
 *
 * Side Effects:
 *	The current clipmask in the X is set to stipple,
 *	if it wasn't that already.
 *---------------------------------------------------------
 */

void
grtoglSetStipple (stipple)
    int stipple;			/* The stipple number to be used. */
{
    static int oldStip = -1;
    if (stipple == oldStip) return;
    oldStip = stipple;
    GR_TOGL_FLUSH_BATCH();
    if (stipple == 0 || stipple > grNumStipples) {
	glDisable(GL_POLYGON_STIPPLE);
    } else {
	if (grTOGLStipples[stipple] == (GLubyte *)NULL) MainExit(1);
	glEnable(GL_POLYGON_STIPPLE);
	glPolygonStipple(grTOGLStipples[stipple]);
    }
}


/*------------------------------------------------------------------------
 * GrTOGLInit:
 *	GrTOGLInit initializes the graphics display and clears its screen.
 *	Files must have been previously opened with GrSetDisplay();
 *
 * Results: TRUE if successful.
 *
 * Notes: When 3D rendering is compiled in, we search for a double-buffered
 *	configuration first, because it generates the smoothest graphics,
 *	and fall back on a single-buffered configuration if necessary.
 *	For normal, 2D-only rendering, we look for a single-buffered
 *	configuration first because we don't use the back buffer, so a
 *	double-buffered configuration just wastes space.
 *------------------------------------------------------------------------
 */

bool
GrTOGLInit ()
{
    bool rstatus;
#ifdef THREE_D
    static int attributeList[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
#else
    static int attributeList[] = { GLX_RGBA, None, None };
#endif

    if (Tk_InitStubs(magicinterp, Tclmagic_InitStubsVersion, 0) == NULL) return FALSE;

    toglCurrent.window = Tk_MainWindow(magicinterp);
    if (toglCurrent.window == NULL)
    {
    	 TxError("No Top-Level Tk window available. . . is Tk running?\n");
	 return FALSE;
    }

    toglCurrent.windowid = Tk_WindowId(toglCurrent.window);
    grXdpy = Tk_Display(toglCurrent.window);
    toglCurrent.depth = Tk_Depth(toglCurrent.window);

    grXscrn = DefaultScreen(grXdpy);

    grVisualInfo = glXChooseVisual(grXdpy, grXscrn, attributeList);

    if (!grVisualInfo)
    {
	/* Try for a double-buffered configuration */
#ifdef THREE_D
	attributeList[1] = None;
#else
	attributeList[1] = GLX_DOUBLEBUFFER;
#endif
	grVisualInfo = glXChooseVisual(grXdpy, grXscrn, attributeList);
	if (!grVisualInfo)
	{
	    TxError("No suitable visual!\n");
	    return FALSE;
	}
    }
    grXscrn = grVisualInfo->screen;
    toglCurrent.depth = grVisualInfo->depth;

    /* glXCreateContext() 4th argument:				*/
    /* TRUE = Direct rendering, FALSE = Indirect rendering 	*/

    /* Direct rendering may not be able to deal with pixmaps. 	*/
    /* Normally, magic should compile with OpenGL framebuffer	*/
    /* backing store and pbuffer offscreen rendering.  However,	*/
    /* if X11_BACKING_STORE is selected then force indirect	*/
    /* rendering.  This can be avoided if the backing store	*/
    /* functions sync between OpenGL and X11;  this has not	*/
    /* been done yet.						*/

    /* The CAIRO_OFFSCREEN_RENDER compile-time option uses	*/
    /* Cairo to do the off-screen rendering, which allows	*/
    /* OpenGL to run in direct-rendering mode only.  To do:	*/
    /* determine from OpenGL attributes if indirect rendering	*/
    /* is allowed, and handle automatically.			*/

#ifdef X11_BACKING_STORE
    grXcontext = glXCreateContext(grXdpy, grVisualInfo, NULL, GL_FALSE);
#else
    grXcontext = glXCreateContext(grXdpy, grVisualInfo, NULL, GL_TRUE);
#endif

    /* Basic GL parameters */

    glLineWidth(1.0);
    glShadeModel (GL_FLAT);
    glPixelStorei(GL_PACK_LSB_FIRST, TRUE);

    /* OpenGL sets its own names for colormap and dstyle file types */
    grCMapType = "OpenGL";
    grDStyleType = "OpenGL";

    /* Globally-accessed variables */
    grNumBitPlanes = toglCurrent.depth;
    grBitPlaneMask = (1 << toglCurrent.depth) - 1;

    HashInit(&grTOGLWindowTable,8,HT_WORDKEYS);

    return grTkLoadFont();
}

/*---------------------------------------------------------
 * GrTOGLClose:
 *
 * Results:	None.
 *
 * Side Effects:
 *---------------------------------------------------------
 */

void
GrTOGLClose ()
{
    if (grXdpy == NULL) return;
    if (grVisualInfo != NULL) XFree(grVisualInfo);

    grTkFreeFonts();

    /* Pop down Tk window but let Tcl/Tk */
    /* do XCloseDisplay()		 */
}


/*---------------------------------------------------------
 * GrTOGLFlush:
 * 	Flush output to display.
 *
 *	Flushing is done automatically the next time input is read,
 *	so this procedure should not be used very often.
 *
 * Results:	None.
 *
 * Side Effects:    None.
 *---------------------------------------------------------
 */

void
GrTOGLFlush ()
{
    GR_TOGL_FLUSH_BATCH();
    glFlush();
}

/*
 *---------------------------------------------------------
 */

static GLXPbuffer pbuffer = None;

#define glTransYs(n) (DisplayHeight(grXdpy, grXscrn)-(n))

/*
 *---------------------------------------------------------
 * Set the OpenGL viewport (projection matrix) for a window
 *---------------------------------------------------------
 */

void
toglSetProjection(llx, lly, width, height)
    int llx, lly, width, height;
{
    if (toglCurrent.mw->w_flags & WIND_OFFSCREEN)
    {
	int count = 0;
	int PBattrib[] = {
	    GLX_PBUFFER_WIDTH, width,
	    GLX_PBUFFER_HEIGHT, height,
	    GLX_LARGEST_PBUFFER, False,
	    None
	};
	GLXFBConfig *config;

	if (pbuffer != None) glXDestroyPbuffer(grXdpy, pbuffer);
	config = glXGetFBConfigs(grXdpy, grXscrn, &count);
	if (config != NULL && count != 0)
	{
	    pbuffer = glXCreatePbuffer(grXdpy, config[0], PBattrib);
	    glXMakeCurrent(grXdpy, (GLXDrawable)pbuffer, grXcontext);
	}
	if (config != NULL) XFree(config);
    }
    else
	glXMakeCurrent(grXdpy, (GLXDrawable)toglCurrent.windowid, grXcontext);

#ifndef OGL_SERVER_SIDE_ONLY
    /* For batch-processing lines and rectangles */
    glEnableClientState(GL_VERTEX_ARRAY);
#endif

    /* Because this tends to result in thick lines, it has been moved	*/
    /* the line drawing routine so it can be enabled for individual	*/
    /* lines.								*/
    /* glEnable(GL_LINE_SMOOTH); */

    /* Force draw to front buffer (in case of double-buffered config) */
    glDrawBuffer(GL_FRONT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glViewport((GLsizei)llx, (GLsizei)lly, (GLsizei) width, (GLsizei) height);

    /* scale to fit window */

#ifdef OGL_INVERT_Y
    glScalef(1.0 / (float)(width >> 1), -1.0 / (float)(height >> 1), 1.0);
#else
    glScalef(1.0 / (float)(width >> 1), 1.0 / (float)(height >> 1), 1.0);
#endif

    /* magic origin maps to window center; move to window origin */

    glTranslated(-(GLsizei)(width >> 1), -(GLsizei)(height >> 1), 0);

    if (toglCurrent.mw->w_flags & WIND_OFFSCREEN)
	glTranslatef((float)0.5, (float)0.5, 0);

    /* Remaining transformations are done on the modelview matrix */

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}


/*
 * ---------------------------------------------------------------------------
 *
 * TOGLEventProc ---
 *
 *	Tk Event Handler
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Calls functions in response to X11 events.
 *
 * ---------------------------------------------------------------------------
 */

void
TOGLEventProc(clientData, xevent)
    ClientData clientData;
    XEvent *xevent;
{
    TxInputEvent *event;
    HashEntry	*entry;
    Tk_Window tkwind = (Tk_Window)clientData;
    Window wind;
    MagWindow *mw;
    unsigned char LocRedirect = TxInputRedirect;

    XKeyPressedEvent *KeyPressedEvent = (XKeyPressedEvent *) xevent;
    KeySym keysym;
    int nbytes;

    /* Keys and Buttons:  Determine expansion of macros or redirect
     * keys to the terminal or console.
     */

    switch (xevent->type)
    {
	case ButtonPress:
	    {
		XButtonEvent *ButtonEvent = (XButtonEvent *) xevent;
		int txbutton;

		switch (ButtonEvent->button) {
		case Button1:
		    txbutton = TX_LEFT_BUTTON;
		    keysym = XK_Pointer_Button1;
		    break;
		case Button2:
		    txbutton = TX_MIDDLE_BUTTON;
		    keysym = XK_Pointer_Button2;
		    break;
		case Button3:
		    txbutton = TX_RIGHT_BUTTON;
		    keysym = XK_Pointer_Button3;
		    break;
		case Button4:
		    txbutton = TX_BUTTON_4;
		    keysym = XK_Pointer_Button4;
		    break;
		case Button5:
		    txbutton = TX_BUTTON_5;
		    keysym = XK_Pointer_Button5;
		    break;
		}
		nbytes = 0;

		entry = HashLookOnly(&grTOGLWindowTable, (char *)tkwind);
		mw = (entry)?(MagWindow *)HashGetValue(entry):0;

		if (mw && (mw->w_flags & WIND_SCROLLBARS))
		    if (WindButtonInFrame(mw, ButtonEvent->x,
				grXtransY(mw, ButtonEvent->y),
				txbutton))
			break;

		goto keys_and_buttons;
	    }
	    break;
	case KeyPress:
	    {
		int keywstate, keymod, modifier, idx, idxmax;
		char inChar[10];
		Tcl_Channel outChannel = Tcl_GetStdChannel(TCL_STDOUT);

		nbytes = XLookupString(KeyPressedEvent, inChar, sizeof(inChar),
			&keysym, NULL);

		if (IsModifierKey(keysym)) break;	/* Don't handle modifiers */

		entry = HashLookOnly(&grTOGLWindowTable, (char *)tkwind);
		mw = (entry)?(MagWindow *)HashGetValue(entry):0;

keys_and_buttons:

		keymod = (LockMask | ControlMask | ShiftMask)
				& KeyPressedEvent->state;
#ifdef __APPLE__
		if (KeyPressedEvent->state & (Mod1Mask | Mod2Mask |
				Mod3Mask | Mod4Mask | Mod5Mask))
		    keymod |= Mod1Mask;
#else
		keymod |= (Mod1Mask & KeyPressedEvent->state);
#endif
		modifier = keymod;

		if (nbytes == 0)	/* No ASCII equivalent */
		{
		    keywstate = (keymod << 16) | (keysym & 0xffff);
		}
		else if (!strncmp(XKeysymToString(keysym), "KP_", 3))
		{
		    /* keypad key (special case---would like to		*/
		    /* differentiate between shift-KP-# and # itself)	*/
		    keymod &= ~ShiftMask;
		    keywstate = (keymod << 16) | (keysym & 0xffff);
		    nbytes = 0;
		}
		else			/* ASCII-valued character */
		{
		    /* Remove single modifier (Control or Shift) */
		    if (!(keymod & (LockMask | Mod1Mask))) {
			if (!(keymod & ControlMask))
			    keymod &= ~ShiftMask;
			else if (!(keymod & ShiftMask))
			    keymod &= ~ControlMask;
		    }
		}

		idxmax = (nbytes == 0) ? 1 : nbytes;
		for (idx = 0; idx < idxmax; idx++)
		{
		    if (nbytes > 0 && inChar[idx] == 3)	/* Ctrl-C interrupt */
		    {
			if (SigInterruptPending)
			    MainExit(0);	/* double Ctrl-C */
			else
			    sigOnInterrupt(0);	/* Set InterruptPending */
			break;
		    }
		    else if (nbytes > 0)
		    {
			if ((keymod & ControlMask) && (inChar[idx] < 32))
			    inChar[idx] += 'A' - 1;

			keywstate = (keymod << 16) | ((int)inChar[idx] & 0xff);
		    }

		    /* Allow buttons to bypass the console and be	*/
		    /* treated as macros.				*/

		    if (LocRedirect == TX_INPUT_REDIRECTED)
		    {
			switch (keysym)
			{
			    case XK_Pointer_Button1:
			    case XK_Pointer_Button2:
			    case XK_Pointer_Button3:
			    case XK_Pointer_Button4:
			    case XK_Pointer_Button5:
				LocRedirect = TX_INPUT_NORMAL;
				break;
			}
		    }

		    if ((LocRedirect == TX_INPUT_REDIRECTED) && TxTkConsole)
		    {
#if TCL_MAJOR_VERSION < 9
			Tcl_SavedResult state;
#else
			Tcl_InterpState state;
#endif
			static char outstr[] = "::tkcon::Insert .text \"x\" ";

			switch (keysym)
			{
			    case XK_Return:
			        TxSetPoint(KeyPressedEvent->x,
					grXtransY(mw, KeyPressedEvent->y),
					mw->w_wid);
				TxInputRedirect = TX_INPUT_PROCESSING;
				Tcl_EvalEx(consoleinterp, "::tkcon::Eval .text",
						19, 0);
				TxInputRedirect = TX_INPUT_NORMAL;
				TxSetPrompt('%');

#if TCL_MAJOR_VERSION < 9
				Tcl_SaveResult(magicinterp, &state);
#else
				state = Tcl_SaveInterpState(magicinterp, TCL_OK);
#endif
				Tcl_EvalEx(magicinterp, "history event 0", 15, 0);
			        MacroDefine(mw->w_client, (int)'.',
					Tcl_GetStringResult(magicinterp), NULL,
					FALSE);
#if TCL_MAJOR_VERSION < 9
				Tcl_RestoreResult(magicinterp, &state);
#else
				Tcl_RestoreInterpState(magicinterp, state);
#endif
				break;
			    case XK_Up:
				Tcl_EvalEx(consoleinterp, "::tkcon::Event -1",
					17, 0);
				break;
			    case XK_Down:
				Tcl_EvalEx(consoleinterp, "::tkcon::Event 1",
					16, 0);
				break;
			    case XK_Left:
				Tcl_EvalEx(consoleinterp, ".text mark set insert "
					"insert-1c ; .text see insert", 50, 0);
				break;
			    case XK_Right:
				Tcl_EvalEx(consoleinterp, ".text mark set insert "
					"insert+1c ; .text see insert", 50, 0);
				break;
			    case XK_BackSpace: case XK_Delete:
				Tcl_EvalEx(consoleinterp, ".text delete insert-1c ;"
					".text see insert", 40, 0);
				break;
			    case XK_quotedbl: case XK_backslash: case XK_bracketleft:
				outstr[23] = '\\';
				outstr[24] = inChar[idx];
				outstr[25] = '\"';
				Tcl_EvalEx(consoleinterp, outstr, 26, 0);
				outstr[24] = '\"';
				outstr[25] = '\0';
				/* fall through */
			    default:
				/* Handle Ctrl-u like tkcon:  Delete entire command */
				if ((keysym == XK_u) && (modifier == ControlMask))
				{
				    Tcl_EvalEx(consoleinterp, ".text delete limit end",
						22, 0);
				}
				else
				{
				    outstr[23] = inChar[idx];
				    Tcl_EvalEx(consoleinterp, outstr, 25, 0);
				}
				break;
			}
		    }
		    else if (LocRedirect == TX_INPUT_REDIRECTED) {
			int tl;
			if (TxBuffer == NULL)
			{
			    TxBuffer = Tcl_Alloc(2);
			    *TxBuffer = '\0';
			    tl = 0;
			}
			else
			{
			    tl = strlen(TxBuffer);
			    TxBuffer = Tcl_Realloc(TxBuffer, tl + 2);
			}
			if (keysym == XK_BackSpace || keysym == XK_Delete)
			{
			    if (tl >= 0)
			    {
				if (tl > 0)
				{
				    *(TxBuffer + tl - 1) = '\0';
				    TxPrintf("\b");
				}
				TxPrintf(" \b");
				TxFlushOut();
			    }
			}
			else if (keysym == XK_Return)
			{
			    *(TxBuffer + tl) = '\n';
			    *(TxBuffer + tl + 1) = '\0';
			    if (tl != 0) MacroDefine(mw->w_client,
					XK_period, TxBuffer, NULL, FALSE);
			    TxInputRedirect = TX_INPUT_NORMAL;
			    TxSetPoint(KeyPressedEvent->x,
					grXtransY(mw, KeyPressedEvent->y),
					mw->w_wid);
			    TxPrintf("\n");
			    TxFlushOut();
			    Tcl_NotifyChannel(Tcl_GetStdChannel(TCL_STDIN),
					TCL_READABLE);
			}
			else
			{
			    *(TxBuffer + tl) = *(inChar + idx);
			    *(TxBuffer + tl + 1) = '\0';
			    TxPrintf("%c", *(inChar + idx));
			    TxFlushOut();
			}
		    }
		    else
		    {
			bool iMacro;
			char *macroDef;

			macroDef = MacroRetrieve(mw->w_client, keywstate, &iMacro);

			/* Special handling:  An imacro beginning with ':'	*/
			/* sets the prompt to ':' and moves to the next char.	*/

			if (macroDef != NULL && *macroDef == ':' && iMacro)
			{
			    if (TxTkConsole)
				TxSetPrompt(':');
			    else
			    {
				TxPrintf("\b\b: ");
				TxFlushOut();
			    }
			    memmove(macroDef, macroDef + 1, strlen(macroDef + 1) + 1);
			}

			macroDef = MacroSubstitute(macroDef, "%W", Tk_PathName(tkwind));

			if (macroDef == NULL)
			{
			    if (keysym != XK_Return)
			    {
				char *vis = MacroName(keywstate);
				TxError("Unknown macro or short command: '%s'\n", vis);

				freeMagic(vis);
			    }
			    /* Print Carriage Return & Put back Tcl/Tk prompt */
			    TxParseString("");
			}
			else
			{
			    int sl = strlen(macroDef);

			    if (iMacro)
			    {
				/* Echo macro to interpreter, then redirect keys */

				if (TxTkConsole)
				{
				    char *outstring = Tcl_Alloc(sl + 20);
				    sprintf(outstring, ".text insert end \"%s\"",
						macroDef);
				    Tcl_EvalEx(consoleinterp, outstring, -1, 0);
				    Tcl_Free(outstring);
				}
				else
				{
				    TxBuffer = Tcl_Alloc(sl + 1);
				    strcpy(TxBuffer, macroDef);
				    TxPrintf("%s", macroDef);
				    TxFlushOut();
				}
				TxInputRedirect = TX_INPUT_REDIRECTED;
			    }
			    else
			    {
				/* TxParseString is defined by tcltk/tclmagic.c
				 * and calls Tcl_Eval()
				 */

				TxSetPoint(KeyPressedEvent->x,
					grXtransY(mw, KeyPressedEvent->y),
					mw->w_wid);
				TxParseString(macroDef);
			    }
			    freeMagic(macroDef);
			}
		    }
		}
	    }
	    break;
	case ConfigureNotify:
	    {
		XConfigureEvent *ConfigureEvent = (XConfigureEvent*) xevent;
		Rect	screenRect;
		int width, height;
		bool result, need_resize;

		width = ConfigureEvent->width;
		height = ConfigureEvent->height;

		entry = HashLookOnly(&grTOGLWindowTable, (char *)tkwind);
		mw = (entry)?(MagWindow *)HashGetValue(entry):0;

		screenRect.r_xbot = ConfigureEvent->x;
            	screenRect.r_xtop = ConfigureEvent->x + width;
            	screenRect.r_ytop = glTransYs(ConfigureEvent->y);
            	screenRect.r_ybot = glTransYs(ConfigureEvent->y + height);

		need_resize = (screenRect.r_xbot != mw->w_screenArea.r_xbot ||
			screenRect.r_xtop != mw->w_screenArea.r_xtop ||
			screenRect.r_ybot != mw->w_screenArea.r_ybot ||
			screenRect.r_ytop != mw->w_screenArea.r_ytop);

		/* Redraw the window */

		WindReframe(mw, &screenRect, FALSE, FALSE);
		WindRedisplay(mw);
		if (need_resize) (*GrCreateBackingStorePtr)(mw);
            }
            break;
	case VisibilityNotify:
	    {
		XVisibilityEvent *VisEvent = (XVisibilityEvent*) xevent;

		entry = HashLookOnly(&grTOGLWindowTable, (char *)tkwind);
		mw = (entry)?(MagWindow *)HashGetValue(entry):0;

		switch(VisEvent->state)
		{
		    case VisibilityUnobscured:
			mw->w_flags &= ~WIND_OBSCURED;
			if (mw->w_backingStore == (ClientData)NULL)
			{
			    (*GrCreateBackingStorePtr)(mw);
			    if (mw->w_backingStore != (ClientData)NULL)
			    {
				WindAreaChanged(mw, &mw->w_allArea);
				WindUpdate();
			    }
			}
			break;
		    case VisibilityPartiallyObscured:
		    case VisibilityFullyObscured:
			mw->w_flags |= WIND_OBSCURED;
			break;
		}
	    }
	    break;
	case Expose:
	    {
		XExposeEvent *ExposeEvent = (XExposeEvent*) xevent;
		Rect screenRect;

		entry = HashLookOnly(&grTOGLWindowTable, (char *)tkwind);
		mw = (entry)?(MagWindow *)HashGetValue(entry):0;

		screenRect.r_xbot = ExposeEvent->x;
            	screenRect.r_xtop = ExposeEvent->x+ExposeEvent->width;
            	screenRect.r_ytop = mw->w_allArea.r_ytop-ExposeEvent->y;
            	screenRect.r_ybot = mw->w_allArea.r_ytop -
				(ExposeEvent->y + ExposeEvent->height);

		if (mw->w_backingStore != (ClientData)NULL)
		{
		    Rect surface;
		    (*GrLockPtr)(mw, FALSE);
		    (*GrGetBackingStorePtr)(mw, &screenRect);
		    (*GrUnlockPtr)(mw);
		    WindScreenToSurface(mw, &screenRect, &surface);
		    DBWHLRedrawPrepWindow(mw, &surface);
		    WindDrawBorder(mw, &screenRect);
		}
		else
		    WindAreaChanged(mw, &screenRect);
		WindUpdate();
            }
	    break;

	case MapNotify:
	case UnmapNotify:
	case DestroyNotify:	/* Do nothing */
            break;

	default:
	    TxError("Tk Event: Unknown (%d)\n", xevent->type);
	    TxFlush();
	    break;
    }
}



/* Set for on-screen display */

void
toglOnScreen()
{
    GrSetCMapPtr = GrTOGLSetCMap;
    GrFlushPtr = GrTOGLFlush;

    grSetSPatternPtr = grtoglSetSPattern;
    grDrawLinePtr = grtoglDrawLine;
    grSetWMandCPtr = grtoglSetWMandC;
    grFillRectPtr = grtoglFillRect;
    grSetStipplePtr = grtoglSetStipple;
    grSetLineStylePtr = grtoglSetLineStyle;
    grFillPolygonPtr = grtoglFillPolygon;
}

/*---------------------------------------------------------
 * oglSetDisplay:
 *	This routine sets the appropriate parameters so that
 *	Magic will work with the X display.
 *
 *      Under Xlib, all input events (mouse and keyboard) are
 *	sent to one queue which has to be polled to discover
 *	whether there is any input or not.  To fit the Magic
 *	interrupt-driven input model, a helper process is
 *	spawned which reads and blocks on the event queue,
 *	sending SIGIO's to Magic when it detects input.  The
 *	input read in the helper process is then sent to Magic
 *	via a communication pipe.
 *
 * Results:  success / fail
 *
 * Side Effects:	Sets up the pipe.
 *---------------------------------------------------------
 */

bool
oglSetDisplay (dispType, outFileName, mouseFileName)
    char *dispType;
    char *outFileName;
    char *mouseFileName;
{
    char *planecount;
    char *fullname;
    FILE* f;
    bool execFailed = FALSE;
    int x, y, width, height;

    WindPackageType = WIND_X_WINDOWS;  /* to be changed? */
    TxInputRedirect = TX_INPUT_NORMAL;

    grCursorType = "color";
    WindScrollBarWidth = 14;

    /* Set up the procedure values in the indirection table. */

    GrPixelCorrect = 0;

    GrLockPtr = GrTOGLLock;
    GrUnlockPtr = GrTOGLUnlock;
    GrInitPtr = GrTOGLInit;
    GrClosePtr = GrTOGLClose;
    GrSetCMapPtr = GrTOGLSetCMap;

    GrEnableTabletPtr = GrTOGLEnableTablet;
    GrDisableTabletPtr = GrTOGLDisableTablet;
    GrSetCursorPtr = GrTOGLSetCursor;
    GrTextSizePtr = GrTOGLTextSize;
    GrDrawGlyphPtr = GrTOGLDrawGlyph;
    GrReadPixelPtr = GrTOGLReadPixel;
    GrFlushPtr = GrTOGLFlush;

    GrCreateWindowPtr = GrTOGLCreate;
    GrDeleteWindowPtr = GrTOGLDelete;
    GrConfigureWindowPtr = GrTOGLConfigure;
    GrOverWindowPtr = GrTOGLRaise;
    GrUnderWindowPtr = GrTOGLLower;
    GrUpdateIconPtr = GrTOGLIconUpdate;
    GrEventPendingPtr = GrTOGLEventPending;
    GrWindowIdPtr = GrTOGLWindowId;
    GrWindowNamePtr = GrTkWindowName;		/* from grTkCommon.c */
    GrGetCursorPosPtr = grtoglGetCursorPos;
    GrGetCursorRootPosPtr = grtoglGetCursorRootPos;

    /* local indirections */
    grSetSPatternPtr = grtoglSetSPattern;
    grPutTextPtr = grtoglPutText;
#ifdef VECTOR_FONTS
    grFontTextPtr = grtoglFontText;
#endif
    grDefineCursorPtr = grTkDefineCursor;
    grFreeCursorPtr = grTkFreeCursors;
    GrBitBltPtr = GrTOGLBitBlt;
    grDrawGridPtr = grtoglDrawGrid;
    grDrawLinePtr = grtoglDrawLine;
    grSetWMandCPtr = grtoglSetWMandC;
    grFillRectPtr = grtoglFillRect;
    grSetStipplePtr = grtoglSetStipple;
    grSetLineStylePtr = grtoglSetLineStyle;
    grSetCharSizePtr = grtoglSetCharSize;
    grFillPolygonPtr = grtoglFillPolygon;

#ifdef X11_BACKING_STORE
    GrFreeBackingStorePtr = grtkFreeBackingStore;
    GrCreateBackingStorePtr = grtkCreateBackingStore;
    GrGetBackingStorePtr = grtkGetBackingStore;
    GrPutBackingStorePtr = grtkPutBackingStore;
    GrScrollBackingStorePtr = grtkScrollBackingStore;
#else
    GrFreeBackingStorePtr = grtoglFreeBackingStore;
    GrCreateBackingStorePtr = grtoglCreateBackingStore;
    GrGetBackingStorePtr = grtoglGetBackingStore;
    GrPutBackingStorePtr = grtoglPutBackingStore;
    GrScrollBackingStorePtr = grtoglScrollBackingStore;
#endif

    if (execFailed) {
	TxError("Execution failed!\n");
	return FALSE;
    }

    if(!GrTOGLInit()){
	return FALSE;
    };

    Tk_GetVRootGeometry(Tk_MainWindow(magicinterp), &x, &y, &width, &height);
    GrScreenRect.r_xbot = x;
    GrScreenRect.r_ybot = y;
    GrScreenRect.r_xtop = width + x;
    GrScreenRect.r_ytop = height + y;

    return Tk_MainWindow(magicinterp) ? TRUE : FALSE;
}

extern void MakeWindowCommand();

/*
 * ----------------------------------------------------------------------------
 *
 * GrTOGLCreate --
 *      Create a new window under the X window system.
 *	Bind X window to Magic Window w.
 *
 * Results:
 *	Success/Fail
 *
 * Side Effects:
 *      Window created, window ID send to Xhelper.
 *
 * ----------------------------------------------------------------------------
 */

bool
GrTOGLCreate(w, name)
    MagWindow *w;
    char *name;
{
    Tk_Window tkwind, tktop;
    Window wind;
    static int WindowNumber = 0;
    HashEntry	*entry;
    char	*windowplace;
    char	windowname[32];
    int		x      = w->w_frameArea.r_xbot;
    int		y      = glTransYs(w->w_frameArea.r_ytop);
    int		width  = w->w_frameArea.r_xtop - w->w_frameArea.r_xbot;
    int		height = w->w_frameArea.r_ytop - w->w_frameArea.r_ybot;
    unsigned long        attribmask = CWBackPixel | CWBorderPixel | CWColormap;
    XSetWindowAttributes grAttributes;

    WindSeparateRedisplay(w);

    sprintf(windowname, ".magic%d", WindowNumber + 1);
    if ((windowplace = XGetDefault(grXdpy, "magic", windowname)))
    {
	XParseGeometry(windowplace,&x,&y,
		(unsigned int *)&width,(unsigned int *)&height);
	w->w_frameArea.r_xbot = x;
	w->w_frameArea.r_xtop = x+width;
	w->w_frameArea.r_ytop = glTransYs(y);
	w->w_frameArea.r_ybot = glTransYs(y+height);
	WindReframe(w,&(w->w_frameArea),FALSE,FALSE);
    }

    grAttributes.colormap = XCreateColormap(grXdpy, RootWindow(grXdpy, grXscrn),
		grVisualInfo->visual, AllocNone);
    grAttributes.background_pixel = WhitePixel(grXdpy,grXscrn);
    grAttributes.border_pixel = BlackPixel(grXdpy,grXscrn);

    if ((tktop = Tk_MainWindow(magicinterp)))
    {
        if (!WindowNumber)
	{
	    /* To do: deal with grVisualInfo---destroy and recreate top	*/
	    /* frame if necessary					*/

	    if (Tk_WindowId(tktop) == 0)
	    {
		Tk_SetWindowVisual(tktop, grVisualInfo->visual,
			toglCurrent.depth, grAttributes.colormap);
	    }
	    else
	    {
	        /* The Top-level window has already been mapped.  We can't mess */
	        /* with it's visual.  If the title is "wish", we'll assume that */
	        /* nobody else is claiming it, and unmap it.		    	*/

	        if (!strcmp(Tk_Name(tktop), "wish")) Tk_UnmapWindow(tktop);
	    }
	}
    }
    else
        return 0;  /* failure */

    /* Last parameter "" indicates a top-level window in the space of	*/
    /* the parent.							*/

    if (name == NULL)
        tkwind = Tk_CreateWindowFromPath(magicinterp, tktop, windowname, "");
    else
        tkwind = Tk_CreateWindowFromPath(magicinterp, tktop, name, NULL);

    /* TxError("Creating window named \"%s\", tkwind = 0x%x\n",
		windowname, tkwind); TxFlush(); */

    if (tkwind != 0)
    {
	bool result;

	GrTOGLFlush();

	toglCurrent.window = tkwind;
	toglCurrent.mw = w;

	w->w_grdata = (ClientData) tkwind;

	entry = HashFind(&grTOGLWindowTable, (char *)tkwind);
	HashSetValue(entry,w);

	/* ensure that the visual is what we wanted, if possible to change */

	Tk_SetWindowVisual(tkwind, grVisualInfo->visual, toglCurrent.depth,
			grAttributes.colormap);

	/* map the window, if necessary */

	Tk_MapWindow(tkwind);

	/* use x, y, width, height to size and position the window */

	Tk_GeometryRequest(tkwind, width, height);
	/* Tk_MoveResizeWindow(tkwind, x, y, width, height); */

	wind = Tk_WindowId(tkwind);
	toglCurrent.windowid = wind;
	glXMakeCurrent(grXdpy, (GLXDrawable)wind, grXcontext);

        Tk_DefineCursor(tkwind, toglCurrent.cursor);
	GrTOGLIconUpdate(w, w->w_caption);

	WindowNumber++;

	/* execute all Tk events up to current */

	while (Tcl_DoOneEvent(TCL_DONT_WAIT) != 0);

	/* set up Tk event handler to start processing */

	Tk_CreateEventHandler(tkwind, ExposureMask | StructureNotifyMask
		| ButtonPressMask | KeyPressMask | VisibilityChangeMask,
		 (Tk_EventProc *)TOGLEventProc, (ClientData) tkwind);

	/* set up commands to be passed expressly to this window */

	MakeWindowCommand((name == NULL) ? windowname : name, w);

	return (WindowNumber == 1) ? grtoglLoadFont() : 1;
    }
    else
    {
	TxError("Could not open new Tk window\n");
    }

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * GrTOGLDelete --
 *      Destroy a Tk/OpenGL window.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *      Window destroyed.
 *
 * ----------------------------------------------------------------------------
 */

void
GrTOGLDelete(w)
    MagWindow *w;
{
    Tk_Window xw;
    HashEntry	*entry;

    xw = (Tk_Window) w->w_grdata;
    entry = HashLookOnly(&grTOGLWindowTable, (char *)xw);
    HashSetValue(entry,NULL);

    Tcl_DeleteCommand(magicinterp, Tk_PathName(xw));
    Tk_DestroyWindow(xw);
}

/*
 * ----------------------------------------------------------------------------
 *
 * GrTOGLConfigure --
 *      Resize/ Move an existing X window.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Window reconfigured to w->w_frameArea.
 *
 * ----------------------------------------------------------------------------
 */

void
GrTOGLConfigure(w)
    MagWindow *w;
{
    if (w->w_flags & WIND_OFFSCREEN) return;

    Tk_MoveResizeWindow((Tk_Window)w->w_grdata,
	    w->w_frameArea.r_xbot, glTransYs(w->w_frameArea.r_ytop),
		w->w_frameArea.r_xtop - w->w_frameArea.r_xbot,
		    w->w_frameArea.r_ytop - w->w_frameArea.r_ybot);
}

/*
 * ----------------------------------------------------------------------------
 *
 * GrTOGLRaise --
 *      Raise a window to the top of the screen such that nothing
 *	obscures it.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Window raised.
 *
 * ----------------------------------------------------------------------------
 */

void
GrTOGLRaise(w)
    MagWindow *w;
{
    Tk_Window tkwind;

    if (w->w_flags & WIND_OFFSCREEN) return;

    tkwind = (Tk_Window)w->w_grdata;
    Tk_RestackWindow(tkwind, Above, NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * GrTOGLLower --
 *      Lower a window below all other Tk windows.
 *	obscures it.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Window lowered.
 *
 * ----------------------------------------------------------------------------
 */

void
GrTOGLLower(w)
    MagWindow *w;
{
    Tk_Window tkwind;

    if (w->w_flags & WIND_OFFSCREEN) return;

    tkwind = (Tk_Window)w->w_grdata;
    Tk_RestackWindow(tkwind, Below, NULL);
}


/*
 * ----------------------------------------------------------------------------
 *
 * GrTOGLLock --
 *      Lock a window and set global variables "toglCurrent.window"
 *	and "toglCurrent.mw" to reference the locked window.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *      Window locked.
 *
 * ----------------------------------------------------------------------------
 */

#ifdef CAIRO_OFFSCREEN_RENDER
extern void GrTCairoLock();
extern void GrTCairoUnlock();
extern void TCairoOffScreen();
#endif

void
GrTOGLLock(w, flag)
    MagWindow *w;
    bool flag;
{
    Window wind;

#ifdef CAIRO_OFFSCREEN_RENDER
    /* Use Cairo graphics for off-screen rendering */

    if ((w != GR_LOCK_SCREEN) && (w->w_flags & WIND_OFFSCREEN))
    {
	GrTCairoLock(w, flag);
	TCairoOffScreen();
	return;
    }
#endif

    grSimpleLock(w, flag);
    if ( w != GR_LOCK_SCREEN )
    {
	toglCurrent.mw = w;

	if (w->w_flags & WIND_OFFSCREEN)
	{
	    toglCurrent.window = (Tk_Window) NULL;
	    toglCurrent.windowid = (Pixmap) w->w_grdata;
	}
	else
	{
	    toglCurrent.window = (Tk_Window) w->w_grdata;
	    toglCurrent.windowid = Tk_WindowId(toglCurrent.window);
	}

	toglSetProjection(w->w_allArea.r_xbot, w->w_allArea.r_ybot,
			w->w_allArea.r_xtop - w->w_allArea.r_xbot,
			w->w_allArea.r_ytop - w->w_allArea.r_ybot);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * GrTOGLUnlock --
 *      Unlock a window, flushing stuff out to the display.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *      Window unlocked.
 *	Display update.
 *
 * ----------------------------------------------------------------------------
 */

void
GrTOGLUnlock(w)
    MagWindow *w;
{

#ifdef CAIRO_OFFSCREEN_RENDER
    /* Use Cairo graphics for off-screen rendering */

    if ((w != GR_LOCK_SCREEN) && (w->w_flags & WIND_OFFSCREEN))
    {
	GrTCairoUnlock(w);
	toglOnScreen();
	return;
    }
#endif

    GrTOGLFlush();

    if ((w != GR_LOCK_SCREEN) && (w->w_flags & WIND_OFFSCREEN))
    {
	GC grXcopyGC;
	XGCValues gcValues;
	unsigned char *pdata, *tdata;
	int i, j;

        Window root_return;
        int x_return, y_return;
        unsigned int pbwidth, pbheight, wborder, depth;

	XGetGeometry(grXdpy, (Drawable)toglCurrent.windowid, &root_return,
		&x_return, &y_return, &pbwidth, &pbheight, &wborder, &depth);

	pdata = (unsigned char *)mallocMagic((pbwidth * pbheight * 3)
			* sizeof(unsigned int));

	/* In offscreen-rendering mode, copy Pbuffer back to window */
	glReadBuffer(GL_FRONT);
	glReadPixels(0, 0, pbwidth, pbheight, GL_RGB, GL_UNSIGNED_BYTE, pdata);

        gcValues.graphics_exposures = FALSE;
        grXcopyGC = XCreateGC(grXdpy, (Drawable)toglCurrent.windowid,
		GCGraphicsExposures, &gcValues);

	/* This is very slow, but the only way I've found to copy data	*/
	/* from a Pbuffer into a Pixmap.  It is only used to make the	*/
	/* icon images for the toolbar, so it does not need to be	*/
	/* efficient.							*/

	tdata = pdata;
	for (i = 0; i < pbwidth; i++) {
	    for (j = 0; j < pbheight; j++)
	    {
		unsigned long pcolor;
		pcolor = *tdata++;
		pcolor <<= 8;
		pcolor |= *tdata++;
		pcolor <<= 8;
		pcolor |= *tdata++;
		XSetForeground(grXdpy, grXcopyGC, pcolor);
		XDrawPoint(grXdpy, (Drawable)toglCurrent.windowid, grXcopyGC,
			pbwidth - i - 1, j);
	    }
	}

	freeMagic(pdata);
	XFreeGC(grXdpy, grXcopyGC);
    }
    grSimpleUnlock(w);
}



/*
 *-------------------------------------------------------------------------
 * GrTOGLEventPending --
 *	check for pending graphics events.
 *      Here we use the X11 check for window events, because Tcl/Tk doesn't
 *      allows peeking into its event queue without executing whatever is
 *      in the queue.
 *
 * Results:
 *	TRUE if an event is waiting in the event queue.
 *
 * Side effects:
 *	None, hopefully (put back the event!)
 *
 *-------------------------------------------------------------------------
 */

bool
GrTOGLEventPending()
{
   Window       wind = toglCurrent.windowid;
   XEvent       genEvent;
   bool         retval;

   XSync(grXdpy, FALSE); /* Necessary, or it won't catch mouse/key events */
   retval = XCheckWindowEvent(grXdpy, wind, ExposureMask
                | StructureNotifyMask | ButtonPressMask
                | KeyPressMask, &genEvent);
   if (retval) XPutBackEvent(grXdpy, &genEvent);
   return retval;
}

/*
 *-------------------------------------------------------------------------
 *
 * GrTOGLIconUpdate -- updates the icon text with the window script
 *
 * Results: none
 *
 * Side Effects: changes the icon text
 *
 *-------------------------------------------------------------------------
 */

void
GrTOGLIconUpdate(w,text)		/* See Blt code */
    MagWindow	*w;
    char	*text;
{
    Tk_Window	tkwind;
    Window	wind;
    XClassHint	class;
    char	*brack;

    if (w->w_flags & WIND_OFFSCREEN) return;

    tkwind = (Tk_Window)(w->w_grdata);
    if (tkwind == NULL) {
	tkwind = Tk_MainWindow(magicinterp);
        if (tkwind == NULL) return;
    }
    wind = Tk_WindowId(tkwind);
    if (wind == 0) return;

    class.res_name = "magic";
    class.res_class = "magic";

    XSetClassHint( grXdpy, wind, &class);
    if (text)
    {
	if ((brack = strchr(text,'[')) && brack != text)
	{
	    brack--;
	    *brack = 0;
            XSetIconName(grXdpy,wind,text);
	    XStoreName(grXdpy,wind,text);
     	    *brack = ' ';
	    return;
	}
	if ((brack = strrchr(text,' '))) text = brack+1;
	XSetIconName(grXdpy,wind,text);
	XStoreName(grXdpy,wind,text);
    }
}

/*
 *-------------------------------------------------------------------------
 * GrTOGLWindowId --
 *	Get magic's ID number from the indicated MagWindow structure
 *
 * Results:
 *	The window ID number.
 *
 * Side effects:
 *	None.
 *
 *-------------------------------------------------------------------------
 */

int
GrTOGLWindowId(tkname)
    char *tkname;
{
    Tk_Window tkwind;
    MagWindow *mw;
    HashEntry *entry;
    int id = 0;

    tkwind = Tk_NameToWindow(magicinterp, tkname, Tk_MainWindow(magicinterp));
    if (tkwind != NULL)
    {
	entry = HashLookOnly(&grTOGLWindowTable, (char *)tkwind);
	mw = (entry) ? (MagWindow *)HashGetValue(entry) : 0;
	if (mw) id = mw->w_wid;
    }
    return id;
}
