Windows2000/private/shell/win16/commctrl/trackme.c
2020-09-30 17:12:32 +02:00

723 lines
20 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[];
#define ID_MOUSEHOVER 0xFFF0L
#define ID_MOUSELEAVE 0xFFF1L
#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);
#ifdef IEWIN31_25
LRESULT CALLBACK TME_SubclassProc(HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam /*, UINT uIdSubclass, DWORD dwRefData*/);
VOID CALLBACK TME_MouseLeaveTimer(HWND hwnd, UINT msg, UINT id, DWORD dwTime);
VOID CALLBACK TME_MouseHoverTimer(HWND hwnd, UINT msg, UINT id, DWORD dwTime);
// We don't need the generlized subclassing code in subclass.c used by the 32-bit version.
// This is because this file doesn't re-subclass windows that are already subclassed.
// Also the code in subclass.c was full of near/far pointer fixups that caused havock.
// So for now, we uses these simplified functions:
char g_szSubProp[] = "SubclassData";
#ifdef SUBCLASSPROC
#undef SUBCLASSPROC
#define SUBCLASSPROC FARPROC
#endif
typedef struct tagSUB_DATA
{
WNDPROC pfnSuper; // original window procedure
DWORD dwData; // misc data associated with the window
} SUB_DATA;
BOOL SetWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT uIdSubclass, DWORD dwRefData)
{
BOOL fSuccess;
// Allocate our structure to be attached to the window
SUB_DATA* pData = (SUB_DATA*)LocalAlloc(LPTR, sizeof(SUB_DATA));
if (!pData)
return FALSE;
// Subclass the control and init our structure
pData->pfnSuper = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (LPARAM)(FARPROC)pfnSubclass);
pData->dwData = dwRefData;
// Associate the data as a window property
fSuccess = SetProp(hWnd, g_szSubProp, (HANDLE)pData);
if (!fSuccess)
{
// Should never fail...
Assert(FALSE);
// Undo the subclass
SetWindowLong(hWnd, GWL_WNDPROC, (LPARAM)pData->pfnSuper);
LocalFree((HLOCAL)pData);
}
return fSuccess;
}
// This function returns the data assocated with the window. the parameters pfnSubclass
// and are ignored.
BOOL GetWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT uIdSubclass, DWORD FAR *pdwRefData)
{
SUB_DATA* pData = (SUB_DATA*)GetProp(hWnd, g_szSubProp);
if (pData)
{
*pdwRefData = pData->dwData;
}
else
{
// Failure, so zero the returned value
*pdwRefData = NULL;
}
return (BOOL)pData;
}
BOOL RemoveWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT uIdSubclass)
{
// Undo the subclassing
SUB_DATA* pData = (SUB_DATA*)GetProp(hWnd, g_szSubProp);
if (pData)
{
RemoveProp(hWnd, "SubclassData");
// Restore the original window procedure
SetWindowLong(hWnd, GWL_WNDPROC, (LPARAM)pData->pfnSuper);
LocalFree((HLOCAL)pData);
}
return (BOOL)pData;
}
LRESULT DefSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Call the original window procedure
SUB_DATA* pData = (SUB_DATA*)GetProp(hWnd, g_szSubProp);
if (pData)
{
return CallWindowProc(pData->pfnSuper, hWnd, uMsg, wParam, lParam);
}
else
{
// Degrade to the global default window procedure
// Assert(FALSE);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
#else // !IEWIN31_25
LRESULT CALLBACK TME_SubclassProc(HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam, UINT uIdSubclass, DWORD dwRefData);
#endif // !IEWIN31_25
LPTMEDATA /*NEAR*/ GetTMEdata(HWND hwnd)
{
LPTMEDATA lpTMEdata;
GetWindowSubclass(hwnd, TME_SubclassProc, 0, (DWORD *)&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()...!
// --------------------------------------------------------------------------
#ifdef IEWIN31_25
LRESULT CALLBACK TME_SubclassProc(HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam/*, UINT uIdSubclass , DWORD dwRefData*/)
#else
LRESULT CALLBACK TME_SubclassProc(HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam, UINT uIdSubclass , DWORD dwRefData)
#endif
{
LPTMEDATA lpTMEdata = GetTMEdata(hwnd);// = (LPTMEDATA)dwRefData;
if (!lpTMEdata)
return 0;
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;
#ifdef IEWIN31_25
// Avoids need of MakeProcInstance for the timer proc
case WM_TIMER:
{
if (wParam == ID_MOUSEHOVER)
{
TME_MouseHoverTimer(hwnd,0,0,0);
}
else if (wParam == ID_MOUSELEAVE)
{
TME_MouseLeaveTimer(hwnd,0,0,0);
}
}
break;
#endif
}
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 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 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, (DWORD)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.
#ifdef IEWIN31_25
SetTimer(lpTME->hwndTrack, ID_MOUSELEAVE, TME_MOUSELEAVE_TIME, NULL);
#else
SetTimer(lpTME->hwndTrack, ID_MOUSELEAVE, TME_MOUSELEAVE_TIME, (TIMERPROC)TME_MouseLeaveTimer);
#endif
}
void NEAR TME_ResetMouseHover(LPTRACKMOUSEEVENT lpTME, LPTMEDATA lpTMEdata)
{
UINT 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 = (UINT)lpTME->dwHoverTime;
if (!dwMouseHoverTime || (dwMouseHoverTime == HOVER_DEFAULT))
dwMouseHoverTime = 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), GetSystemMetrics(SM_CXDOUBLECLK)/2, GetSystemMetrics(SM_CXDOUBLECLK)/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;
#ifdef IEWIN31_25
SetTimer(lpTME->hwndTrack, ID_MOUSEHOVER, (UINT)dwMouseHoverTime, (TIMERPROC)NULL);
#else
SetTimer(lpTME->hwndTrack, ID_MOUSEHOVER, (UINT)dwMouseHoverTime, (TIMERPROC)TME_MouseHoverTimer);
#endif
}
// --------------------------------------------------------------------------
// 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);
}