windows-nt/Source/XPSP1/NT/shell/comctl32/v5/trackme.c
2020-09-26 16:20:57 +08:00

594 lines
17 KiB
C

//---------------------------------------------------------------------------
//
// 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);
}