Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

593 lines
17 KiB

  1. //---------------------------------------------------------------------------
  2. //
  3. // TrackME.C (TrackMouseEvent)
  4. //
  5. // Created by: Sankar on 1/24/96
  6. //
  7. // What:
  8. // This emulates the TrackMouseEvent() API for the Nashville project
  9. // in comctl32.dll
  10. //
  11. // How:
  12. // This subclasses the given window to get mouse messages and uses a
  13. // high frequency timer to learn about mouse leaves.
  14. //
  15. //---------------------------------------------------------------------------
  16. #include "ctlspriv.h"
  17. #ifdef TrackMouseEvent
  18. #undef TrackMouseEvent
  19. #endif
  20. extern const TCHAR FAR c_szTMEdata[];
  21. extern DWORD g_dwHoverSelectTimeout;
  22. #define ID_MOUSEHOVER 0xFFFFFFF0L
  23. #define ID_MOUSELEAVE 0xFFFFFFF1L
  24. #define TME_MOUSELEAVE_TIME (GetDoubleClickTime() / 5)
  25. #define IsKeyDown(Key) (GetKeyState(Key) & 0x8000)
  26. // This is the structure whose pointer gets added as a property of a window
  27. // being tracked.
  28. typedef struct tagTMEDATA {
  29. TRACKMOUSEEVENT TrackMouseEvent;
  30. RECT rcMouseHover; //In screen co-ordinates.
  31. } TMEDATA, FAR *LPTMEDATA;
  32. void NEAR TME_ResetMouseHover(LPTRACKMOUSEEVENT lpTME, LPTMEDATA lpTMEdata);
  33. LRESULT CALLBACK TME_SubclassProc(HWND hwnd, UINT message, WPARAM wParam,
  34. LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData);
  35. LPTMEDATA NEAR GetTMEdata(HWND hwnd)
  36. {
  37. LPTMEDATA lpTMEdata;
  38. GetWindowSubclass(hwnd, TME_SubclassProc, 0, (ULONG_PTR *)&lpTMEdata);
  39. return lpTMEdata;
  40. }
  41. void NEAR TME_PostMouseLeave(HWND hwnd)
  42. {
  43. PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L);
  44. }
  45. void NEAR TME_CancelMouseLeave(LPTMEDATA lpTMEdata)
  46. {
  47. if(!(lpTMEdata->TrackMouseEvent.dwFlags & TME_LEAVE))
  48. return;
  49. // Remove the flag.
  50. lpTMEdata->TrackMouseEvent.dwFlags &= ~(TME_LEAVE);
  51. // We leave the timer set here since our hover implementation uses it too.
  52. // TME_CancelTracking will kill it later.
  53. }
  54. void NEAR TME_CancelMouseHover(LPTMEDATA lpTMEdata)
  55. {
  56. if(!(lpTMEdata->TrackMouseEvent.dwFlags & TME_HOVER))
  57. return;
  58. lpTMEdata->TrackMouseEvent.dwFlags &= ~(TME_HOVER);
  59. KillTimer(lpTMEdata->TrackMouseEvent.hwndTrack, ID_MOUSEHOVER);
  60. }
  61. void NEAR TME_CancelTracking(LPTMEDATA lpTMEdata)
  62. {
  63. HWND hwndTrack;
  64. //If either MouseLeave or MouseHover is ON, don't cancel tracking.
  65. if(lpTMEdata->TrackMouseEvent.dwFlags & (TME_HOVER | TME_LEAVE))
  66. return;
  67. hwndTrack = lpTMEdata->TrackMouseEvent.hwndTrack;
  68. // Uninstall our subclass callback.
  69. RemoveWindowSubclass(hwndTrack, TME_SubclassProc, 0);
  70. // Kill the mouseleave timer.
  71. KillTimer(hwndTrack, ID_MOUSELEAVE);
  72. // Free the tracking data.
  73. LocalFree((HANDLE)lpTMEdata);
  74. }
  75. void NEAR TME_RemoveAllTracking(LPTMEDATA lpTMEdata)
  76. {
  77. TME_CancelMouseLeave(lpTMEdata);
  78. TME_CancelMouseHover(lpTMEdata);
  79. TME_CancelTracking(lpTMEdata);
  80. }
  81. //---------------------------------------------------------------------------
  82. //
  83. // TME_MouseHasLeft()
  84. // The mouse has left the region being tracked. Send the MOUSELEAVE msg
  85. // and then cancel all tracking.
  86. //
  87. //---------------------------------------------------------------------------
  88. void NEAR TME_MouseHasLeft(LPTMEDATA lpTMEdata)
  89. {
  90. DWORD dwFlags;
  91. //Is WM_MOUSELEAVE notification requied?
  92. if((dwFlags = lpTMEdata->TrackMouseEvent.dwFlags) & TME_LEAVE)
  93. TME_PostMouseLeave(lpTMEdata->TrackMouseEvent.hwndTrack); //Then, do it!
  94. // Cancel all the tracking since the mouse has left.
  95. TME_RemoveAllTracking(lpTMEdata);
  96. }
  97. // --------------------------------------------------------------------------
  98. //
  99. // TME_SubclassWndProc()
  100. //
  101. // The subclass proc used for TrackMouseEvent()...!
  102. //
  103. // --------------------------------------------------------------------------
  104. LRESULT CALLBACK TME_SubclassProc(HWND hwnd, UINT message, WPARAM wParam,
  105. LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData)
  106. {
  107. LPTMEDATA lpTMEdata = (LPTMEDATA)dwRefData;
  108. ASSERT(lpTMEdata);
  109. switch(message)
  110. {
  111. case WM_DESTROY:
  112. case WM_NCDESTROY:
  113. TME_RemoveAllTracking(lpTMEdata);
  114. break;
  115. case WM_ENTERMENULOOP:
  116. // If the window being tracked enters menu mode, then we need to
  117. // act asif the mouse has left.
  118. // NOTE: Because when we are in menu mode, the SCREEN_CAPTURE has occurred
  119. // and we don't see any mouse moves. This is the only way out!
  120. // Post mouse leave and cancel all tracking!
  121. TME_MouseHasLeft(lpTMEdata);
  122. break;
  123. case WM_LBUTTONDOWN:
  124. case WM_LBUTTONUP:
  125. case WM_MBUTTONDOWN:
  126. case WM_MBUTTONUP:
  127. case WM_RBUTTONDOWN:
  128. case WM_RBUTTONUP:
  129. case WM_NCLBUTTONDOWN:
  130. case WM_NCLBUTTONUP:
  131. case WM_NCMBUTTONDOWN:
  132. case WM_NCMBUTTONUP:
  133. case WM_NCRBUTTONDOWN:
  134. case WM_NCRBUTTONUP:
  135. //Whenever there is a mouse click, reset mouse hover.
  136. if(lpTMEdata->TrackMouseEvent.dwFlags & TME_HOVER)
  137. TME_ResetMouseHover(&(lpTMEdata->TrackMouseEvent), lpTMEdata);
  138. break;
  139. case WM_NCMOUSEMOVE:
  140. TME_MouseHasLeft(lpTMEdata);
  141. break;
  142. case WM_MOUSEMOVE:
  143. {
  144. POINT Pt;
  145. Pt.x = GET_X_LPARAM(lParam);
  146. Pt.y = GET_Y_LPARAM(lParam);
  147. ClientToScreen(hwnd, &Pt);
  148. //Check if the mouse is within the hover rect.
  149. if((lpTMEdata->TrackMouseEvent.dwFlags & TME_HOVER) &&
  150. !PtInRect(&(lpTMEdata->rcMouseHover), Pt))
  151. TME_ResetMouseHover(&(lpTMEdata->TrackMouseEvent), lpTMEdata);
  152. }
  153. break;
  154. }
  155. return DefSubclassProc(hwnd, message, wParam, lParam);
  156. }
  157. // --------------------------------------------------------------------------
  158. //
  159. // TME_CheckInWindow()
  160. //
  161. // This get the current cursor position and checks if it still lies in the
  162. // "valid" area.
  163. // Returns TRUE, if it lies in the valid area.
  164. // FALSE, otherwise.
  165. //
  166. // --------------------------------------------------------------------------
  167. BOOL NEAR TME_CheckInWindow(LPTRACKMOUSEEVENT lpTME, LPPOINT lpPt)
  168. {
  169. POINT pt;
  170. HWND hwnd; // Given window.
  171. HWND hwndPt; //Window from the given point.
  172. HWND hwndCapture;
  173. hwnd = lpTME->hwndTrack; //Given window handle.
  174. //See if anyone has captured the mouse input.
  175. if((hwndCapture = GetCapture()) && IsWindow(hwndCapture))
  176. {
  177. // If tracking is required for a window other than the one that
  178. // has the capture, forget it! It is not possible!
  179. if(hwndCapture != hwnd)
  180. return(FALSE);
  181. }
  182. GetCursorPos(&pt); //Get cursor point in screen co-ordinates.
  183. if (!hwndCapture)
  184. {
  185. hwndPt = WindowFromPoint(pt);
  186. if (!hwndPt || !IsWindow(hwndPt) || (hwnd != hwndPt))
  187. return FALSE;
  188. if (SendMessage(hwnd, WM_NCHITTEST, 0,
  189. MAKELPARAM((SHORT)pt.x, (SHORT)pt.y)) != HTCLIENT)
  190. {
  191. return FALSE;
  192. }
  193. }
  194. // The current point falls on the same area of the same window.
  195. // It is a valid location.
  196. if (lpPt)
  197. *lpPt = pt;
  198. return(TRUE);
  199. }
  200. // --------------------------------------------------------------------------
  201. // TME_MouseLeaveTimer()
  202. //
  203. // Timer callback for WM_MOUSELEAVE generation and cancelling HOVER!
  204. //
  205. // --------------------------------------------------------------------------
  206. VOID CALLBACK TME_MouseLeaveTimer(HWND hwnd, UINT msg, UINT_PTR id, DWORD dwTime)
  207. {
  208. LPTMEDATA lpTMEdata;
  209. if(!(lpTMEdata = GetTMEdata(hwnd)))
  210. return;
  211. // YIELD!!!
  212. if(TME_CheckInWindow(&(lpTMEdata->TrackMouseEvent), NULL))
  213. return; //The mouse is still in the valid region. So, do nothing.
  214. if (!IsWindow(hwnd))
  215. return;
  216. //The mouse has left the valid region. So, post mouse-leave if requested
  217. //Because we are cancelling mouse-leave, we need to cancel mouse-hover too!
  218. // There can be no hover tracking, if the mouse has already left!
  219. TME_MouseHasLeft(lpTMEdata);
  220. }
  221. WPARAM NEAR GetMouseKeyFlags()
  222. {
  223. WPARAM wParam = 0;
  224. if (IsKeyDown(VK_LBUTTON))
  225. wParam |= MK_LBUTTON;
  226. if (IsKeyDown(VK_RBUTTON))
  227. wParam |= MK_RBUTTON;
  228. if (IsKeyDown(VK_MBUTTON))
  229. wParam |= MK_MBUTTON;
  230. if (IsKeyDown(VK_SHIFT))
  231. wParam |= MK_SHIFT;
  232. if (IsKeyDown(VK_CONTROL))
  233. wParam |= MK_CONTROL;
  234. return wParam;
  235. }
  236. // --------------------------------------------------------------------------
  237. // TME_MouseHoverTimer()
  238. //
  239. // Timer callback for WM_MOUSEHOVER/WM_NCMOUSEHOVER generation.
  240. //
  241. // --------------------------------------------------------------------------
  242. VOID CALLBACK TME_MouseHoverTimer(HWND hwnd, UINT msg, UINT_PTR id, DWORD dwTime)
  243. {
  244. POINT pt;
  245. WPARAM wParam;
  246. LPTMEDATA lpTMEdata;
  247. if (!(lpTMEdata = GetTMEdata(hwnd)))
  248. return;
  249. //BOGUS: we can not detect hwndSysModal from here!
  250. //Also, tracking is for a per-window basis now!
  251. //
  252. // BOGUS: We don't have to worry about JournalPlayback?
  253. //pt = fJournalPlayback? Lpq(hwnd->hq)->ptLast : ptTrueCursor;
  254. // YIELD!!!
  255. if(!TME_CheckInWindow(&(lpTMEdata->TrackMouseEvent), &pt))
  256. {
  257. // Mouse has left the valid region of the window. So, cancel all
  258. // the tracking.
  259. TME_MouseHasLeft(lpTMEdata);
  260. return;
  261. }
  262. if (!IsWindow(hwnd))
  263. return;
  264. if (!PtInRect(&(lpTMEdata->rcMouseHover), pt))
  265. {
  266. // Mouse has gone out of the hover rectangle. Reset the hovering.
  267. TME_ResetMouseHover(&(lpTMEdata->TrackMouseEvent), lpTMEdata);
  268. return;
  269. }
  270. //
  271. // set up to check the tolerance and
  272. //
  273. wParam = GetMouseKeyFlags();
  274. ScreenToClient(hwnd, &pt);
  275. //Mouse is still within the hover rectangle. Let's post hover msg
  276. PostMessage(hwnd, WM_MOUSEHOVER, wParam, MAKELPARAM(pt.x, pt.y));
  277. //And then cancel the hovering.
  278. TME_CancelMouseHover(lpTMEdata);
  279. TME_CancelTracking(lpTMEdata); //Cancel the tracking, if needed.
  280. }
  281. BOOL NEAR TME_SubclassWnd(LPTMEDATA lpTMEdata)
  282. {
  283. BOOL fResult;
  284. fResult = SetWindowSubclass(lpTMEdata->TrackMouseEvent.hwndTrack,
  285. TME_SubclassProc, 0, (ULONG_PTR)lpTMEdata);
  286. ASSERT(fResult);
  287. return fResult;
  288. }
  289. void NEAR TME_ResetMouseLeave(LPTRACKMOUSEEVENT lpTME, LPTMEDATA lpTMEdata)
  290. {
  291. //See if already MouseLeave is being tracked.
  292. if(lpTMEdata->TrackMouseEvent.dwFlags & TME_LEAVE)
  293. return; // Nothing else to do.
  294. //Else, set the flag.
  295. lpTMEdata ->TrackMouseEvent.dwFlags |= TME_LEAVE;
  296. //Set the high frequency Timer.
  297. SetTimer(lpTME->hwndTrack, ID_MOUSELEAVE, TME_MOUSELEAVE_TIME, TME_MouseLeaveTimer);
  298. }
  299. void NEAR TME_ResetMouseHover(LPTRACKMOUSEEVENT lpTME, LPTMEDATA lpTMEdata)
  300. {
  301. DWORD dwMouseHoverTime;
  302. POINT pt;
  303. // Even if the hover tracking is already happening, the caller might
  304. // change the timer value, restart the timer or change the hover
  305. // rectangle.
  306. lpTMEdata->TrackMouseEvent.dwFlags |= TME_HOVER;
  307. dwMouseHoverTime = lpTME->dwHoverTime;
  308. if (!dwMouseHoverTime || (dwMouseHoverTime == HOVER_DEFAULT))
  309. dwMouseHoverTime = (g_dwHoverSelectTimeout ? g_dwHoverSelectTimeout : GetDoubleClickTime()*4/5); // BUGBUG: Can't we remember this?
  310. GetCursorPos(&pt);
  311. //
  312. // update the tolerance rectangle for the hover window.
  313. //
  314. *((POINT *)&(lpTMEdata->rcMouseHover.left)) = *((POINT *)&(lpTMEdata->rcMouseHover.right)) = pt;
  315. //BOGUS: Can we use globals to remeber these metrics. What about NT?
  316. InflateRect(&(lpTMEdata->rcMouseHover), g_cxDoubleClk/2, g_cyDoubleClk/2);
  317. // We need to remember the timer interval we are setting. This value
  318. // needs to be returned when TME_QUERY is used.
  319. lpTME->dwHoverTime = dwMouseHoverTime;
  320. lpTMEdata->TrackMouseEvent.dwHoverTime = dwMouseHoverTime;
  321. SetTimer(lpTME->hwndTrack, ID_MOUSEHOVER, dwMouseHoverTime, TME_MouseHoverTimer);
  322. }
  323. // --------------------------------------------------------------------------
  324. // QueryTrackMouseEvent()
  325. //
  326. // Fills in a TRACKMOUSEEVENT structure describing current tracking state
  327. // for a given window. The given window is in lpTME->hwndTrack.
  328. //
  329. // --------------------------------------------------------------------------
  330. BOOL NEAR QueryTrackMouseEvent(LPTRACKMOUSEEVENT lpTME)
  331. {
  332. HWND hwndTrack;
  333. LPTMEDATA lpTMEdata;
  334. //
  335. // if there isn't anything being tracked get out
  336. //
  337. if((!(hwndTrack = lpTME->hwndTrack)) || !IsWindow(hwndTrack))
  338. goto Sorry;
  339. if(!(lpTMEdata = GetTMEdata(hwndTrack)))
  340. goto Sorry;
  341. if(!(lpTMEdata->TrackMouseEvent.dwFlags & (TME_HOVER | TME_LEAVE)))
  342. goto Sorry;
  343. //
  344. // fill in the requested information
  345. //
  346. lpTME->dwFlags = lpTMEdata->TrackMouseEvent.dwFlags;
  347. if (lpTMEdata->TrackMouseEvent.dwFlags & TME_HOVER)
  348. lpTME->dwHoverTime = lpTMEdata->TrackMouseEvent.dwHoverTime;
  349. else
  350. lpTME->dwHoverTime = 0;
  351. goto Done;
  352. Sorry:
  353. // zero out the struct
  354. lpTME->dwFlags = 0;
  355. lpTME->hwndTrack = NULL;
  356. lpTME->dwHoverTime = 0;
  357. Done:
  358. return TRUE;
  359. }
  360. // --------------------------------------------------------------------------
  361. // EmulateTrackMouseEvent()
  362. //
  363. // emulate API for requesting extended mouse notifications (hover, leave...)
  364. //
  365. // --------------------------------------------------------------------------
  366. BOOL WINAPI EmulateTrackMouseEvent(LPTRACKMOUSEEVENT lpTME)
  367. {
  368. HWND hwnd;
  369. DWORD dwFlags;
  370. LPTMEDATA lpTMEdata;
  371. if (lpTME->dwFlags & ~TME_VALID)
  372. return FALSE;
  373. #ifdef TME_NONCLIENT
  374. //
  375. // this implementation does not handle TME_NONCLIENT (anymore)
  376. // we agreed with the NT team to rip it out until the system uses it...
  377. //
  378. if (lpTME->dwFlags & TME_NONCLIENT)
  379. return FALSE;
  380. #endif
  381. //
  382. // implement queries separately
  383. //
  384. if (lpTME->dwFlags & TME_QUERY)
  385. return QueryTrackMouseEvent(lpTME);
  386. //
  387. // Check the validity of the request.
  388. //
  389. hwnd = lpTME->hwndTrack;
  390. dwFlags = lpTME->dwFlags;
  391. if (!IsWindow(hwnd))
  392. return FALSE;
  393. // Check if the mouse is currently in a valid position
  394. // Use GetCursorPos() to get the mouse position and then check if
  395. // it lies within the client/non-client portion of the window as
  396. // defined in this call;
  397. // YIELD!!!
  398. if(!TME_CheckInWindow(lpTME, NULL))
  399. {
  400. //If the mouse leave is requested when the mouse is already outside
  401. // the window, then generate one mouse leave immly.
  402. if((dwFlags & TME_LEAVE) && !(dwFlags & TME_CANCEL))
  403. TME_PostMouseLeave(hwnd);
  404. //Because it is an invalid request, we return immly.
  405. return(TRUE);
  406. }
  407. if (!IsWindow(hwnd))
  408. return FALSE;
  409. //It is a valid request, either to install or remove tracking.
  410. //See if we already have tracking for this window.
  411. if(!(lpTMEdata = GetTMEdata(hwnd)))
  412. {
  413. //We are not tracking this window already.
  414. if(dwFlags & TME_CANCEL)
  415. return(TRUE); //There is nothing to cancel; Ignore!
  416. //Do they want any tracking at all?
  417. ASSERT(dwFlags & (TME_HOVER | TME_LEAVE));
  418. //Allocate global mem to remember the tracking data
  419. if(!(lpTMEdata = (LPTMEDATA)LocalAlloc(LPTR, sizeof(TMEDATA))))
  420. return(FALSE);
  421. // copy in the hwnd
  422. lpTMEdata->TrackMouseEvent.hwndTrack = lpTME->hwndTrack;
  423. // Make sure our subclass callback is installed.
  424. if (!TME_SubclassWnd(lpTMEdata))
  425. {
  426. TME_CancelTracking(lpTMEdata);
  427. return(FALSE);
  428. }
  429. }
  430. //Else fall through!
  431. if(dwFlags & TME_CANCEL)
  432. {
  433. if(dwFlags & TME_HOVER)
  434. TME_CancelMouseHover(lpTMEdata);
  435. if(dwFlags & TME_LEAVE)
  436. TME_CancelMouseLeave(lpTMEdata);
  437. // If both hover and leave are cancelled, then we don't need any
  438. // tracking.
  439. TME_CancelTracking(lpTMEdata);
  440. return(TRUE); // Cancelled whatever they asked for.
  441. }
  442. if(dwFlags & TME_HOVER)
  443. TME_ResetMouseHover(lpTME, lpTMEdata);
  444. if(dwFlags & TME_LEAVE)
  445. TME_ResetMouseLeave(lpTME, lpTMEdata);
  446. return(TRUE);
  447. }
  448. typedef BOOL (WINAPI* PFNTME)(LPTRACKMOUSEEVENT);
  449. PFNTME g_pfnTME = NULL;
  450. // --------------------------------------------------------------------------
  451. // _TrackMouseEvent() entrypoint
  452. //
  453. // calls TrackMouseEvent if present, otherwise uses EmulateTrackMouseEvent
  454. //
  455. // --------------------------------------------------------------------------
  456. BOOL WINAPI _TrackMouseEvent(LPTRACKMOUSEEVENT lpTME)
  457. {
  458. if (!g_pfnTME)
  459. {
  460. HMODULE hmod = GetModuleHandle(TEXT("USER32"));
  461. if (hmod)
  462. g_pfnTME = (PFNTME)GetProcAddress(hmod, "TrackMouseEvent");
  463. if (!g_pfnTME)
  464. g_pfnTME = EmulateTrackMouseEvent;
  465. }
  466. return g_pfnTME(lpTME);
  467. }