/*
 * tkMacOSXMouseEvent.c --
 *
 *	This file implements functions that decode & handle mouse events
 *      on MacOS X.
 *
 *      Copyright 2001, Apple Computer, Inc.
 *
 *      The following terms apply to all files originating from Apple
 *      Computer, Inc. ("Apple") and associated with the software
 *      unless explicitly disclaimed in individual files.
 *
 *
 *      Apple hereby grants permission to use, copy, modify,
 *      distribute, and license this software and its documentation
 *      for any purpose, provided that existing copyright notices are
 *      retained in all copies and that this notice is included
 *      verbatim in any distributions. No written agreement, license,
 *      or royalty fee is required for any of the authorized
 *      uses. Modifications to this software may be copyrighted by
 *      their authors and need not follow the licensing terms
 *      described here, provided that the new terms are clearly
 *      indicated on the first page of each file where they apply.
 *
 *
 *      IN NO EVENT SHALL APPLE, THE AUTHORS OR DISTRIBUTORS OF THE
 *      SOFTWARE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
 *      INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
 *      THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF,
 *      EVEN IF APPLE OR THE AUTHORS HAVE BEEN ADVISED OF THE
 *      POSSIBILITY OF SUCH DAMAGE.  APPLE, THE AUTHORS AND
 *      DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING,
 *      BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
 *      FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS
 *      SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND APPLE,THE
 *      AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
 *      MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *      GOVERNMENT USE: If you are acquiring this software on behalf
 *      of the U.S. government, the Government shall have only
 *      "Restricted Rights" in the software and related documentation
 *      as defined in the Federal Acquisition Regulations (FARs) in
 *      Clause 52.227.19 (c) (2).  If you are acquiring the software
 *      on behalf of the Department of Defense, the software shall be
 *      classified as "Commercial Computer Software" and the
 *      Government shall have only "Restricted Rights" as defined in
 *      Clause 252.227-7013 (c) (1) of DFARs.  Notwithstanding the
 *      foregoing, the authors grant the U.S. Government and others
 *      acting in its behalf permission to use and distribute the
 *      software in accordance with the terms specified in this
 *      license.
 */

#include "tkInt.h"
#include <X11/X.h>
#include <X11/Xlib.h>
#include "tkMacOSXEvent.h"
#include "tkMacOSXInt.h"
#include "tkPort.h"
#include "tkMacOSXDebug.h"

typedef struct {
    WindowRef      whichWin;
    WindowRef      activeNonFloating;
    WindowPartCode windowPart;
    Point          global;
    Point          local;   
    unsigned int   state;
    long           delta;
} MouseEventData;

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

static int gEatButtonUp = 0;       /* 1 if we need to eat the next * up event */

/*
 * Declarations of functions used only in this file.
 */

static void BringWindowForward _ANSI_ARGS_((WindowRef wRef));
static int GeneratePollingEvents(MouseEventData * medPtr);
static int GenerateMouseWheelEvent(MouseEventData * medPtr);

extern int TkMacOSXGetEatButtonUp();
extern void TkMacOSXSetEatButtonUp(int f);

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXProcessMouseEvent --
 *
 *        This routine processes the event in eventPtr, and
 *        generates the appropriate Tk events from it.
 *
 * Results:
 *        True if event(s) are generated - false otherwise.
 *
 * Side effects:
 *        Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */
 
