/* 
 * tkMacWindowMgr.c --
 *
 *	Implements common window manager functions for the Macintosh.
 *
 * Copyright (c) 1995-1996 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.36 97/01/17 16:00:18
 */

#include <Events.h>
#include <Dialogs.h>
#include <EPPC.h>
#include <Windows.h>
#include <ToolUtils.h>
#include <DiskInit.h>
#include <LowMem.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. */

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

static int	 gEatButtonUp = 0;	 /* 1 if we need to eat the next up event */
static int	 gCaptured = 0;		 /* 1 if mouse events outside of Tk 
				 	  * windows will be reported, else 0 */
static Tk_Window gGrabWinPtr = NULL;     /* Window that defines the top of the
				          * grab tree in a global grab. */
static Tk_Window gKeyboardWinPtr = NULL; /* Current keyboard grab window. */
static Point 	 gLastPointerPos;	 /* Last known position of mouse. */
static Tk_Window gLastWinPtr = NULL;     /* The last window the mouse was in */
static Tk_Window gRestrictWinPtr = NULL; /* Window to which all mouse
				          * events will be reported. */
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 *macEvent));
static int	GenerateKeyEvent _ANSI_ARGS_((EventRecord *macEvent));
static int	GenerateUpdateEvent _ANSI_ARGS_((EventRecord *macEvent));
static int 	GenerateEnterLeave _ANSI_ARGS_((Tk_Window tkwin,
			long x, int y));
static int 	GenerateMotion _ANSI_ARGS_((Tk_Window tkwin,
			Point whereLocal, Point whereGlobal));
static void 	GenerateUpdates _ANSI_ARGS_((RgnHandle updateRgn,
			TkWindow *winPtr));
static int 	GeneratePollingEvents _ANSI_ARGS_((void));	
static void	InitializeCrossingEvent _ANSI_ARGS_((XEvent *eventPtr,
			TkWindow *winPtr, long x, long y));
static OSErr	TellWindowDefProcToCalcRegions _ANSI_ARGS_((WindowRef wRef));
static void 	UpdateCursor _ANSI_ARGS_((TkWindow *winPtr,
			WindowRef whichwindow, Point whereLocal));
static int	WindowManagerMouse _ANSI_ARGS_((EventRecord *theEvent));


