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