//---------------------------------------------------------------------------
//
//     TrackME.C   (TrackMouseEvent)
//
// Created by:  Sankar  on 1/24/96
//
// What:
//     This emulates the TrackMouseEvent() API for the Nashville project
//     in comctl32.dll
//
// How:
//     This subclasses the given window to get mouse messages and uses a 
//     high frequency timer to learn about mouse leaves.
//
//---------------------------------------------------------------------------

#include "ctlspriv.h"

#ifdef TrackMouseEvent
#undef TrackMouseEvent
#endif

extern const TCHAR FAR c_szTMEdata[];

extern DWORD g_dwHoverSelectTimeout;

#define ID_MOUSEHOVER          0xFFFFFFF0L
#define ID_MOUSELEAVE          0xFFFFFFF1L

#define TME_MOUSELEAVE_TIME    (GetDoubleClickTime() / 5)

#define IsKeyDown(Key)   (GetKeyState(Key) & 0x8000)

// This is the structure whose pointer gets added as a property of a window
// being tracked.
typedef struct  tagTMEDATA {
       TRACKMOUSEEVENT TrackMouseEvent;
       RECT            rcMouseHover;  //In screen co-ordinates.
   }  TMEDATA, FAR *LPTMEDATA;


void NEAR TME_ResetMouseHover(LPTRACKMOUSEEVENT lpTME, LPTMEDATA lpTMEdata);
LRESULT CALLBACK TME_SubclassProc(HWND hwnd, UINT message, WPARAM wParam,
    LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData);

LPTMEDATA NEAR GetTMEdata(HWND hwnd)
{
    LPTMEDATA lpTMEdata;

    GetWindowSubclass(hwnd, TME_SubclassProc, 0, (ULONG_PTR *)&lpTMEdata);

    return lpTMEdata;
}

void NEAR TME_PostMouseLeave(HWND hwnd)
{
  PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L);
}

void NEAR TME_CancelMouseLeave(LPTMEDATA lpTMEdata)
{
  if(!(lpTMEdata->TrackMouseEvent.dwFlags & TME_LEAVE))
      return;

  // Remove the flag.
  lpTMEdata->TrackMouseEvent.dwFlags &= ~(TME_LEAVE);

  // We leave the timer set here since our hover implementation uses it too.
  // TME_CancelTracking will kill it later.
}

void NEAR TME_CancelMouseHover(LPTMEDATA lpTMEdata)
{
  if(!(lpTMEdata->TrackMouseEvent.dwFlags & TME_HOVER))
      return;

  lpTMEdata->TrackMouseEvent.dwFlags &= ~(TME_HOVER);

  KillTimer(lpTMEdata->TrackMouseEvent.hwndTrack, ID_MOUSEHOVER);
}

void NEAR TME_CancelTracking(LPTMEDATA lpTMEdata)
{
  HWND hwndTrack;

  //If either MouseLeave or MouseHover is ON, don't cancel tracking.
  if(lpTMEdata->TrackMouseEvent.dwFlags & (TME_HOVER | TME_LEAVE))
      return;

  hwndTrack = lpTMEdata->TrackMouseEvent.hwndTrack;

  // Uninstall our subclass callback.
  RemoveWindowSubclass(hwndTrack, TME_SubclassProc, 0);

  // Kill the mouseleave timer.
  KillTimer(hwndTrack, ID_MOUSELEAVE);

  // Free the tracking data.
  LocalFree((HANDLE)lpTMEdata);
}

void NEAR TME_RemoveAllTracking(LPTMEDATA lpTMEdata)
{
  TME_CancelMouseLeave(lpTMEdata);
  TME_CancelMouseHover(lpTMEdata);
  TME_CancelTracking(lpTMEdata);
}

//---------------------------------------------------------------------------
//
// TME_MouseHasLeft()
//     The mouse has left the region being tracked. Send the MOUSELEAVE msg
// and then cancel all tracking.
//
//---------------------------------------------------------------------------
void NEAR TME_MouseHasLeft(LPTMEDATA  lpTMEdata)
{
  DWORD  dwFlags;

  //Is WM_MOUSELEAVE notification requied?
  if((dwFlags = lpTMEdata->TrackMouseEvent.dwFlags) & TME_LEAVE)
      TME_PostMouseLeave(lpTMEdata->TrackMouseEvent.hwndTrack); //Then, do it!

  // Cancel all the tracking since the mouse has left.
  TME_RemoveAllTracking(lpTMEdata);
}