int
TkMacOSXProcessMouseEvent(TkMacOSXEvent *eventPtr, MacEventStatus * statusPtr)
{
    WindowRef      frontWindow;
    Tk_Window      tkwin;
    Point          where, where2;
    int            xOffset, yOffset;
    TkDisplay *    dispPtr;
    Window         window;
    int            status,err;
    MouseEventData mouseEventData, * medPtr = &mouseEventData;
    KeyMap         keyMap;

    switch (eventPtr->eKind) {
        case kEventMouseUp:
        case kEventMouseDown:
        case kEventMouseMoved:
        case kEventMouseDragged:
        case kEventMouseWheelMoved:
            break;
        default:
            statusPtr->handledByTk = 1;
            return 0;
            break;
    }
    statusPtr->handledByTk = 1;
    status = GetEventParameter(eventPtr->eventRef, 
            kEventParamMouseLocation,
            typeQDPoint, NULL, 
            sizeof(where), NULL,
            &where);
    if (status != noErr) {
        fprintf (stderr, "Failed to retrieve mouse location,%d\n", status);
        return 0;
    }
    medPtr->state = 0;
    GetKeys(keyMap);
    if (keyMap[1] & 2) { 
        medPtr->state |= LockMask;
    }
    if (keyMap[1] & 1) {
        medPtr->state |= ShiftMask;
    }
    if (keyMap[1] & 8) {
        medPtr->state |= ControlMask;
    }
    if (keyMap[1] & 32768) {
        medPtr->state |= Mod1Mask;              /* command key */
    }
    if (keyMap[1] & 4) {
        medPtr->state |= Mod2Mask;              /* option key */
    }
    if (eventPtr->eKind == kEventMouseDown 
            || eventPtr->eKind== kEventMouseDragged ) {
        EventMouseButton mouseButton;
        if ((status=GetEventParameter(eventPtr->eventRef,
             kEventParamMouseButton,
             typeMouseButton, NULL,
             sizeof(mouseButton), NULL,&mouseButton)) != noErr ) {
             fprintf (stderr, "Failed to retrieve mouse button, %d\n", status);
             statusPtr->err = 1;
             return 0;
         }
         medPtr->state |= 1 << ((mouseButton-1)+8);
    }

    medPtr->windowPart= FindWindow(where, &medPtr->whichWin);
    window = TkMacOSXGetXWindow(medPtr->whichWin);
    if (medPtr->whichWin != NULL && window == None) {
        statusPtr->handledByTk = 0;
        return 0;
    }
    
    frontWindow = FrontWindow();
    medPtr->activeNonFloating = ActiveNonFloatingWindow();

    /*
     * 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->eKind == kEventMouseUp) {
        if (TkMacOSXGetEatButtonUp()) {
            TkMacOSXSetEatButtonUp(false);
            return false;
        }
        return TkGenerateButtonEvent(where.h, where.v,
            window, medPtr->state);
    }
    if (eventPtr->eKind == kEventMouseWheelMoved) {
        if ((status=GetEventParameter(eventPtr->eventRef,
             kEventParamMouseWheelDelta,
             typeLongInteger, NULL,
             sizeof(medPtr->delta), NULL,&medPtr->delta)) != noErr ) {
             fprintf (stderr,
                 "Failed to retrieve mouse wheel delta, %d\n", status);
             statusPtr->err = 1;
             return false;
         }
    }
    
    dispPtr = TkGetDisplayList();
    tkwin = Tk_IdToWindow(dispPtr->display, window);

    if (eventPtr->eKind != kEventMouseDown ) {
        /* 
         * MouseMoved, MouseDragged or kEventMouseWheelMoved 
         */
         
        medPtr->global = where;
        medPtr->local = where;
	/* 
	 * We must set the port to the right window -- the one
	 * we are actually going to use -- before finding
	 * the local coordinates, otherwise we will have completely
	 * wrong local x,y!
	 * 
	 * I'm pretty sure this window is medPtr->whichWin, unless
	 * perhaps there is a grab.  Certainly 'frontWindow' or
	 * 'medPtr->activeNonFloating' are wrong.
	 */ 
	SetPortWindowPort(medPtr->whichWin);
        GlobalToLocal(&medPtr->local);
        if (eventPtr->eKind == kEventMouseWheelMoved ) {
            return GenerateMouseWheelEvent(medPtr); 
        } else {
            return GeneratePollingEvents(medPtr); 
        }
    }

    if (medPtr->whichWin && eventPtr->eKind==kEventMouseDown) {
        ProcessSerialNumber frontPsn, ourPsn;
        Boolean             flag;
        if ((err=GetFrontProcess(&frontPsn))!=noErr) {
            fprintf(stderr, "GetFrontProcess failed, %d\n", err);
            statusPtr->err = 1;
            return 1;
        }
        
        GetCurrentProcess(&ourPsn);
        if ((err=SameProcess(&frontPsn, &ourPsn, &flag))!=noErr) {
            fprintf(stderr, "SameProcess failed, %d\n", err);
            statusPtr->err = 1;
            return 1;
        } else {
            if (!flag) {
                if ((err=SetFrontProcess(&ourPsn)) != noErr) {
                    fprintf(stderr,"SetFrontProcess failed,%d\n", err);
                    statusPtr->err = 1;
                    return 1;
                }
            }
        }

    }

    if (medPtr->whichWin) {
        /*
         * We got a mouse down in a window
         * See if this is the activate click
         * This click moves the window forward.  We don't want
         * the corresponding mouse-up to be reported to the application
         * or else it will mess up some Tk scripts.
         */
         
         if (!(TkpIsWindowFloating(medPtr->whichWin))
             && (medPtr->whichWin != medPtr->activeNonFloating)) {
             Tk_Window grabWin = TkMacOSXGetCapture();
             if ((grabWin != NULL) && (grabWin != tkwin)) {
                 TkWindow * tkw, * grb;
                 tkw = (TkWindow *)tkwin;
                 grb = (TkWindow *)grabWin;
                 SysBeep(1);  
                 return false;
             }
             TkMacOSXSetEatButtonUp(true);
             BringWindowForward(medPtr->whichWin);
             return false;
         }
    }

    switch (medPtr->windowPart) {
        case inDrag:
            DragWindow(medPtr->whichWin, where, NULL);
            where2.h = where2.v = 0;
            LocalToGlobal(&where2);
            if (EqualPt(where, where2)) {
                return false;
            }
            TkMacOSXWindowOffset(medPtr->whichWin, &xOffset, &yOffset);
            where2.h -= xOffset;
            where2.v -= yOffset;
            TkGenWMConfigureEvent(tkwin, where2.h, where2.v,
                    -1, -1, TK_LOCATION_CHANGED);
            return true;
            break;
        case inContent:
            return TkGenerateButtonEvent(where.h, where.v, 
                    window, medPtr->state);
            break;
        case inGrow:
            /*      
             * 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 (TkMacOSXGrowToplevel(medPtr->whichWin, where) == true) {
                return true;
            } else {
                return TkGenerateButtonEvent(where.h,
                           where.v, window, medPtr->state);
            }
            break;
        case inGoAway:
            if (TrackGoAway(medPtr->whichWin,where)) {
                if (tkwin == NULL) {
                    return false;
                }
                TkGenWMDestroyEvent(tkwin);
                return true;
            }
            return false;
            break;
        case inMenuBar:
            {
                int oldMode;
                KeyMap theKeys;

                GetKeys(theKeys);
                oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL);
                TkMacOSXClearMenubarActive();
                
                /*
                 * Handle -postcommand
                 */
                 
                TkMacOSXPreprocessMenu();
                TkMacOSXHandleMenuSelect(MenuSelect(where),
                        theKeys[1] & 4);
                Tcl_SetServiceMode(oldMode);
                return true; /* TODO: may not be on event on queue. */
            }
            break;
        case inZoomIn:
        case inZoomOut:
            if (TkMacOSXZoomToplevel(medPtr->whichWin, where, 
                    medPtr->windowPart) == true) {
                return true;
            } else {
                return false;
            }
            break;
        case inCollapseBox:
             if ((err = CollapseWindow(medPtr->whichWin,
                 !IsWindowCollapsed(medPtr->whichWin)))!=noErr) {
                 fprintf(stderr,"CollapseWindow failed,%d\n", err);
                 statusPtr->err = 1;
             }
             break;
        default:
            return false;
            break;
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * 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(MouseEventData * medPtr)
{
    Tk_Window tkwin, rootwin, grabWin;
    Window window;
    int local_x, local_y;
    TkDisplay *dispPtr;

    /* 
     * I really do not understand this complicated logic.  Surely the event
     * should be to either: (1) medPtr->whichWin, the window under the mouse
     * (from which we then obviously extract the correct Tk subwindow), or
     * (2) the current grab window.  I really don't see why anything else is
     * relevant.
     */
#if 0
    if ((!TkpIsWindowFloating(medPtr->whichWin) 
            && (medPtr->activeNonFloating != medPtr->whichWin))) {
        tkwin = NULL;
    } else {
        window = TkMacOSXGetXWindow(medPtr->whichWin);
        dispPtr = TkGetDisplayList();
        rootwin = Tk_IdToWindow(dispPtr->display, window);
        if (rootwin == NULL) {
            tkwin = NULL;
        } else {
            tkwin = Tk_TopCoordsToWindow(rootwin, 
                    medPtr->local.h, medPtr->local.v, 
                    &local_x, &local_y);
        }
    }
    
    /*
     * The following call will generate the appropiate X events and
     * adjust any state that Tk must remember.
     */

    grabWin = TkMacOSXGetCapture();

    if ((tkwin == NULL) && (grabWin != NULL)) {
        tkwin = grabWin;
    }
#else
    grabWin = TkMacOSXGetCapture();
    if (grabWin != NULL) {
	tkwin = grabWin;
    } else {
	window = TkMacOSXGetXWindow(medPtr->whichWin);
	dispPtr = TkGetDisplayList();
	rootwin = Tk_IdToWindow(dispPtr->display, window);
	if (rootwin == NULL) {
	    tkwin = NULL;
	} else {
	    tkwin = Tk_TopCoordsToWindow(rootwin, 
		    medPtr->local.h, medPtr->local.v, 
		    &local_x, &local_y);
	}
    }
#endif
    Tk_UpdatePointer(tkwin, medPtr->global.h, medPtr->global.v,
            medPtr->state);
    

    TkMacOSXInstallCursor(0);

    return true;
}