/*
 *----------------------------------------------------------------------
 *
 * 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(eventPtr)
    EventRecord *eventPtr;
{
    WindowRef whichWindow, frontWindow;
    Window window;
    Tk_Window tkwin;
    Point where, where2;
    int xOffset, yOffset;
    short windowPart;
				
    
    frontWindow = FrontWindow();

    /* 
     * The window manager only needs to know about mouse down events.
     */
    if (eventPtr->what == mouseUp) {
	if (gEatButtonUp) {
	    gEatButtonUp = false;
	    return false;
	}
	return TkGenerateButtonEvent(eventPtr->where.h, eventPtr->where.v, 
		TkMacButtonKeyState());
    }

    windowPart = FindWindow(eventPtr->where, &whichWindow);
    switch (windowPart) {
	case inSysWindow:
	    SystemClick(eventPtr, (GrafPort *) whichWindow);
	    return false;
	case inDrag:
	    if (whichWindow != frontWindow) {
		if (!(eventPtr->modifiers & cmdKey)) {
		    BringWindowForward(whichWindow);
		}
	    }
	    
	    SetPort((GrafPort *) whichWindow);
	    where.h = where.v = 0;
	    LocalToGlobal(&where);
	    DragWindow(whichWindow, eventPtr->where,
		    &qd.screenBits.bounds);
			
	    where2.h = where2.v = 0;
	    LocalToGlobal(&where2);
	    if (EqualPt(where, where2)) {
		return false;
	    }

	    window = TkMacGetXWindow(whichWindow);
	    tkwin = Tk_IdToWindow(tkDisplayList->display, window);
	    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 ) {
		BringWindowForward(whichWindow);
		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, TkMacButtonKeyState());
		}
	    }
	case inGoAway:
	    if (TrackGoAway( whichWindow, eventPtr->where)) {
		Window window;
		Tk_Window tkwin;

		window = TkMacGetXWindow(whichWindow);
		tkwin = Tk_IdToWindow(tkDisplayList->display, window);
		if (tkwin == NULL) {
		    return false;
		}
		TkGenWMDestroyEvent(tkwin);
		return true;
	    }
	    return false;
	case inMenuBar:
	    {
		KeyMap theKeys;

		GetKeys(theKeys);
		TkMacHandleMenuSelect(MenuSelect(eventPtr->where),
			theKeys[1] & 4);
		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(macEvent)
    EventRecord *macEvent;	/* Incoming Mac event */
{
    WindowRef macWindow = (WindowRef)macEvent->message;
    Window window;
    register TkWindow *winPtr;
	
    window = TkMacGetXWindow(macWindow);
    winPtr = (TkWindow *) Tk_IdToWindow(tkDisplayList->display, window);

    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 (winPtr != NULL) {
	BeginUpdate(macWindow);
	GenerateUpdates(macWindow->visRgn, winPtr);
	EndUpdate(macWindow);
	return true;
    }
	
    return false;
}

/*
 *----------------------------------------------------------------------
 *
 * 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(updateRgn, winPtr)
    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);

    for (childPtr = winPtr->childList; childPtr != NULL;
				       childPtr = childPtr->nextPtr) {
	if (!Tk_IsMapped(childPtr) || Tk_IsTopLevel(childPtr)) {
	    continue;
	}

	GenerateUpdates(updateRgn, childPtr);
    }

    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(x, y, state)
    int x;		/* X location of mouse */
    int y;		/* Y location of mouse */
    unsigned int state;	/* Button Key state suitable for X event */
{
    WindowRef whichWin, frontWin;
    Point where;
    Tk_Window tkwin;
    Window window;
    XEvent event;
    Rect bounds;

    /* 
     * 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 && gRestrictWinPtr == NULL)) {
	return false;
    }

    event.xany.send_event = False;
    event.xbutton.x_root = where.h;
    event.xbutton.y_root = where.v;
    event.xbutton.subwindow = None;
    event.xbutton.same_screen = true;

    GlobalToLocal(&where);
    window = TkMacGetXWindow(whichWin);
    tkwin = Tk_IdToWindow(tkDisplayList->display, window);
    
    if (tkwin == NULL) {
    	tkwin = gRestrictWinPtr;
    } else {
	tkwin = Tk_TopCoordsToWindow(tkwin, where.h, where.v, 
	    &event.xbutton.x, &event.xbutton.y);
    }

    if (TkPositionInTree((TkWindow *) tkwin, (TkWindow *) gGrabWinPtr) !=
		TK_GRAB_IN_TREE) {
	    tkwin = gGrabWinPtr;
    }
	
    TkMacWinBounds((TkWindow *) tkwin, &bounds);		
    event.xbutton.x = where.h - bounds.left;
    event.xbutton.y = where.v - bounds.top;

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

    /*
     * Button events will also start or end an implicit grab.  Do
     * different things wether we generate up or down mouse events.
     */
    if (state & Button1Mask) {
	event.xany.type = ButtonPress;
	/*
	 * Set mouse capture and the restrict window if we are
	 * currently unrestricted.
	 */

	if (!gRestrictWinPtr) {
	    if (!gGrabWinPtr) {
		gRestrictWinPtr = tkwin;
		gCaptured = 1;
	    } else {

		/*
		 * Make sure the new restrict window is inside the
		 * current grab tree.
		 */

		if (TkPositionInTree((TkWindow *) tkwin, (TkWindow *)
			gGrabWinPtr) > 0) {
		    gRestrictWinPtr = tkwin;
		} else {
		    gRestrictWinPtr = gGrabWinPtr;
		}
		gCaptured = 1;
	    }
	}
    } else {
	event.xany.type = ButtonRelease;

	/*
	 * The function TkMacButtonKeyState which is used to passed the
	 * state into this function will have incorrect information
	 * during a ButtonRelease event.  It will report that no
	 * button is depressed - which is true.  However, during a
	 * ButtonRelease event the state needs to include the button
	 * that *was* depressed.  There for we need to set the
	 * Button1Mask here.
	 */
	event.xbutton.state |= Button1Mask;
	
	/*
	 * Release the mouse capture when the last button is
	 * released and we aren't in a global grab.
	 */

	if (gGrabWinPtr == NULL) {
	    gCaptured = 0;
	}
		
	/*
	 * If we are releasing a restrict window, then we need
	 * to send the button event followed by mouse motion from
	 * the restrict window the the current mouse position.
	 */

	if (gRestrictWinPtr) {
	    if (Tk_WindowId(gRestrictWinPtr) != event.xbutton.window) {
		TkChangeEventWindow(&event, (TkWindow *) gRestrictWinPtr);
	    }
	    gLastWinPtr = gRestrictWinPtr;
	    gRestrictWinPtr = NULL;
	}
	
    }

    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateActivateEvents --
 *
 *	Generate Activate/Deactivate and 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
GenerateActivateEvents(macEvent)
    EventRecord *macEvent;	/* Incoming Mac event */
{
    XEvent event;
    Tk_Window tkwin;
    Window window;

    window = TkMacGetXWindow((WindowRef) macEvent->message);
    tkwin = Tk_IdToWindow(tkDisplayList->display, window);
    if (tkwin == NULL) {
	return false;
    }

    /* 
     * Generate Activate and Deactivate events.  This event
     * is sent to every subwindow in a toplevel window.
     */
    if (macEvent->modifiers & activeFlag) {
	event.xany.type = ActivateNotify;
    } else {
	event.xany.type = DeactivateNotify;
    }

    event.xany.serial = tkDisplayList->display->request;
    event.xany.send_event = False;
    event.xany.display = tkDisplayList->display;
    event.xany.window = window;

    TkQueueEventForAllChildren(tkwin, &event);

    /* 
     * Generate FocusIn and FocusOut events.  This event
     * is only sent to the toplevel window.
     */
    if (macEvent->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.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

static int
GenerateKeyEvent(macEvent)
    EventRecord *macEvent;	/* Incoming Mac event */
{
    Point where;
    Tk_Window tkwin;
    XEvent event;

    /*
     * Tk keeps track of the current focus window.  If Tk doesn't
     * claim to have any focus - then we ignore the event.
     */
    tkwin = (Tk_Window) tkDisplayList->focusWinPtr;
    if (tkwin == NULL) {
	return false;
    }

    event.xany.send_event = False;
    event.xkey.same_screen = true;
    event.xkey.subwindow = None;
    event.xkey.time = TkMacGenerateTime();
    event.xkey.keycode = macEvent->message;

    where.v = macEvent->where.v;
    where.h = macEvent->where.h;
    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.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 (macEvent->what == keyDown) {
	event.xany.type = KeyPress;
	Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
    } else if (macEvent->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);
	}
    }
     
    /* 
     * Then check to see if the window the mouse is positioned
     * over has changed.  Generate enter and leave events if it
     * has.  We can also generate the cursor at this time.
     */

    generatedEvents = GenerateEnterLeave(tkwin, whereGlobal.h, whereGlobal.v);
    UpdateCursor((TkWindow *) gLastWinPtr, whichwindow, whereLocal);
    
    /*
     * Now we generate motion events - assuming the mouse moved.
     */

    if (EqualPt(gLastPointerPos, whereGlobal)) {
	return generatedEvents;
    } else {
	generatedEvents |= GenerateMotion(tkwin, whereLocal, whereGlobal);
	gLastPointerPos = whereGlobal;
    }

    return generatedEvents;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacButtonKeyState --
 *
 *	Returns the current state of the button & modifier keys.  This
 *	value is usually used in the XEvent structure.
 *
 * 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;
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateCursor --
 *
 *	This function updates the cursor based on the current window the
 *  	the cursor is over.  It also generates the resize cursor for the
 *  	resize region in the lower right hand corner of the window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The cursor may change shape.
 *
 *----------------------------------------------------------------------
 */

static void
UpdateCursor(winPtr, whichwindow, whereLocal)
    TkWindow *winPtr;
    WindowRef whichwindow;
    Point whereLocal;
{
    WindowRef frontWin;
    CursHandle cursor;

    if (tkMacAppInFront == false) {
	return;
    }
    
    /*
     * The cursor will change during an implicit grab only under
     * a few special cases - such as bindings.
     */
    if (gRestrictWinPtr != NULL) {
	if (TkPositionInTree(winPtr, (TkWindow *) gRestrictWinPtr) ==
		TK_GRAB_IN_TREE) {
	    TkUpdateCursor(winPtr);
	} else {
	    TkUpdateCursor((TkWindow *) gRestrictWinPtr);
	}
	return;
    }

    /*
     * The cursor should be the arrow if outside the active window.
     */
    frontWin = FrontWindow();
    if (frontWin != whichwindow) {	
	TkUpdateCursor(NULL);
	return;
    }

    /*
     * One special case is the grow region.  Because a Tk window may
     * not have allocated space for the grow region the grow region
     * floats above the rest of the Tk window.  This is shown by
     * changing the cursor over the grow region.  This is not needed
     * if the window is not resizable or a scrollbar is growing the
     * drag region for the window.
     */
    if ((gGrabWinPtr == NULL) && TkMacResizable(winPtr) && 
	    (TkMacGetScrollbarGrowWindow(winPtr) == NULL)) {
	if (whereLocal.h > (whichwindow->portRect.right - 16) &&
		whereLocal.v > (whichwindow->portRect.bottom - 16)) {
	    cursor = (CursHandle) GetNamedResource('CURS', "\presize");
	    SetCursor(*cursor);
	    return;
	}
    }

    /*
     * Set the cursor to the value set for the given window.  If
     * the value is None - set to the arrow cursor
     */
    TkUpdateCursor(winPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateMotion --
 *
 *	Given a Tk window and the current mouse position this
 *	function will generate the appropiate X Motion events.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.  
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

static int
GenerateMotion(tkwin, whereLocal, whereGlobal)
    Tk_Window tkwin;		/* current Tk window (or NULL)  */
    Point whereLocal;		/* current mouse position local coords */
    Point whereGlobal;		/* current mouse position global coords */
{
    XEvent event;
    int local_x, local_y;
    Rect bounds;
    
    /* 
     * Mouse moved events generated only when mouse is in the
     * content of the front window.  We also need to take into
     * account any grabs that may be in effect.
     */
     
    if (gRestrictWinPtr) {
	tkwin = gRestrictWinPtr;
    } else if (gGrabWinPtr && !tkwin) {
	tkwin = gGrabWinPtr;
    }

    if (tkwin == NULL) {
	return false;
    }
    
    TkMacWinBounds((TkWindow *) tkwin, &bounds);		
    local_x = whereLocal.h - bounds.left;
    local_y = whereLocal.v - bounds.top;

    event.xany.type = MotionNotify;
    event.xany.serial = Tk_Display(tkwin)->request;
    event.xany.send_event = False;
    event.xany.display = Tk_Display(tkwin);
    event.xmotion.window = Tk_WindowId(tkwin);
    event.xmotion.root = XRootWindow(Tk_Display(tkwin), 0);
    event.xmotion.state = TkMacButtonKeyState();
    event.xmotion.subwindow = None;
    event.xmotion.time = TkMacGenerateTime();
    event.xmotion.x_root = whereGlobal.h;
    event.xmotion.y_root = whereGlobal.v;
    event.xmotion.x = local_x;
    event.xmotion.y = local_y;
    event.xmotion.same_screen = true;
    event.xmotion.is_hint = NotifyNormal;
    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);

    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * InitializeCrossingEvent --
 *
 *	Initializes the common fields for enter/leave events.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Fills in the specified event structure.
 *
 *----------------------------------------------------------------------
 */

static void
InitializeCrossingEvent(eventPtr, winPtr, x, y)
    XEvent* eventPtr;		/* Event structure to initialize. */
    TkWindow *winPtr;		/* Window to make event relative to. */
    long x, y;			/* Root coords of event. */
{
    eventPtr->xcrossing.serial = Tk_Display(winPtr)->request;
    eventPtr->xcrossing.send_event = 0;
    eventPtr->xcrossing.display = winPtr->display;
    eventPtr->xcrossing.root = RootWindow(winPtr->display,
	    winPtr->screenNum);
    eventPtr->xcrossing.time = TkMacGenerateTime();
    eventPtr->xcrossing.x_root = x;
    eventPtr->xcrossing.y_root = y;
    eventPtr->xcrossing.state = TkMacButtonKeyState();
    eventPtr->xcrossing.mode = NotifyNormal;
    eventPtr->xcrossing.focus = False;
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateEnterLeave --
 *
 *	Given a Tk window and the current mouse position this
 *	function will generate the appropiate X Enter and Leave
 *	events.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.  
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

static int
GenerateEnterLeave(tkwin, x, y)
    Tk_Window tkwin;		/* current Tk window (or NULL)  */
    long x;			/* current mouse position in */
    long y;			/* root coordinates */
{
    int crossed = 0;		/* 1 if mouse crossed a window boundary */

    if (tkwin != gLastWinPtr) {
	if (gRestrictWinPtr) {
	    int newPos, oldPos;

	    newPos = TkPositionInTree((TkWindow *) tkwin,
		    (TkWindow *) gRestrictWinPtr);
	    oldPos = TkPositionInTree((TkWindow *) gLastWinPtr,
		    (TkWindow *) gRestrictWinPtr);

	    /*
	     * Check if the mouse crossed into or out of the restrict
	     * window.  If so, we need to generate an Enter or Leave event.
	     */

	    if ((newPos != oldPos) && ((newPos == TK_GRAB_IN_TREE)
		    || (oldPos == TK_GRAB_IN_TREE))) {
		XEvent event;

		InitializeCrossingEvent(&event, gRestrictWinPtr, x, y);
		if (newPos == TK_GRAB_IN_TREE) {
		    event.type = EnterNotify;
		} else {
		    event.type = LeaveNotify;
		}
		if ((oldPos == TK_GRAB_ANCESTOR)
			|| (newPos == TK_GRAB_ANCESTOR)) {
		    event.xcrossing.detail = NotifyAncestor;
		} else {
		    event.xcrossing.detail = NotifyVirtual;
		}
		TkChangeEventWindow(&event, (TkWindow *) gRestrictWinPtr);
		Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
	    }

	} else {
	    Tk_Window targetPtr;

	    if ((gLastWinPtr == NULL)
		|| (Tk_WindowId(gLastWinPtr) == None)) {
		targetPtr = tkwin;
	    } else {
		targetPtr = gLastWinPtr;
	    }

	    if (targetPtr && (Tk_WindowId(targetPtr) != None)) {
		XEvent event;

		/*
		 * Generate appropriate Enter/Leave events.
		 */

		InitializeCrossingEvent(&event, targetPtr, x, y);

		TkInOutEvents(&event, (TkWindow *) gLastWinPtr,
			(TkWindow *) tkwin, LeaveNotify,
			EnterNotify, TCL_QUEUE_TAIL);

		crossed = 1;
	    }
	}
	gLastWinPtr = tkwin;
    }

    return crossed;
}

/*
 *----------------------------------------------------------------------
 *
 * XGrabPointer --
 *
 *	Capture the mouse so event are reported outside of toplevels.
 *	Note that this is a very limited implementation that only
 *	supports GrabModeAsync and owner_events True.
 *
 * Results:
 *	Always returns GrabSuccess.
 *
 * Side effects:
 *	Turns on mouse capture, sets the global grab pointer, and
 *	clears any window restrictions.
 *
 *----------------------------------------------------------------------
 */

int
XGrabPointer(display, grab_window, owner_events, event_mask, pointer_mode,
	keyboard_mode, confine_to, cursor, time)
    Display* display;
    Window grab_window;
    Bool owner_events;
    unsigned int event_mask;
    int pointer_mode;
    int keyboard_mode;
    Window confine_to;
    Cursor cursor;
    Time time;
{
    gCaptured = 1;
    gGrabWinPtr = Tk_IdToWindow(display, grab_window);
    gRestrictWinPtr = NULL;
    if (TkPositionInTree((TkWindow *) gLastWinPtr, (TkWindow *) gGrabWinPtr)
	    != TK_GRAB_IN_TREE) {
	TkUpdateCursor((TkWindow *) gGrabWinPtr);
    }
    return GrabSuccess;
}

/*
 *----------------------------------------------------------------------
 *
 * XUngrabPointer --
 *
 *	Release the current grab.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Releases the mouse capture.
 *
 *----------------------------------------------------------------------
 */

void
XUngrabPointer(display, time)
    Display* display;
    Time time;
{
    gCaptured = 0;
    gGrabWinPtr = NULL;
    gRestrictWinPtr = NULL;
    TkUpdateCursor((TkWindow *) gLastWinPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * 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, grab_window, owner_events, pointer_mode,
	keyboard_mode, time)
    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, time)
    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, w, root_return, child_return, root_x_return,
	root_y_return, win_x_return, win_y_return, mask_return)
    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();
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacPointerDeadWindow --
 *
 *	Clean up pointer module state when a window is destroyed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May change the grab module settings.
 *
 *----------------------------------------------------------------------
 */

void
TkMacPointerDeadWindow(winPtr)
    TkWindow *winPtr;
{
    if ((Tk_Window) winPtr == gLastWinPtr) {
	gLastWinPtr = NULL;
    }
    if ((Tk_Window) winPtr == gGrabWinPtr) {
	gGrabWinPtr = NULL;
    }
    if ((Tk_Window) winPtr == gRestrictWinPtr) {
	gRestrictWinPtr = NULL;
    }
    if (!(gRestrictWinPtr || gGrabWinPtr)) {
	gCaptured = 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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(eventPtr)
    EventRecord *eventPtr;
{
    int eventFound = false;
    
    switch (eventPtr->what) {
	case nullEvent:
	    if (GeneratePollingEvents()) {
		eventFound = true;
	    }
	    break;
	case updateEvt:
	    if (GenerateUpdateEvent(eventPtr)) {
		eventFound = true;
	    }
	    break;
	case mouseDown:
	case mouseUp:
	    if (WindowManagerMouse(eventPtr)) {
		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 = MenuKey(eventPtr->message & charCodeMask);
		
		if (HiWord(menuResult) != 0) {
		    TkMacHandleMenuSelect(menuResult, false);
		    break;
		}
	    }
	case keyUp:
	    eventFound |= GenerateKeyEvent(eventPtr);
	    break;
	case activateEvt:
	    eventFound |= GenerateActivateEvents(eventPtr);
	    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;
}

/*
 *----------------------------------------------------------------------
 *
 * 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;
}

/*
 *----------------------------------------------------------------------
 *
 * 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(wRef, xOffset, yOffset)
    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(wRef)
    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((WindowDefProcPtr) *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(wRef)
    WindowRef wRef;
{
    SelectWindow(wRef);
    gEatButtonUp = true;
}