// --------------------------------------------------------------------------
//  
//  TME_SubclassWndProc()
//  
//  The subclass proc used for TrackMouseEvent()...!
//
// --------------------------------------------------------------------------

LRESULT CALLBACK TME_SubclassProc(HWND hwnd, UINT message, WPARAM wParam,
    LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData)
{
      LPTMEDATA lpTMEdata = (LPTMEDATA)dwRefData;

      ASSERT(lpTMEdata);

      switch(message)
        {
          case WM_DESTROY:
          case WM_NCDESTROY:
              TME_RemoveAllTracking(lpTMEdata);
              break;

          case WM_ENTERMENULOOP:
              // If the window being tracked enters menu mode, then we need to
              // act asif the mouse has left.
              // NOTE: Because when we are in menu mode, the SCREEN_CAPTURE has occurred
              // and we don't see any mouse moves. This is the only way out!

              // Post mouse leave and cancel all tracking!
              TME_MouseHasLeft(lpTMEdata);
              break;

          case WM_LBUTTONDOWN:
          case WM_LBUTTONUP:
          case WM_MBUTTONDOWN:
          case WM_MBUTTONUP:
          case WM_RBUTTONDOWN:
          case WM_RBUTTONUP:
          case WM_NCLBUTTONDOWN:
          case WM_NCLBUTTONUP:
          case WM_NCMBUTTONDOWN:
          case WM_NCMBUTTONUP:
          case WM_NCRBUTTONDOWN:
          case WM_NCRBUTTONUP:
              //Whenever there is a mouse click, reset mouse hover.
              if(lpTMEdata->TrackMouseEvent.dwFlags & TME_HOVER)
                  TME_ResetMouseHover(&(lpTMEdata->TrackMouseEvent), lpTMEdata);
              break;

          case WM_NCMOUSEMOVE:
              TME_MouseHasLeft(lpTMEdata);
              break;

          case WM_MOUSEMOVE:
              {
                POINT Pt;

                Pt.x = GET_X_LPARAM(lParam);
                Pt.y = GET_Y_LPARAM(lParam);

                ClientToScreen(hwnd, &Pt);
 
                //Check if the mouse is within the hover rect.
                if((lpTMEdata->TrackMouseEvent.dwFlags & TME_HOVER) &&
                   !PtInRect(&(lpTMEdata->rcMouseHover), Pt))
                    TME_ResetMouseHover(&(lpTMEdata->TrackMouseEvent), lpTMEdata);
              }
              break;
        }

      return DefSubclassProc(hwnd, message, wParam, lParam);
}

// --------------------------------------------------------------------------
//  
//  TME_CheckInWindow()
//  
//  This get the current cursor position and checks if it still lies in the
//  "valid" area.
//   Returns TRUE, if it lies in the valid area.
//   FALSE, otherwise.
//
// --------------------------------------------------------------------------

BOOL NEAR TME_CheckInWindow(LPTRACKMOUSEEVENT lpTME, LPPOINT lpPt)
{
    POINT      pt;
    HWND       hwnd;   // Given window.
    HWND       hwndPt; //Window from the given point.
    HWND       hwndCapture;

    hwnd = lpTME->hwndTrack;  //Given window handle.

    //See if anyone has captured the mouse input.
    if((hwndCapture = GetCapture()) && IsWindow(hwndCapture))
      {
        // If tracking is required for a window other than the one that
        // has the capture, forget it! It is not possible!

        if(hwndCapture != hwnd)
            return(FALSE);
      }

    GetCursorPos(&pt);  //Get cursor point in screen co-ordinates.

    if (!hwndCapture)
    {
        hwndPt = WindowFromPoint(pt);

        if (!hwndPt || !IsWindow(hwndPt) || (hwnd != hwndPt))
            return FALSE;

        if (SendMessage(hwnd, WM_NCHITTEST, 0,
            MAKELPARAM((SHORT)pt.x, (SHORT)pt.y)) != HTCLIENT)
        {
            return FALSE;
        }
    }

    // The current point falls on the same area of the same window.
    // It is a valid location.
    if (lpPt)
        *lpPt = pt;

    return(TRUE);
}