/*           
 *----------------------------------------------------------------------
 *
 * 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)
{           
    if (!TkpIsWindowFloating(wRef)) {
        if (IsValidWindowPtr(wRef))
        SelectWindow(wRef);
    }
}

static int
GenerateMouseWheelEvent(MouseEventData * medPtr)
{
    Tk_Window tkwin, rootwin, grabWin;
    Window window;
    int local_x, local_y;
    TkDisplay *dispPtr;
    TkWindow  *winPtr;
    XEvent     xEvent;

    if ((!TkpIsWindowFloating(medPtr->whichWin) 
            && (medPtr->activeNonFloating != medPtr->whichWin))) {
        tkwin = NULL;
    } else {
        window = TkMacOSXGetXWindow(medPtr->whichWin);
        dispPtr = TkGetDisplayList();
        rootwin = Tk_IdToWindow(dispPtr->display, window);
        if (rootwin == NULL) {
            tkwin = NULL;
        } else {
            tkwin = Tk_TopCoordsToWindow(rootwin, 
                    medPtr->local.h, medPtr->local.v, 
                    &local_x, &local_y);
        }
    }
    
    /*
     * The following call will generate the appropiate X events and
     * adjust any state that Tk must remember.
     */

    grabWin = TkMacOSXGetCapture();

    if ((tkwin == NULL) && (grabWin != NULL)) {
        tkwin = grabWin;
    }
    if (!tkwin) {
       return true;
    }
    winPtr = ( TkWindow *)tkwin;
    xEvent.type = MouseWheelEvent;
    xEvent.xkey.keycode = medPtr->delta;
    xEvent.xany.serial = LastKnownRequestProcessed(winPtr->display);
    xEvent.xany.send_event = false;
    xEvent.xany.display = winPtr->display;
    xEvent.xany.window = Tk_WindowId(winPtr);
    Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);

    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXGetEatButtonUp --
 *
 * Results:
 *      Returns the flag indicating if we need to eat the
 *      next mouse up event
 *
 * Side effects:
 *        None.
 *
 *----------------------------------------------------------------------
 */
