/* 
 * tkMacWindowMgr.c --
 *
 *	Implements common window manager functions for the Macintosh.
 *
 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * SCCS: @(#) tkMacWindowMgr.c 1.59 97/11/20 18:56:39
 */

#include <Events.h>
#include <Dialogs.h>
#include <EPPC.h>
#include <Windows.h>
#include <ToolUtils.h>
#include <DiskInit.h>
#include <LowMem.h>
#include <Timer.h>
#include <Sound.h>

#include "tkInt.h"
#include "tkPort.h"
#include "tkMacInt.h"

#define TK_DEFAULT_ABOUT 128

/*
 * Declarations of global variables defined in this file.
 */

int tkMacAppInFront = true;		 /* Boolean variable for determining 
					  * if we are the frontmost app. */

/*
 *  Non-standard event types that can be passed to HandleEvent.
 * These are defined and used by Netscape's plugin architecture.
 */
#define getFocusEvent       (osEvt + 16)
#define loseFocusEvent      (osEvt + 17)
#define adjustCursorEvent   (osEvt + 18)

/*
 * Declarations of static variables used in this file.
 */

static int	 gEatButtonUp = 0;	 /* 1 if we need to eat the next
					  * up event */
static Tk_Window gGrabWinPtr = NULL;	 /* Current grab window, NULL if no grab. */
static Tk_Window gKeyboardWinPtr = NULL; /* Current keyboard grab window. */
static RgnHandle gDamageRgn = NULL;	 /* Damage region used for handling
					  * screen updates. */
/*
 * Forward declarations of procedures used in this file.
 */

static void	BringWindowForward _ANSI_ARGS_((WindowRef wRef));
static int 	CheckEventsAvail _ANSI_ARGS_((void));
static int 	GenerateActivateEvents _ANSI_ARGS_((EventRecord *eventPtr,
			Window window));
static int 	GenerateFocusEvent _ANSI_ARGS_((EventRecord *eventPtr,
			Window window));
static int	GenerateKeyEvent _ANSI_ARGS_((EventRecord *eventPtr,
			Window window));
static int	GenerateUpdateEvent _ANSI_ARGS_((EventRecord *eventPtr,
			Window window));
static void 	GenerateUpdates _ANSI_ARGS_((RgnHandle updateRgn,
			TkWindow *winPtr));
static int 	GeneratePollingEvents _ANSI_ARGS_((void));	
static int 	GeneratePollingEvents2 _ANSI_ARGS_((Window window));	
static OSErr	TellWindowDefProcToCalcRegions _ANSI_ARGS_((WindowRef wRef));
static int	WindowManagerMouse _ANSI_ARGS_((EventRecord *theEvent,
		    Window window));


/*
 *----------------------------------------------------------------------
 *
 * WindowManagerMouse --
 *
 *	This function determines if a button event is a "Window Manager"
 *	function or an event that should be passed to Tk's event
 *	queue.
 *
 * Results:
 *	Return true if event was placed on Tk's event queue.
 *
 * Side effects:
 *	Depends on where the button event occurs.
 *
 *----------------------------------------------------------------------
 */