// --------------------------------------------------------------------------
//  TME_MouseLeaveTimer()
//
//  Timer callback for WM_MOUSELEAVE generation and cancelling HOVER!
//
// --------------------------------------------------------------------------
VOID CALLBACK TME_MouseLeaveTimer(HWND hwnd, UINT msg, UINT_PTR id, DWORD dwTime)
{
    LPTMEDATA  lpTMEdata;

    if(!(lpTMEdata = GetTMEdata(hwnd)))
        return;

    // YIELD!!!
    if(TME_CheckInWindow(&(lpTMEdata->TrackMouseEvent), NULL))
        return;  //The mouse is still in the valid region. So, do nothing.

    if (!IsWindow(hwnd))
        return;

    //The mouse has left the valid region. So, post mouse-leave if requested
    //Because we are cancelling mouse-leave, we need to cancel mouse-hover too!
    // There can be no hover tracking, if the mouse has already left!

    TME_MouseHasLeft(lpTMEdata);
}


WPARAM NEAR GetMouseKeyFlags()
{
    WPARAM wParam = 0;

    if (IsKeyDown(VK_LBUTTON))
        wParam |= MK_LBUTTON;
    if (IsKeyDown(VK_RBUTTON))
        wParam |= MK_RBUTTON;
    if (IsKeyDown(VK_MBUTTON))
        wParam |= MK_MBUTTON;
    if (IsKeyDown(VK_SHIFT))
        wParam |= MK_SHIFT;
    if (IsKeyDown(VK_CONTROL))
        wParam |= MK_CONTROL;

    return wParam;
}

// --------------------------------------------------------------------------
//  TME_MouseHoverTimer()
//
//  Timer callback for WM_MOUSEHOVER/WM_NCMOUSEHOVER generation.
//
// --------------------------------------------------------------------------
VOID CALLBACK TME_MouseHoverTimer(HWND hwnd, UINT msg, UINT_PTR id, DWORD dwTime)
{
    POINT pt;
    WPARAM wParam;
    LPTMEDATA lpTMEdata;

    if (!(lpTMEdata = GetTMEdata(hwnd)))
        return;

    //BOGUS: we can not detect hwndSysModal from here!
    //Also, tracking is for a per-window basis now!
    //
    // BOGUS: We don't have to worry about JournalPlayback?
    //pt = fJournalPlayback? Lpq(hwnd->hq)->ptLast : ptTrueCursor;

    // YIELD!!!
    if(!TME_CheckInWindow(&(lpTMEdata->TrackMouseEvent), &pt))
      {
        // Mouse has left the valid region of the window. So, cancel all
        // the tracking.
        TME_MouseHasLeft(lpTMEdata);
        return;
      }

    if (!IsWindow(hwnd))
        return;

    if (!PtInRect(&(lpTMEdata->rcMouseHover), pt))
      {
        // Mouse has gone out of the hover rectangle. Reset the hovering.
        TME_ResetMouseHover(&(lpTMEdata->TrackMouseEvent), lpTMEdata);
        return;
      }

    //
    // set up to check the tolerance and 
    //
    wParam = GetMouseKeyFlags();
    ScreenToClient(hwnd, &pt);

    //Mouse is still within the hover rectangle. Let's post hover msg
    PostMessage(hwnd, WM_MOUSEHOVER, wParam, MAKELPARAM(pt.x, pt.y));

    //And then cancel the hovering.
    TME_CancelMouseHover(lpTMEdata);
    TME_CancelTracking(lpTMEdata);  //Cancel the tracking, if needed.
}

BOOL NEAR TME_SubclassWnd(LPTMEDATA lpTMEdata)
{
    BOOL fResult;

    fResult = SetWindowSubclass(lpTMEdata->TrackMouseEvent.hwndTrack,
        TME_SubclassProc, 0, (ULONG_PTR)lpTMEdata);

    ASSERT(fResult);
    return fResult;
}