int
TkMacOSXGetEatButtonUp()
{
    return gEatButtonUp;
}

/*
 * TkMacOSXSetEatButtonUp --
 *
 * Results:
 *        None.
 *
 * Side effects:
 *      Sets the flag indicating if we need to eat the
 *      next mouse up event
 *
 */
void
TkMacOSXSetEatButtonUp(int f)
{
    gEatButtonUp = f;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXButtonKeyState --
 *
 *        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
TkMacOSXButtonKeyState()
{
    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;
}

/*
 *----------------------------------------------------------------------
 *
 * 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;
    CGrafPtr port;
    GDHandle dev;

    GetGWorld(&port,&dev);
    GetMouse(&where);
    LocalToGlobal(&where);

    *root_x_return = where.h;
    *root_y_return = where.v;
    *mask_return = TkMacOSXButtonKeyState();    
    return True;
}


/*
 *----------------------------------------------------------------------
 *
 * 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;
    TkDisplay *dispPtr;

    /* 
     * 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 = FrontNonFloatingWindow();
                        
    if (0 && ((frontWin == NULL) || ((!(TkpIsWindowFloating(whichWin)) 
            && (frontWin != whichWin))
            && TkMacOSXGetCapture() == NULL))) {
        return false;
    }

    dispPtr = TkGetDisplayList();
    tkwin = Tk_IdToWindow(dispPtr->display, window);
    
    /* SetPortWindowPort(ActiveNonFloatingWindow()); */
    SetPortWindowPort(whichWin);
    GlobalToLocal(&where);
    if (tkwin != NULL) {
        tkwin = Tk_TopCoordsToWindow(tkwin, where.h, where.v, 
                &dummy, &dummy);
    }

    Tk_UpdatePointer(tkwin, x,  y, state);

    return true;
}