static int
WindowManagerMouse(
    EventRecord *eventPtr,	/* Macintosh event record. */
    Window window)		/* Window pointer. */
{
    WindowRef whichWindow, frontWindow;
    Tk_Window tkwin;
    Point where, where2;
    int xOffset, yOffset;
    short windowPart;
				
    frontWindow = FrontWindow();

    /* 
     * The window manager only needs to know about mouse down events
     * and sometimes we need to "eat" the mouse up.  Otherwise, we
     * just pass the event to Tk.
     */
    if (eventPtr->what == mouseUp) {
	if (gEatButtonUp) {
	    gEatButtonUp = false;
	    return false;
	}
	return TkGenerateButtonEvent(eventPtr->where.h, eventPtr->where.v, 
		window, TkMacButtonKeyState());
    }

    windowPart = FindWindow(eventPtr->where, &whichWindow);
    tkwin = Tk_IdToWindow(tkDisplayList->display, window);
    switch (windowPart) {
	case inSysWindow:
	    SystemClick(eventPtr, (GrafPort *) whichWindow);
	    return false;
	case inDrag:
	    if (whichWindow != frontWindow) {
		if (!(eventPtr->modifiers & cmdKey)) {
		    if ((gGrabWinPtr != NULL) && (gGrabWinPtr != tkwin)) {
			SysBeep(1);
			return false;
		    }
		}
	    }

	    /*
	     * Call DragWindow to move the window around.  It will
	     * also eat the mouse up event.
	     */
	    SetPort((GrafPort *) whichWindow);
	    where.h = where.v = 0;
	    LocalToGlobal(&where);
	    DragWindow(whichWindow, eventPtr->where,
		    &tcl_macQdPtr->screenBits.bounds);
	    gEatButtonUp = false;
			
	    where2.h = where2.v = 0;
	    LocalToGlobal(&where2);
	    if (EqualPt(where, where2)) {
		return false;
	    }

	    TkMacWindowOffset(whichWindow, &xOffset, &yOffset);
	    where2.h -= xOffset;
	    where2.v -= yOffset;
	    TkGenWMConfigureEvent(tkwin, where2.h, where2.v,
		    -1, -1, TK_LOCATION_CHANGED);
	    return true;
	case inGrow:
	case inContent:
	    if (whichWindow != frontWindow ) {
		/*
		 * This click moves the window forward.  We don't want
		 * the corasponding mouse-up to be reported to the application
		 * or else it will mess up some Tk scripts.
		 */
		if ((gGrabWinPtr != NULL) && (gGrabWinPtr != tkwin)) {
		    SysBeep(1);
		    return false;
		}
		BringWindowForward(whichWindow);
		gEatButtonUp = true;
		SetPort((GrafPort *) whichWindow);
		return false;
	    } else {
		/*
		 * Generally the content region is the domain of Tk
		 * sub-windows.  However, one exception is the grow
		 * region.  A button down in this area will be handled
		 * by the window manager.  Note: this means that Tk 
		 * may not get button down events in this area!
		 */

		if (TkMacGrowToplevel(whichWindow, eventPtr->where) == true) {
		    return true;
		} else {
		    return TkGenerateButtonEvent(eventPtr->where.h,
			    eventPtr->where.v, window, TkMacButtonKeyState());
		}
	    }
	case inGoAway:
	    if (TrackGoAway( whichWindow, eventPtr->where)) {
		if (tkwin == NULL) {
		    return false;
		}
		TkGenWMDestroyEvent(tkwin);
		return true;
	    }
	    return false;
	case inMenuBar:
	    {
		int oldMode;
		KeyMap theKeys;

		GetKeys(theKeys);
		oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
		TkMacClearMenubarActive();
		TkMacHandleMenuSelect(MenuSelect(eventPtr->where),
			theKeys[1] & 4);
		Tcl_SetServiceMode(oldMode);
		return true; /* TODO: may not be on event on queue. */
	    }
	case inZoomIn:
	case inZoomOut:
	    if (TkMacZoomToplevel(whichWindow, eventPtr->where, windowPart)
		    == true) {
		return true;
	    } else {
		return false;
	    }
	default:
	    return false;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkAboutDlg --
 *
 *	Displays the default Tk About box.  This code uses Macintosh
 *	resources to define the content of the About Box.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void 
TkAboutDlg()
{
    DialogPtr aboutDlog;
    short itemHit = -9;
	
    aboutDlog = GetNewDialog(128, NULL, (void*)(-1));
	
    if (!aboutDlog) {
	return;
    }
	
    SelectWindow((WindowRef) aboutDlog);
	
    while (itemHit != 1) {
	ModalDialog( NULL, &itemHit);
    }
    DisposDialog(aboutDlog);
    aboutDlog = NULL;
	
    SelectWindow(FrontWindow());

    return;
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateUpdateEvent --
 *
 *	Given a Macintosh update event this function generates all the
 *	X update events needed by Tk.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.  
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

static int
GenerateUpdateEvent(
    EventRecord *eventPtr,	/* Incoming Mac event */
    Window window)		/* Root X window for event. */
{
    WindowRef macWindow;
    register TkWindow *winPtr;
	
    winPtr = (TkWindow *) Tk_IdToWindow(tkDisplayList->display, window);

    if (winPtr == NULL) {
	 return false;
    }
    
    if (gDamageRgn == NULL) {
	gDamageRgn = NewRgn();
    }

    /*
     * After the call to BeginUpdate the visable region (visRgn) of the 
     * window is equal to the intersection of the real visable region and
     * the update region for this event.  We use this region in all of our
     * calculations.
     */

    if (eventPtr->message != NULL) {
	macWindow = (WindowRef) TkMacGetDrawablePort(window);
	BeginUpdate(macWindow);
	GenerateUpdates(macWindow->visRgn, winPtr);
	EndUpdate(macWindow);
	return true;
    } else {
	/*
	 * This event didn't come from the system.  This might
	 * occur if we are running from inside of Netscape.
	 * In this we shouldn't call BeginUpdate as the vis region
	 * may be NULL.
	 */
	RgnHandle rgn;
	Rect bounds;
	
	rgn = NewRgn();
	TkMacWinBounds(winPtr, &bounds);
	RectRgn(rgn, &bounds);
	GenerateUpdates(rgn, winPtr);
	DisposeRgn(rgn);
	return true;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateUpdates --
 *
 *	Given a Macintosh update region and a Tk window this function
 *	geneates a X damage event for the window if it is within the
 *	update region.  The function will then recursivly have each
 *	damaged window generate damage events for its child windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

static void
GenerateUpdates(
    RgnHandle updateRgn,
    TkWindow *winPtr)
{
    TkWindow *childPtr;
    XEvent event;
    Rect bounds;

    TkMacWinBounds(winPtr, &bounds);
	
    if (bounds.top > (*updateRgn)->rgnBBox.bottom ||
	    (*updateRgn)->rgnBBox.top > bounds.bottom ||
	    bounds.left > (*updateRgn)->rgnBBox.right ||
	    (*updateRgn)->rgnBBox.left > bounds.right ||
	    !RectInRgn(&bounds, updateRgn)) {
	return;
    }

    event.xany.serial = Tk_Display(winPtr)->request;
    event.xany.send_event = false;
    event.xany.window = Tk_WindowId(winPtr);
    event.xany.display = Tk_Display(winPtr);
	
    event.type = Expose;

    /* 
     * Compute the bounding box of the area that the damage occured in.
     */

    /*
     * CopyRgn(TkMacVisableClipRgn(winPtr), rgn);
     * TODO: this call doesn't work doing resizes!!!
     */
    RectRgn(gDamageRgn, &bounds);
    SectRgn(gDamageRgn, updateRgn, gDamageRgn);
    OffsetRgn(gDamageRgn, -bounds.left, -bounds.top);
    event.xexpose.x = (**gDamageRgn).rgnBBox.left;
    event.xexpose.y = (**gDamageRgn).rgnBBox.top;
    event.xexpose.width = (**gDamageRgn).rgnBBox.right -
	(**gDamageRgn).rgnBBox.left;
    event.xexpose.height = (**gDamageRgn).rgnBBox.bottom -
	(**gDamageRgn).rgnBBox.top;
    event.xexpose.count = 0;
    
    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);

    /*
     * Generate updates for the children of this window
     */
     
    for (childPtr = winPtr->childList; childPtr != NULL;
				       childPtr = childPtr->nextPtr) {
	if (!Tk_IsMapped(childPtr) || Tk_IsTopLevel(childPtr)) {
	    continue;
	}

	GenerateUpdates(updateRgn, childPtr);
    }
    
    /*
     * Generate updates for any contained windows
     */

    if (Tk_IsContainer(winPtr)) {
	childPtr = TkpGetOtherWindow(winPtr);
	if (childPtr != NULL && Tk_IsMapped(childPtr)) {
	    GenerateUpdates(updateRgn, childPtr);
	}
	    
	/*
	 * NOTE: Here we should handle out of process embedding.
	 */
		    
    }	

    return;
}

/*
 *----------------------------------------------------------------------
 *
 * TkGenerateButtonEvent --
 *
 *	Given a global x & y position and the button key status this 
 *	procedure generates the appropiate X button event.  It also 
 *	handles the state changes needed to implement implicit grabs.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *	Grab state may also change.
 *
 *----------------------------------------------------------------------
 */

int
TkGenerateButtonEvent(
    int x,		/* X location of mouse */
    int y,		/* Y location of mouse */
    Window window,	/* X Window containing button event. */
    unsigned int state)	/* Button Key state suitable for X event */
{
    WindowRef whichWin, frontWin;
    Point where;
    Tk_Window tkwin;
    int dummy;

    /* 
     * ButtonDown events will always occur in the front
     * window.  ButtonUp events, however, may occur anywhere
     * on the screen.  ButtonUp events should only be sent
     * to Tk if in the front window or during an implicit grab.
     */
    where.h = x;
    where.v = y;
    FindWindow(where, &whichWin);
    frontWin = FrontWindow();
			
    if ((frontWin == NULL) || (frontWin != whichWin && gGrabWinPtr == NULL)) {
	return false;
    }

    tkwin = Tk_IdToWindow(tkDisplayList->display, window);
    
    GlobalToLocal(&where);
    if (tkwin != NULL) {
	tkwin = Tk_TopCoordsToWindow(tkwin, where.h, where.v, &dummy, &dummy);
    }

    Tk_UpdatePointer(tkwin, x,  y, state);

    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateActivateEvents --
 *
 *	Generate Activate/Deactivate events from a Macintosh Activate 
 *	event.  Note, the activate-on-foreground bit must be set in the 
 *	SIZE flags to ensure we get Activate/Deactivate in addition to 
 *	Susspend/Resume events.
 *
 * Results:
 *	Returns true if events were generate.
 *
 * Side effects:
 *	Queue events on Tk's event queue.
 *
 *----------------------------------------------------------------------
 */

static int
GenerateActivateEvents(
    EventRecord *eventPtr,	/* Incoming Mac event */
    Window window)		/* Root X window for event. */
{
    TkWindow *winPtr;
    
    winPtr = (TkWindow *) Tk_IdToWindow(tkDisplayList->display, window);
    if (winPtr == NULL || winPtr->window == None) {
	return false;
    }

    TkGenerateActivateEvents(winPtr,
	    (eventPtr->modifiers & activeFlag) ? 1 : 0);
    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * XSetInputFocus --
 *
 *	Change the focus window for the application.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
XSetInputFocus(
    Display* display,
    Window focus,
    int revert_to,
    Time time)
{
    /*
     * Don't need to do a thing.  Tk manages the focus for us.
     */
}

/*
 *----------------------------------------------------------------------
 *
 * TkpChangeFocus --
 *
 *	This procedure is a stub on the Mac because we always own the
 *	focus if we are a front most application.
 *
 * Results:
 *	The return value is the serial number of the command that
 *	changed the focus.  It may be needed by the caller to filter
 *	out focus change events that were queued before the command.
 *	If the procedure doesn't actually change the focus then
 *	it returns 0.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkpChangeFocus(winPtr, force)
    TkWindow *winPtr;		/* Window that is to receive the X focus. */
    int force;			/* Non-zero means claim the focus even
				 * if it didn't originally belong to
				 * topLevelPtr's application. */
{
    /*
     * We don't really need to do anything on the Mac.  Tk will
     * keep all this state for us.
     */

    if (winPtr->atts.override_redirect) {
	return 0;
    }

    /*
     * Remember the current serial number for the X server and issue
     * a dummy server request.  This marks the position at which we
     * changed the focus, so we can distinguish FocusIn and FocusOut
     * events on either side of the mark.
     */

    return NextRequest(winPtr->display);
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateFocusEvent --
 *
 *	Generate FocusIn/FocusOut events from a Macintosh Activate 
 *	event.  Note, the activate-on-foreground bit must be set in 
 *	the SIZE flags to ensure we get Activate/Deactivate in addition 
 *	to Susspend/Resume events.
 *
 * Results:
 *	Returns true if events were generate.
 *
 * Side effects:
 *	Queue events on Tk's event queue.
 *
 *----------------------------------------------------------------------
 */

static int
GenerateFocusEvent(
    EventRecord *eventPtr,	/* Incoming Mac event */
    Window window)		/* Root X window for event. */
{
    XEvent event;
    Tk_Window tkwin;
    
    tkwin = Tk_IdToWindow(tkDisplayList->display, window);
    if (tkwin == NULL) {
	return false;
    }

    /* 
     * Generate FocusIn and FocusOut events.  This event
     * is only sent to the toplevel window.
     */

    if (eventPtr->modifiers & activeFlag) {
	event.xany.type = FocusIn;
    } else {
	event.xany.type = FocusOut;
    }

    event.xany.serial = tkDisplayList->display->request;
    event.xany.send_event = False;
    event.xfocus.display = tkDisplayList->display;
    event.xfocus.window = window;
    event.xfocus.mode = NotifyNormal;
    event.xfocus.detail = NotifyDetailNone;

    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateKeyEvent --
 *
 *	Given Macintosh keyUp, keyDown & autoKey events this function
 *	generates the appropiate X key events.  The window that is passed
 *	should represent the frontmost window - which will recieve the
 *	event.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

static int
GenerateKeyEvent(
    EventRecord *eventPtr,	/* Incoming Mac event */
    Window window)		/* Root X window for event. */
{
    Point where;
    Tk_Window tkwin;
    XEvent event;

    /*
     * The focus must be in the FrontWindow on the Macintosh.
     * We then query Tk to determine the exact Tk window
     * that owns the focus.
     */

    tkwin = Tk_IdToWindow(tkDisplayList->display, window);
    tkwin = (Tk_Window) ((TkWindow *) tkwin)->dispPtr->focusPtr;
    if (tkwin == NULL) {
	return false;
    }

    where.v = eventPtr->where.v;
    where.h = eventPtr->where.h;

    event.xany.send_event = False;
    event.xkey.same_screen = true;
    event.xkey.subwindow = None;
    event.xkey.time = TkpGetMS();

    event.xkey.x_root = where.h;
    event.xkey.y_root = where.v;
    GlobalToLocal(&where);
    Tk_TopCoordsToWindow(tkwin, where.h, where.v, 
	    &event.xkey.x, &event.xkey.y);
    event.xkey.keycode = eventPtr->message;

    event.xany.serial = Tk_Display(tkwin)->request;
    event.xkey.window = Tk_WindowId(tkwin);
    event.xkey.display = Tk_Display(tkwin);
    event.xkey.root = XRootWindow(Tk_Display(tkwin), 0);
    event.xkey.state = TkMacButtonKeyState();

    if (eventPtr->what == keyDown) {
	event.xany.type = KeyPress;
	Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
    } else if (eventPtr->what == keyUp) {
	event.xany.type = KeyRelease;
	Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
    } else {
	/*
	 * Autokey events send multiple XKey events.
	 *
	 * Note: the last KeyRelease will always be missed with
	 * this scheme.  However, most Tk scripts don't look for
	 * KeyUp events so we should be OK.
	 */
	event.xany.type = KeyRelease;
	Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
	event.xany.type = KeyPress;
	Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
    }
    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * GeneratePollingEvents --
 *
 *	This function polls the mouse position and generates X Motion,
 *	Enter & Leave events.  The cursor is also updated at this
 *	time.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.  
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *	The cursor may be changed.
 *
 *----------------------------------------------------------------------
 */

static int
GeneratePollingEvents()
{
    Tk_Window tkwin, rootwin;
    Window window;
    WindowRef whichwindow, frontWin;
    Point whereLocal, whereGlobal;
    Boolean inContentRgn;
    short part;
    int local_x, local_y;
    int generatedEvents = false;
    
    /*
     * First we get the current mouse position and determine
     * what Tk window the mouse is over (if any).
     */
    frontWin = FrontWindow();
    if (frontWin == NULL) {
	return false;
    }
    SetPort((GrafPort *) frontWin);
   
    GetMouse(&whereLocal);
    whereGlobal = whereLocal;
    LocalToGlobal(&whereGlobal);
	
    part = FindWindow(whereGlobal, &whichwindow);
    inContentRgn = (part == inContent || part == inGrow);

    if ((frontWin != whichwindow) || !inContentRgn) {
	tkwin = NULL;
    } else {
	window = TkMacGetXWindow(whichwindow);
	rootwin = Tk_IdToWindow(tkDisplayList->display, window);
	if (rootwin == NULL) {
	    tkwin = NULL;
	} else {
	    tkwin = Tk_TopCoordsToWindow(rootwin, whereLocal.h, whereLocal.v, 
		    &local_x, &local_y);
	}
    }
    
    /*
     * The following call will generate the appropiate X events and
     * adjust any state that Tk must remember.
     */

    if ((tkwin == NULL) && (gGrabWinPtr != NULL)) {
	tkwin = gGrabWinPtr;
    }
    Tk_UpdatePointer(tkwin, whereGlobal.h,  whereGlobal.v,
	    TkMacButtonKeyState());

    /*
     * Finally, we make sure the proper cursor is installed.  The installation
     * is polled to 1) make our resize hack work, and 2) make sure we have the 
     * proper cursor even if someone else changed the cursor out from under
     * us.
     */
    if ((gGrabWinPtr == NULL) && (part == inGrow) && 
	    TkMacResizable((TkWindow *) tkwin) && 
	    (TkMacGetScrollbarGrowWindow((TkWindow *) tkwin) == NULL)) {
	TkMacInstallCursor(1);
    } else {
	TkMacInstallCursor(0);
    }

    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * GeneratePollingEvents2 --
 *
 *	This function polls the mouse position and generates X Motion,
 *	Enter & Leave events.  The cursor is also updated at this
 *	time.  NOTE: this version is for Netscape!!!
 *
 * Results:
 *	True if event(s) are generated - false otherwise.  
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *	The cursor may be changed.
 *
 *----------------------------------------------------------------------
 */

static int
GeneratePollingEvents2(
    Window window)
{
    Tk_Window tkwin, rootwin;
    WindowRef whichwindow, frontWin;
    Point whereLocal, whereGlobal;
    int local_x, local_y;
    int generatedEvents = false;
    Rect bounds;
    
    /*
     * First we get the current mouse position and determine
     * what Tk window the mouse is over (if any).
     */
    frontWin = FrontWindow();
    if (frontWin == NULL) {
	return false;
    }
    SetPort((GrafPort *) frontWin);
   
    GetMouse(&whereLocal);
    whereGlobal = whereLocal;
    LocalToGlobal(&whereGlobal);

    /*
     * Determine if we are in a Tk window or not.
     */
    whichwindow = (WindowRef) TkMacGetDrawablePort(window);
    if (whichwindow != frontWin) {
	tkwin = NULL;
    } else {
	rootwin = Tk_IdToWindow(tkDisplayList->display, window);
	TkMacWinBounds((TkWindow *) rootwin, &bounds);
	if (!PtInRect(whereLocal, &bounds)) {
	    tkwin = NULL;
	} else {
	    tkwin = Tk_TopCoordsToWindow(rootwin, whereLocal.h, whereLocal.v, 
		    &local_x, &local_y);
	}
    }

    /*
     * The following call will generate the appropiate X events and
     * adjust any state that Tk must remember.
     */

    if ((tkwin == NULL) && (gGrabWinPtr != NULL)) {
	tkwin = gGrabWinPtr;
    }
    Tk_UpdatePointer(tkwin, whereGlobal.h,  whereGlobal.v,
	    TkMacButtonKeyState());

    /*
     * Finally, we make sure the proper cursor is installed.  The installation
     * is polled to 1) make our resize hack work, and 2) make sure we have the 
     * proper cursor even if someone else changed the cursor out from under
     * us.
     */
    TkMacInstallCursor(0);

    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacButtonKeyState --
 *
 *	Returns the current state of the button & modifier keys.
 *
 * Results:
 *	A bitwise inclusive OR of a subset of the following:
 *	Button1Mask, ShiftMask, LockMask, ControlMask, Mod?Mask,
 *	Mod?Mask.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

unsigned int
TkMacButtonKeyState()
{
    unsigned int state = 0;
    KeyMap theKeys;

    if (Button() & !gEatButtonUp) {
	state |= Button1Mask;
    }

    GetKeys(theKeys);

    if (theKeys[1] & 2) {
	state |= LockMask;
    }

    if (theKeys[1] & 1) {
	state |= ShiftMask;
    }

    if (theKeys[1] & 8) {
	state |= ControlMask;
    }

    if (theKeys[1] & 32768) {
	state |= Mod1Mask;		/* command key */
    }

    if (theKeys[1] & 4) {
	state |= Mod2Mask;		/* option key */
    }

    return state;
}

/*
 *----------------------------------------------------------------------
 *
 * XGrabKeyboard --
 *
 *	Simulates a keyboard grab by setting the focus.
 *
 * Results:
 *	Always returns GrabSuccess.
 *
 * Side effects:
 *	Sets the keyboard focus to the specified window.
 *
 *----------------------------------------------------------------------
 */

int
XGrabKeyboard(
    Display* display,
    Window grab_window,
    Bool owner_events,
    int pointer_mode,
    int keyboard_mode,
    Time time)
{
    gKeyboardWinPtr = Tk_IdToWindow(display, grab_window);
    return GrabSuccess;
}

/*
 *----------------------------------------------------------------------
 *
 * XUngrabKeyboard --
 *
 *	Releases the simulated keyboard grab.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets the keyboard focus back to the value before the grab.
 *
 *----------------------------------------------------------------------
 */

void
XUngrabKeyboard(
    Display* display,
    Time time)
{
    gKeyboardWinPtr = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * XQueryPointer --
 *
 *	Check the current state of the mouse.  This is not a complete
 *	implementation of this function.  It only computes the root
 *	coordinates and the current mask.
 *
 * Results:
 *	Sets root_x_return, root_y_return, and mask_return.  Returns
 *	true on success.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

Bool
XQueryPointer(
    Display* display,
    Window w,
    Window* root_return,
    Window* child_return,
    int* root_x_return,
    int* root_y_return,
    int* win_x_return,
    int* win_y_return,
    unsigned int* mask_return)
{
    Point where;

    GetMouse(&where);
    LocalToGlobal(&where);
    *root_x_return = where.h;
    *root_y_return = where.v;
    *mask_return = TkMacButtonKeyState();    
    return True;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacGenerateTime --
 *
 *	Returns the total number of ticks from startup  This function
 *	is used to generate the time of generated X events.
 *
 * Results:
 *	Returns the current time (ticks from startup).
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

Time
TkMacGenerateTime()
{
    return (Time) LMGetTicks();
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacConvertEvent --
 *
 *	This function converts a Macintosh event into zero or more
 *	Tcl events.
 *
 * Results:
 *	Returns 1 if event added to Tcl queue, 0 otherwse.
 *
 * Side effects:
 *	May add events to Tcl's event queue.
 *
 *----------------------------------------------------------------------
 */

int
TkMacConvertEvent(
    EventRecord *eventPtr)
{
    WindowRef whichWindow;
    Window window;
    int eventFound = false;
    
    switch (eventPtr->what) {
	case nullEvent:
	case adjustCursorEvent:
	    if (GeneratePollingEvents()) {
		eventFound = true;
	    }
	    break;
	case updateEvt:
	    whichWindow = (WindowRef)eventPtr->message;	
	    window = TkMacGetXWindow(whichWindow);
	    if (GenerateUpdateEvent(eventPtr, window)) {
		eventFound = true;
	    }
	    break;
	case mouseDown:
	case mouseUp:
	    FindWindow(eventPtr->where, &whichWindow);
	    window = TkMacGetXWindow(whichWindow);
	    if (WindowManagerMouse(eventPtr, window)) {
		eventFound = true;
	    }
	    break;
	case autoKey:
	case keyDown:
	    /*
	     * Handle menu-key events here.  If it is *not*
	     * a menu key - just fall through to handle as a
	     * normal key event.
	     */
	    if ((eventPtr->modifiers & cmdKey) == cmdKey) {
		long menuResult;
		int oldMode;

		oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
		menuResult = MenuKey(eventPtr->message & charCodeMask);
		Tcl_SetServiceMode(oldMode);

		if (HiWord(menuResult) != 0) {
		    TkMacHandleMenuSelect(menuResult, false);
		    break;
		}
	    }
	case keyUp:
	    whichWindow = FrontWindow();
	    window = TkMacGetXWindow(whichWindow);
	    eventFound |= GenerateKeyEvent(eventPtr, window);
	    break;
	case activateEvt:
	    window = TkMacGetXWindow((WindowRef) eventPtr->message);
	    eventFound |= GenerateActivateEvents(eventPtr, window);
	    eventFound |= GenerateFocusEvent(eventPtr, window);
	    break;
	case getFocusEvent:
	    eventPtr->modifiers |= activeFlag;
	    window = TkMacGetXWindow((WindowRef) eventPtr->message);
	    eventFound |= GenerateFocusEvent(eventPtr, window);
	    break;
	case loseFocusEvent:
	    eventPtr->modifiers &= ~activeFlag;
	    window = TkMacGetXWindow((WindowRef) eventPtr->message);
	    eventFound |= GenerateFocusEvent(eventPtr, window);
	    break;
	case kHighLevelEvent:
	    TkMacDoHLEvent(eventPtr);
	    /* TODO: should return true if events were placed on event queue. */
	    break;
	case osEvt:
	    /*
	     * Do clipboard conversion.
	     */
	    switch ((eventPtr->message & osEvtMessageMask) >> 24) {
		case mouseMovedMessage:
		    if (GeneratePollingEvents()) {
			eventFound = true;
		    }
		    break;
		case suspendResumeMessage:
		    if (!(eventPtr->message & resumeFlag)) {
			TkSuspendClipboard();
		    }
		    tkMacAppInFront = (eventPtr->message & resumeFlag);
		    break;
	    }
	    break;
	case diskEvt:
	    /* 
	     * Disk insertion. 
	     */
	    if (HiWord(eventPtr->message) != noErr) {
		Point pt;
			
		DILoad();
		pt.v = pt.h = 120;	  /* parameter ignored in sys 7 */
		DIBadMount(pt, eventPtr->message);
		DIUnload();
	    }
	    break;
    }
    
    return eventFound;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacConvertTkEvent --
 *
 *	This function converts a Macintosh event into zero or more
 *	Tcl events.
 *
 * Results:
 *	Returns 1 if event added to Tcl queue, 0 otherwse.
 *
 * Side effects:
 *	May add events to Tcl's event queue.
 *
 *----------------------------------------------------------------------
 */

int
TkMacConvertTkEvent(
    EventRecord *eventPtr,
    Window window)
{
    int eventFound = false;
    Point where;
    
    switch (eventPtr->what) {
	case nullEvent:
	case adjustCursorEvent:
	    if (GeneratePollingEvents2(window)) {
		eventFound = true;
	    }
	    break;
	case updateEvt:
	    if (GenerateUpdateEvent(eventPtr, window)) {
		eventFound = true;
	    }
	    break;
	case mouseDown:
	case mouseUp:
	    GetMouse(&where);
	    LocalToGlobal(&where);
	    eventFound |= TkGenerateButtonEvent(where.h, where.v, 
		window, TkMacButtonKeyState());
	    break;
	case autoKey:
	case keyDown:
	    /*
	     * Handle menu-key events here.  If it is *not*
	     * a menu key - just fall through to handle as a
	     * normal key event.
	     */
	    if ((eventPtr->modifiers & cmdKey) == cmdKey) {
		long menuResult = MenuKey(eventPtr->message & charCodeMask);
		
		if (HiWord(menuResult) != 0) {
		    TkMacHandleMenuSelect(menuResult, false);
		    break;
		}
	    }
	case keyUp:
	    eventFound |= GenerateKeyEvent(eventPtr, window);
	    break;
	case activateEvt:
	    eventFound |= GenerateActivateEvents(eventPtr, window);
	    eventFound |= GenerateFocusEvent(eventPtr, window);
	    break;
	case getFocusEvent:
	    eventPtr->modifiers |= activeFlag;
	    eventFound |= GenerateFocusEvent(eventPtr, window);
	    break;
	case loseFocusEvent:
	    eventPtr->modifiers &= ~activeFlag;
	    eventFound |= GenerateFocusEvent(eventPtr, window);
	    break;
	case kHighLevelEvent:
	    TkMacDoHLEvent(eventPtr);
	    /* TODO: should return true if events were placed on event queue. */
	    break;
	case osEvt:
	    /*
	     * Do clipboard conversion.
	     */
	    switch ((eventPtr->message & osEvtMessageMask) >> 24) {
		case mouseMovedMessage:
		    if (GeneratePollingEvents2(window)) {
			eventFound = true;
		    }
		    break;
		case suspendResumeMessage:
		    if (!(eventPtr->message & resumeFlag)) {
			TkSuspendClipboard();
		    }
		    tkMacAppInFront = (eventPtr->message & resumeFlag);
		    break;
	    }
	    break;
	case diskEvt:
	    /* 
	     * Disk insertion. 
	     */
	    if (HiWord(eventPtr->message) != noErr) {
		Point pt;
			
		DILoad();
		pt.v = pt.h = 120;	  /* parameter ignored in sys 7 */
		DIBadMount(pt, eventPtr->message);
		DIUnload();
	    }
	    break;
    }
    
    return eventFound;
}

/*
 *----------------------------------------------------------------------
 *
 * CheckEventsAvail --
 *
 *	Checks to see if events are available on the Macintosh queue.
 *	This function looks for both queued events (eg. key & button)
 *	and generated events (update).
 *
 * Results:
 *	True is events exist, false otherwise.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CheckEventsAvail()
{
    QHdrPtr evPtr;
    WindowPeek macWinPtr;
    
    evPtr = GetEvQHdr();
    if (evPtr->qHead != NULL) {
	return true;
    }
    
    macWinPtr = (WindowPeek) FrontWindow();
    while (macWinPtr != NULL) {
	if (!EmptyRgn(macWinPtr->updateRgn)) {
	    return true;
	}
	macWinPtr = macWinPtr->nextWindow;
    }
    return false;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpSetCapture --
 *
 *	This function captures the mouse so that all future events
 *	will be reported to this window, even if the mouse is outside
 *	the window.  If the specified window is NULL, then the mouse
 *	is released. 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets the capture flag and captures the mouse.
 *
 *----------------------------------------------------------------------
 */

void
TkpSetCapture(
    TkWindow *winPtr)			/* Capture window, or NULL. */
{
    while ((winPtr != NULL) && !Tk_IsTopLevel(winPtr)) {
	winPtr = winPtr->parentPtr;
    }
    gGrabWinPtr = (Tk_Window) winPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacWindowOffset --
 *
 *	Determines the x and y offset from the orgin of the toplevel
 *	window dressing (the structure region, ie. title bar) and the
 *	orgin of the content area.
 *
 * Results:
 *	The x & y offset in pixels.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TkMacWindowOffset(
    WindowRef wRef,
    int *xOffset,
    int *yOffset)
{
    OSErr err = noErr;
    WindowPeek wPeek = (WindowPeek) wRef;
    RgnHandle strucRgn = wPeek->strucRgn;
    RgnHandle contRgn = wPeek->contRgn;
    Rect strucRect, contRect;

    if (!EmptyRgn(strucRgn) && !EmptyRgn(contRgn)) {
	strucRect = (**strucRgn).rgnBBox;
	contRect = (**contRgn).rgnBBox;
    } else {		
	/*
	 * The current window's regions are not up to date.
	 * Probably because the window isn't visable.  What we
	 * will do is save the old regions, have the window calculate
	 * what the regions should be, and then restore it self.
	 */
	strucRgn = NewRgn( );
	contRgn = NewRgn( );

	if (!strucRgn || !contRgn) {
	    err = MemError( );
	} else {
	    CopyRgn(wPeek->strucRgn, strucRgn);
	    CopyRgn(wPeek->contRgn, contRgn);

	    if (!(err = TellWindowDefProcToCalcRegions(wRef))) {
		strucRect = (**(wPeek->strucRgn)).rgnBBox;
		contRect = (**(wPeek->contRgn)).rgnBBox;
	    }

	    CopyRgn(strucRgn, wPeek->strucRgn);
	    CopyRgn(contRgn, wPeek->contRgn);
	}

	if (contRgn) {
	    DisposeRgn(contRgn);
	}
		
	if (strucRgn) {
	    DisposeRgn(strucRgn);
	}
    }

    if (!err) {
	*xOffset = contRect.left - strucRect.left;
	*yOffset = contRect.top - strucRect.top;
    } else {
	*xOffset = 0;
	*yOffset = 0;
    }

    return;
}

/*
 *----------------------------------------------------------------------
 *
 * TellWindowDefProcToCalcRegions --
 *
 *	Force a Macintosh window to recalculate it's content and
 *	structure regions.
 *
 * Results:
 *	An OS error.
 *
 * Side effects:
 *	The windows content and structure regions may be updated.
 *
 *----------------------------------------------------------------------
 */

static OSErr 
TellWindowDefProcToCalcRegions(
    WindowRef wRef)
{
    OSErr err = noErr;
    SInt8 hState;
    Handle wdef = ((WindowPeek) wRef)->windowDefProc;

    /*
     * Load and lock the window definition procedure for
     * the window.
     */
    hState = HGetState(wdef);
    if (!(err = MemError())) {
	LoadResource(wdef);
	if (!(err = ResError())) {
	    MoveHHi(wdef);
	    err = MemError();
	    if (err == memLockedErr) {
	        err = noErr;
	    } else if (!err) {
		HLock(wdef);
		err = MemError();
	    }
	}
    }
    
    /*
     * Assuming there are no errors we now call the window definition 
     * procedure to tell it to calculate the regions for the window.
     */

    if (err == noErr) {
 	(void) CallWindowDefProc((UniversalProcPtr) *wdef,
		GetWVariant(wRef), wRef, wCalcRgns, 0);

	HSetState(wdef, hState);
	if (!err) {
	     err = MemError();
	}
    }

    return err;
}

/*
 *----------------------------------------------------------------------
 *
 * BringWindowForward --
 *
 *	Bring this background window to the front.  We also set state
 *	so Tk thinks the button is currently up.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window is brought forward.
 *
 *----------------------------------------------------------------------
 */

static void 
BringWindowForward(
    WindowRef wRef)
{
    SelectWindow(wRef);
}

/*
 *----------------------------------------------------------------------
 *
 * TkpGetMS --
 *
 *	Return a relative time in milliseconds.  It doesn't matter
 *	when the epoch was.
 *
 * Results:
 *	Number of milliseconds.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

unsigned long
TkpGetMS()
{
    long long * int64Ptr;
    UnsignedWide micros;
    
    Microseconds(&micros);
    int64Ptr = (long long *) &micros;

    /*
     * We need 64 bit math to do this.  This is available in CW 11
     * and on.  Other's will need to use a different scheme.
     */

    *int64Ptr /= 1000;

    return (long) *int64Ptr;
}