void NEAR TME_ResetMouseLeave(LPTRACKMOUSEEVENT lpTME, LPTMEDATA lpTMEdata)
{
  //See if already MouseLeave is being tracked.
  if(lpTMEdata->TrackMouseEvent.dwFlags & TME_LEAVE)
      return;   // Nothing else to do.
  
  //Else, set the flag.
  lpTMEdata ->TrackMouseEvent.dwFlags |= TME_LEAVE;

  //Set the high frequency Timer.
  SetTimer(lpTME->hwndTrack, ID_MOUSELEAVE, TME_MOUSELEAVE_TIME, TME_MouseLeaveTimer);
}

void NEAR TME_ResetMouseHover(LPTRACKMOUSEEVENT lpTME, LPTMEDATA lpTMEdata)
{
    DWORD  dwMouseHoverTime;
    POINT  pt;

    // Even if the hover tracking is already happening, the caller might 
    // change the timer value, restart the timer or change the hover 
    // rectangle.
    lpTMEdata->TrackMouseEvent.dwFlags |= TME_HOVER;

    dwMouseHoverTime = lpTME->dwHoverTime;
    if (!dwMouseHoverTime || (dwMouseHoverTime == HOVER_DEFAULT))
        dwMouseHoverTime = (g_dwHoverSelectTimeout ? g_dwHoverSelectTimeout : GetDoubleClickTime()*4/5); // BUGBUG: Can't we remember this?
    GetCursorPos(&pt);

    //
    // update the tolerance rectangle for the hover window.
    //
    *((POINT *)&(lpTMEdata->rcMouseHover.left)) = *((POINT *)&(lpTMEdata->rcMouseHover.right)) = pt;

    //BOGUS: Can we use globals to remeber these metrics. What about NT?
    InflateRect(&(lpTMEdata->rcMouseHover), g_cxDoubleClk/2, g_cyDoubleClk/2);
                       
    // We need to remember the timer interval we are setting. This value
    // needs to be returned when TME_QUERY is used.
    lpTME->dwHoverTime = dwMouseHoverTime;
    lpTMEdata->TrackMouseEvent.dwHoverTime = dwMouseHoverTime;
    SetTimer(lpTME->hwndTrack, ID_MOUSEHOVER, dwMouseHoverTime, TME_MouseHoverTimer);
}

// --------------------------------------------------------------------------
//  QueryTrackMouseEvent()
//
//  Fills in a TRACKMOUSEEVENT structure describing current tracking state
//  for a given window. The given window is in lpTME->hwndTrack.
//
// --------------------------------------------------------------------------
BOOL NEAR QueryTrackMouseEvent(LPTRACKMOUSEEVENT lpTME)
{
    HWND hwndTrack;
    LPTMEDATA lpTMEdata;

    //
    // if there isn't anything being tracked get out
    //
    if((!(hwndTrack = lpTME->hwndTrack)) || !IsWindow(hwndTrack))
        goto Sorry;

    if(!(lpTMEdata = GetTMEdata(hwndTrack)))
        goto Sorry;

    if(!(lpTMEdata->TrackMouseEvent.dwFlags & (TME_HOVER | TME_LEAVE)))
        goto Sorry;

    //
    // fill in the requested information
    //
    lpTME->dwFlags = lpTMEdata->TrackMouseEvent.dwFlags;

    if (lpTMEdata->TrackMouseEvent.dwFlags & TME_HOVER)
        lpTME->dwHoverTime = lpTMEdata->TrackMouseEvent.dwHoverTime;
    else
        lpTME->dwHoverTime = 0;

    goto Done;

Sorry:
    // zero out the struct
    lpTME->dwFlags = 0;
    lpTME->hwndTrack = NULL;
    lpTME->dwHoverTime = 0;

Done:
    return TRUE;
}


// --------------------------------------------------------------------------
//  EmulateTrackMouseEvent()
//
//  emulate API for requesting extended mouse notifications (hover, leave...)
//
// --------------------------------------------------------------------------
BOOL WINAPI EmulateTrackMouseEvent(LPTRACKMOUSEEVENT lpTME)
{
    HWND    hwnd;
    DWORD   dwFlags;
    LPTMEDATA  lpTMEdata;

    if (lpTME->dwFlags & ~TME_VALID)
        return FALSE;

#ifdef TME_NONCLIENT
    //
    // this implementation does not handle TME_NONCLIENT (anymore)
    // we agreed with the NT team to rip it out until the system uses it...
    //
    if (lpTME->dwFlags & TME_NONCLIENT)
        return FALSE;
#endif

    //
    // implement queries separately
    //
    if (lpTME->dwFlags & TME_QUERY)
        return QueryTrackMouseEvent(lpTME);
    
    // 
    // Check the validity of the request.
    //
    hwnd = lpTME->hwndTrack;
    dwFlags = lpTME->dwFlags;

    if (!IsWindow(hwnd))
        return FALSE;

    // Check if the mouse is currently in a valid position
    // Use GetCursorPos() to get the mouse position and then check if
    // it lies within the client/non-client portion of the window as
    // defined in this call;

    // YIELD!!!
    if(!TME_CheckInWindow(lpTME, NULL))
      {
        //If the mouse leave is requested when the mouse is already outside
        // the window, then generate one mouse leave immly.
        if((dwFlags & TME_LEAVE) && !(dwFlags & TME_CANCEL))
            TME_PostMouseLeave(hwnd);
        
        //Because it is an invalid request, we return immly.
        return(TRUE);
      }

    if (!IsWindow(hwnd))
        return FALSE;

    //It is a valid request, either to install or remove tracking.

    //See if we already have tracking for this window.
    if(!(lpTMEdata = GetTMEdata(hwnd)))
      {
        //We are not tracking this window already.
        if(dwFlags & TME_CANCEL)
            return(TRUE);   //There is nothing to cancel; Ignore!
        
        //Do they want any tracking at all?
        ASSERT(dwFlags & (TME_HOVER | TME_LEAVE));

        //Allocate global mem to remember the tracking data
        if(!(lpTMEdata = (LPTMEDATA)LocalAlloc(LPTR, sizeof(TMEDATA))))
            return(FALSE);

        // copy in the hwnd
        lpTMEdata->TrackMouseEvent.hwndTrack = lpTME->hwndTrack;

        // Make sure our subclass callback is installed.
        if (!TME_SubclassWnd(lpTMEdata))
          {
            TME_CancelTracking(lpTMEdata);
            return(FALSE);
          }
      }

    //Else fall through!

    if(dwFlags & TME_CANCEL)
      {
        if(dwFlags & TME_HOVER)
            TME_CancelMouseHover(lpTMEdata);
        
        if(dwFlags & TME_LEAVE)
            TME_CancelMouseLeave(lpTMEdata);

        // If both hover and leave are cancelled, then we don't need any
        // tracking.
        TME_CancelTracking(lpTMEdata);

        return(TRUE); // Cancelled whatever they asked for.
      }

    if(dwFlags & TME_HOVER)
        TME_ResetMouseHover(lpTME, lpTMEdata);

    if(dwFlags & TME_LEAVE)
        TME_ResetMouseLeave(lpTME, lpTMEdata);

    return(TRUE);
}

typedef BOOL (WINAPI* PFNTME)(LPTRACKMOUSEEVENT);

PFNTME g_pfnTME = NULL;

// --------------------------------------------------------------------------
//  _TrackMouseEvent() entrypoint
//
//  calls TrackMouseEvent if present, otherwise uses EmulateTrackMouseEvent
//
// --------------------------------------------------------------------------
BOOL WINAPI _TrackMouseEvent(LPTRACKMOUSEEVENT lpTME)
{
    if (!g_pfnTME)
    {
        HMODULE hmod = GetModuleHandle(TEXT("USER32"));

        if (hmod)
            g_pfnTME = (PFNTME)GetProcAddress(hmod, "TrackMouseEvent");

        if (!g_pfnTME)
            g_pfnTME = EmulateTrackMouseEvent;
    }

    return g_pfnTME(lpTME);
}