Leaked source code of windows server 2003
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.

988 lines
34 KiB

  1. /**************************** Module Header ********************************\
  2. * Module Name: mnloop.c
  3. *
  4. * Copyright (c) 1985 - 1999, Microsoft Corporation
  5. *
  6. * Menu Modal Loop Routines
  7. *
  8. * History:
  9. * 10-10-90 JimA Cleanup.
  10. * 03-18-91 IanJa Window revalidation added
  11. \***************************************************************************/
  12. #include "precomp.h"
  13. #pragma hdrstop
  14. /***************************************************************************\
  15. * xxxMNRemoveMessage
  16. *
  17. * History
  18. * 11/23/96 GerardoB Created
  19. \***************************************************************************/
  20. BOOL xxxMNRemoveMessage (UINT message1, UINT message2)
  21. {
  22. MSG msg;
  23. if (!xxxPeekMessage(&msg, NULL, 0, 0, PM_NOYIELD | PM_NOREMOVE)) {
  24. return FALSE;
  25. }
  26. if ((msg.message == message1) || (msg.message == message2)) {
  27. UserAssert(msg.message != 0);
  28. xxxPeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE);
  29. return TRUE;
  30. } else {
  31. return FALSE;
  32. }
  33. }
  34. /***************************************************************************\
  35. * xxxHandleMenuMessages
  36. *
  37. * History:
  38. \***************************************************************************/
  39. BOOL xxxHandleMenuMessages(
  40. LPMSG lpmsg,
  41. PMENUSTATE pMenuState,
  42. PPOPUPMENU ppopupmenu)
  43. {
  44. DWORD ch;
  45. ULONG_PTR cmdHitArea;
  46. UINT cmdItem;
  47. LPARAM lParam;
  48. BOOL fThreadLock = FALSE;
  49. TL tlpwndHitArea;
  50. TL tlpwndT;
  51. POINT pt;
  52. PWND pwnd;
  53. RECT rc;
  54. /*
  55. * Paranoia. Let's bail up front if we don't have a menu.
  56. * Some code checks for NULL spmenu, other parts assume it's always not NULL
  57. * Use RIP_ERROR for a while to make sure this is OK
  58. */
  59. if (ppopupmenu->spmenu == NULL) {
  60. RIPMSG2(RIP_ERROR, "xxxHandleMenuMessages NULL spmenu. pMenuSate:%p ppopupmenu:%p",
  61. pMenuState, ppopupmenu);
  62. return FALSE;
  63. }
  64. /*
  65. * Get things out of the structure so that we can access them quicker.
  66. */
  67. ch = (DWORD)lpmsg->wParam;
  68. lParam = lpmsg->lParam;
  69. /*
  70. * In this switch statement, we only look at messages we want to handle and
  71. * swallow. Messages we don't understand will get translated and
  72. * dispatched.
  73. */
  74. switch (lpmsg->message) {
  75. case WM_RBUTTONDOWN:
  76. case WM_NCRBUTTONDOWN:
  77. if (ppopupmenu->fRightButton) {
  78. goto HandleButtonDown;
  79. }
  80. /*
  81. * Fall through
  82. */
  83. case WM_RBUTTONDBLCLK:
  84. case WM_NCRBUTTONDBLCLK:
  85. /*
  86. * Right click outside the menu dismisses the menu
  87. * (we didn't use to do this for single right clicks on 4.0)
  88. */
  89. pMenuState->mnFocus = MOUSEHOLD;
  90. cmdHitArea = xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, MAKEPOINTS(lParam));
  91. if (cmdHitArea == MFMWFP_OFFMENU) {
  92. xxxMNDismiss(pMenuState);
  93. return TRUE;
  94. }
  95. /*
  96. * Do nothing on right clicks on the menu
  97. */
  98. if (!pMenuState->fModelessMenu) {
  99. xxxMNRemoveMessage(lpmsg->message, 0);
  100. }
  101. return TRUE;
  102. case WM_LBUTTONDOWN:
  103. case WM_NCLBUTTONDOWN:
  104. // Commented out due to TandyT whinings...
  105. // if ((ppopupmenu->trackPopupMenuFlags & TPM_RIGHTBUTTON))
  106. // break;
  107. HandleButtonDown:
  108. /*
  109. * Find out where this mouse down occurred.
  110. */
  111. pMenuState->mnFocus = MOUSEHOLD;
  112. pMenuState->ptMouseLast.x = GET_X_LPARAM(lParam);
  113. pMenuState->ptMouseLast.y = GET_Y_LPARAM(lParam);
  114. cmdHitArea = xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, MAKEPOINTS(lParam));
  115. /*
  116. * Thread lock this if it is a pwnd. This certainly isn't the way
  117. * you'd implement this if you had locking to begin with.
  118. */
  119. fThreadLock = IsMFMWFPWindow(cmdHitArea);
  120. if (fThreadLock) {
  121. ThreadLock((PWND)cmdHitArea, &tlpwndHitArea);
  122. }
  123. /*
  124. * If this is a drag and drop menu, remember the mouse
  125. * position and the hit test results.
  126. */
  127. if (pMenuState->fDragAndDrop) {
  128. pMenuState->ptButtonDown = pMenuState->ptMouseLast;
  129. pMenuState->uButtonDownIndex = cmdItem;
  130. LockMFMWFPWindow(&pMenuState->uButtonDownHitArea, cmdHitArea);
  131. }
  132. /*
  133. * Modeless menus don't capture the mouse so we might not see
  134. * the button up. We also release capture when sending the
  135. * WM_MENUDODRAGDROP message. So we want to remember what
  136. * mouse button went down.
  137. */
  138. if (pMenuState->fDragAndDrop || pMenuState->fModelessMenu) {
  139. if (ch & MK_RBUTTON) {
  140. pMenuState->vkButtonDown = VK_RBUTTON;
  141. } else {
  142. pMenuState->vkButtonDown = VK_LBUTTON;
  143. }
  144. }
  145. if ((cmdHitArea == MFMWFP_OFFMENU) && (cmdItem == 0)) {
  146. //
  147. // Clicked in middle of nowhere, so terminate menus, and
  148. // let button pass through.
  149. CancelOut:
  150. xxxMNDismiss(pMenuState);
  151. goto Unlock;
  152. } else if (ppopupmenu->fHasMenuBar && (cmdHitArea == MFMWFP_ALTMENU)) {
  153. //
  154. // Switching between menu bar & popup
  155. //
  156. xxxMNSwitchToAlternateMenu(ppopupmenu);
  157. cmdHitArea = MFMWFP_NOITEM;
  158. }
  159. if (cmdHitArea == MFMWFP_NOITEM) {
  160. //
  161. // On menu bar (system or main)
  162. //
  163. xxxMNButtonDown(ppopupmenu, pMenuState, cmdItem, TRUE);
  164. } else {
  165. // On popup window menu
  166. UserAssert(cmdHitArea);
  167. xxxSendMessage((PWND)cmdHitArea, MN_BUTTONDOWN, cmdItem, 0L);
  168. }
  169. /*
  170. * Swallow the message since we handled it.
  171. */
  172. /*
  173. * The excel guys change a wm_rbuttondown to a wm_lbuttondown message
  174. * in their message filter hook. Remove the message here or we'll
  175. * get in a nasty loop.
  176. *
  177. * We need to swallow msg32.message ONLY. It is possible for
  178. * the LBUTTONDOWN to not be at the head of the input queue.
  179. * If not, we will swallow a WM_MOUSEMOVE or something else like
  180. * that. The reason Peek() doesn't need to check the range
  181. * is because we've already Peek(PM_NOYIELD'ed) before, which
  182. * locked the sys queue.
  183. */
  184. if (!pMenuState->fModelessMenu) {
  185. xxxMNRemoveMessage(lpmsg->message, WM_RBUTTONDOWN);
  186. }
  187. goto Unlock;
  188. case WM_MOUSEMOVE:
  189. case WM_NCMOUSEMOVE:
  190. /*
  191. * Is the user starting to drag?
  192. */
  193. if (pMenuState->fDragAndDrop
  194. && pMenuState->fButtonDown
  195. && !pMenuState->fDragging
  196. && !pMenuState->fButtonAlwaysDown
  197. && (pMenuState->uButtonDownHitArea != MFMWFP_OFFMENU)) {
  198. /*
  199. * We expect the mouse to go down on a menu item before a drag can start
  200. */
  201. UserAssert(!ppopupmenu->fFirstClick);
  202. /*
  203. * Calculate drag detect rectangle using the position the mouse went
  204. * down on
  205. */
  206. *(LPPOINT)&rc.left = pMenuState->ptButtonDown;
  207. *(LPPOINT)&rc.right = pMenuState->ptButtonDown;
  208. InflateRect(&rc, SYSMET(CXDRAG), SYSMET(CYDRAG));
  209. pt.x = GET_X_LPARAM(lParam);
  210. pt.y = GET_Y_LPARAM(lParam);
  211. /*
  212. * If we've moved outside the drag rect, then the user is dragging
  213. */
  214. if (!PtInRect(&rc, pt)) {
  215. /*
  216. * Post a message so we'll finish processing this message
  217. * and get out of here before telling the app that the user
  218. * is dragging
  219. */
  220. pwnd = GetMenuStateWindow(pMenuState);
  221. if (pwnd != NULL) {
  222. pMenuState->fDragging = TRUE;
  223. _PostMessage(pwnd, MN_DODRAGDROP, 0, 0);
  224. } else {
  225. RIPMSG0(RIP_ERROR, "xxxMNMouseMove. Unble to post MN_DODGRAGDROP");
  226. }
  227. }
  228. } /* if (pMenuState->fDragAndDrop */
  229. xxxMNMouseMove(ppopupmenu, pMenuState, MAKEPOINTS(lParam));
  230. return TRUE;
  231. case WM_RBUTTONUP:
  232. case WM_NCRBUTTONUP:
  233. if (ppopupmenu->fRightButton) {
  234. goto HandleButtonUp;
  235. }
  236. /*
  237. * If the button is down, simply swallow this message
  238. */
  239. if (pMenuState->fButtonDown) {
  240. if (!pMenuState->fModelessMenu) {
  241. xxxMNRemoveMessage(lpmsg->message, 0);
  242. }
  243. return TRUE;
  244. }
  245. // New feature for shell start menu -- notify when a right click
  246. // occurs on a menu item, and open a window of opportunity for
  247. // menus to recurse, allowing them to popup a context-sensitive
  248. // menu for that item. (jeffbog 9/28/95)
  249. //
  250. // NOTE: Though this feature was added for Nashville, it was enabled
  251. // by default for all apps on Win2K and exhibited no app compat
  252. // problems. Thus, it is assumed that no version checking is required;
  253. // moreoever, changing at this point is needlessly dangerous.
  254. if ((lpmsg->message == WM_RBUTTONUP) && !ppopupmenu->fNoNotify) {
  255. PPOPUPMENU ppopupActive;
  256. if ((ppopupmenu->spwndActivePopup != NULL)
  257. && (ppopupActive = ((PMENUWND)(ppopupmenu->spwndActivePopup))->ppopupmenu)
  258. && MNIsItemSelected(ppopupActive))
  259. {
  260. TL tlpwndNotify;
  261. ThreadLock( ppopupActive->spwndNotify, &tlpwndNotify );
  262. xxxSendMessage(ppopupActive->spwndNotify, WM_MENURBUTTONUP,
  263. ppopupActive->posSelectedItem, (LPARAM)PtoH(ppopupActive->spmenu));
  264. ThreadUnlock( &tlpwndNotify );
  265. }
  266. }
  267. break;
  268. case WM_LBUTTONUP:
  269. case WM_NCLBUTTONUP:
  270. // Commented out due to TandyT whinings...
  271. // if ((ppopupmenu->trackPopupMenuFlags & TPM_RIGHTBUTTON))
  272. // break;
  273. HandleButtonUp:
  274. if (!pMenuState->fButtonDown) {
  275. /*
  276. * Don't care about this mouse up since we never saw the button
  277. * down for some reason.
  278. */
  279. return TRUE;
  280. }
  281. /*
  282. * Cancel the dragging state, if any.
  283. */
  284. if (pMenuState->fDragAndDrop) {
  285. UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
  286. pMenuState->fDragging = FALSE;
  287. if (pMenuState->fIgnoreButtonUp) {
  288. pMenuState->fButtonDown =
  289. pMenuState->fIgnoreButtonUp = FALSE;
  290. return TRUE;
  291. }
  292. }
  293. /*
  294. * Find out where this mouse up occurred.
  295. */
  296. pMenuState->ptMouseLast.x = GET_X_LPARAM(lParam);
  297. pMenuState->ptMouseLast.y = GET_Y_LPARAM(lParam);
  298. cmdHitArea = xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, MAKEPOINTS(lParam));
  299. /*
  300. * If this is not true, some the code below won't work right.
  301. */
  302. UserAssert((cmdHitArea != MFMWFP_OFFMENU) || (cmdItem == 0));
  303. UserAssert(cmdHitArea != 0x0000FFFF);
  304. /*
  305. * Thread lock this if it is a pwnd. This certainly isn't the way
  306. * you'd implement this if you had locking to begin with.
  307. */
  308. fThreadLock = IsMFMWFPWindow(cmdHitArea);
  309. if (fThreadLock) {
  310. ThreadLock((PWND)cmdHitArea, &tlpwndHitArea);
  311. }
  312. if (ppopupmenu->fHasMenuBar) {
  313. if (((cmdHitArea == MFMWFP_OFFMENU) && (cmdItem == 0)) ||
  314. ((cmdHitArea == MFMWFP_NOITEM) && ppopupmenu->fIsSysMenu && ppopupmenu->fToggle))
  315. // Button up occurred in some random spot. Terminate
  316. // menus and swallow the message.
  317. goto CancelOut;
  318. } else {
  319. if ((cmdHitArea == MFMWFP_OFFMENU) && (cmdItem == 0)) {
  320. if (!ppopupmenu->fFirstClick) {
  321. //
  322. // User upclicked in some random spot. Terminate
  323. // menus and don't swallow the message.
  324. //
  325. //
  326. // Don't do anything with HWND here cuz the window is
  327. // destroyed after this SendMessage().
  328. //
  329. // DONTREVALIDATE();
  330. ThreadLock(ppopupmenu->spwndPopupMenu, &tlpwndT);
  331. xxxSendMessage(ppopupmenu->spwndPopupMenu, MN_CANCELMENUS, 0, 0);
  332. ThreadUnlock(&tlpwndT);
  333. goto Unlock;
  334. }
  335. }
  336. ppopupmenu->fFirstClick = FALSE;
  337. }
  338. if (cmdHitArea == MFMWFP_NOITEM) {
  339. //
  340. // This is a system menu or a menu bar and the button up
  341. // occurred on the system menu or on a menu bar item.
  342. //
  343. xxxMNButtonUp(ppopupmenu, pMenuState, cmdItem, 0);
  344. } else if ((cmdHitArea != MFMWFP_OFFMENU) && (cmdHitArea != MFMWFP_ALTMENU)) {
  345. //
  346. // Warning: It's common for the popup to go away during the
  347. // processing of this message, so don't add any code that
  348. // messes with hwnd after this call!
  349. //
  350. // DONTREVALIDATE();
  351. //
  352. // We send lParam (that has the mouse co-ords ) for the app
  353. // to get it in its SC_RESTORE/SC_MINIMIZE messages 3.0
  354. // compat
  355. //
  356. xxxSendMessage((PWND)cmdHitArea, MN_BUTTONUP, (DWORD)cmdItem, lParam);
  357. } else {
  358. pMenuState->fButtonDown =
  359. pMenuState->fButtonAlwaysDown = FALSE;
  360. }
  361. Unlock:
  362. if (fThreadLock)
  363. ThreadUnlock(&tlpwndHitArea);
  364. return TRUE;
  365. case WM_LBUTTONDBLCLK:
  366. case WM_NCLBUTTONDBLCLK:
  367. // Commented out due to TandyT whinings...
  368. // if (ppopup->fRightButton)
  369. // break;
  370. pMenuState->mnFocus = MOUSEHOLD;
  371. cmdHitArea = xxxMNFindWindowFromPoint(ppopupmenu, &cmdItem, MAKEPOINTS(lParam));
  372. if ((cmdHitArea == MFMWFP_OFFMENU) && (cmdItem == 0)) {
  373. // Dbl-clicked in middle of nowhere, so terminate menus, and
  374. // let button pass through.
  375. xxxMNDismiss(pMenuState);
  376. return TRUE;
  377. } else if (ppopupmenu->fHasMenuBar && (cmdHitArea == MFMWFP_ALTMENU)) {
  378. //
  379. // BOGUS
  380. // TREAT LIKE BUTTON DOWN since we didn't dblclk on same item.
  381. //
  382. xxxMNSwitchToAlternateMenu(ppopupmenu);
  383. cmdHitArea = MFMWFP_NOITEM;
  384. }
  385. if (cmdHitArea == MFMWFP_NOITEM)
  386. xxxMNDoubleClick(pMenuState, ppopupmenu, cmdItem);
  387. else {
  388. UserAssert(cmdHitArea);
  389. ThreadLock((PWND)cmdHitArea, &tlpwndHitArea);
  390. xxxSendMessage((PWND)cmdHitArea, MN_DBLCLK,
  391. (DWORD)cmdItem, 0L);
  392. ThreadUnlock(&tlpwndHitArea);
  393. }
  394. return TRUE;
  395. case WM_KEYDOWN:
  396. case WM_SYSKEYDOWN:
  397. /*
  398. * If mouse button is down, ignore keyboard input (fix #3899, IanJa)
  399. */
  400. if (pMenuState->fButtonDown && (ch != VK_F1)) {
  401. /*
  402. * Check if the user wants to cancel dragging.
  403. */
  404. if (pMenuState->fDragging && (ch == VK_ESCAPE)) {
  405. RIPMSG0(RIP_WARNING, "xxxHandleMenuMessages: ESC while dragging");
  406. pMenuState->fIgnoreButtonUp = TRUE;
  407. }
  408. return TRUE;
  409. }
  410. pMenuState->mnFocus = KEYBDHOLD;
  411. switch (ch) {
  412. case VK_UP:
  413. case VK_DOWN:
  414. case VK_LEFT:
  415. case VK_RIGHT:
  416. case VK_RETURN:
  417. case VK_CANCEL:
  418. case VK_ESCAPE:
  419. case VK_MENU:
  420. case VK_F10:
  421. case VK_F1:
  422. if (ppopupmenu->spwndActivePopup) {
  423. ThreadLockAlways(ppopupmenu->spwndActivePopup, &tlpwndT);
  424. xxxSendMessage(ppopupmenu->spwndActivePopup, lpmsg->message,
  425. ch, 0L);
  426. ThreadUnlock(&tlpwndT);
  427. } else {
  428. xxxMNKeyDown(ppopupmenu, pMenuState, (UINT)ch);
  429. }
  430. break;
  431. case VK_TAB:
  432. /*
  433. * People hit the ALT key now just to turn underlines ON in dialogs.
  434. * This throws them into "invisible menu mode". If they hit any char
  435. * at that point, we'll bail in xxxMNChar. But not so if they hit ctrl-tab,
  436. * which is used to navigate property sheets. So let's help them out.
  437. */
  438. if (ppopupmenu->fIsMenuBar && (ppopupmenu->spwndActivePopup == NULL)) {
  439. xxxMNDismiss(pMenuState);
  440. return TRUE;
  441. }
  442. /*
  443. * Fall through
  444. */
  445. default:
  446. TranslateKey:
  447. if (!pMenuState->fModelessMenu) {
  448. xxxTranslateMessage(lpmsg, 0);
  449. }
  450. break;
  451. }
  452. return TRUE;
  453. case WM_CHAR:
  454. case WM_SYSCHAR:
  455. if (ppopupmenu->spwndActivePopup) {
  456. ThreadLockAlways(ppopupmenu->spwndActivePopup, &tlpwndT);
  457. xxxSendMessage(ppopupmenu->spwndActivePopup, lpmsg->message,
  458. ch, 0L);
  459. ThreadUnlock(&tlpwndT);
  460. } else {
  461. xxxMNChar(ppopupmenu, pMenuState, (UINT)ch);
  462. }
  463. return TRUE;
  464. case WM_SYSKEYUP:
  465. /*
  466. * Ignore ALT and F10 keyup messages since they are handled on
  467. * the KEYDOWN message.
  468. */
  469. if (ch == VK_MENU || ch == VK_F10) {
  470. return TRUE;
  471. }
  472. /*
  473. ** fall thru **
  474. */
  475. case WM_KEYUP:
  476. /*
  477. * Do RETURNs on the up transition only
  478. */
  479. goto TranslateKey;
  480. case WM_SYSTIMER:
  481. /*
  482. * Prevent the caret from flashing by eating all WM_SYSTIMER messages.
  483. */
  484. return TRUE;
  485. default:
  486. break;
  487. }
  488. #if DBG
  489. /*
  490. * Nobody should be able to steal capture from modal menus.
  491. */
  492. if (!pMenuState->fModelessMenu
  493. && !pMenuState->fInDoDragDrop
  494. && !ExitMenuLoop (pMenuState, ppopupmenu) ) {
  495. UserAssert(PtiCurrent()->pq->QF_flags & QF_CAPTURELOCKED);
  496. UserAssert(PtiCurrent()->pq->spwndCapture == ppopupmenu->spwndNotify);
  497. }
  498. #endif
  499. /*
  500. * We didn't handle this message
  501. */
  502. return FALSE;
  503. }
  504. /***************************************************************************\
  505. * xxxEndMenuLoop
  506. *
  507. * Makes sure that the menu has been ended/canceled
  508. *
  509. * History:
  510. * 10/25/96 GerardoB Extracted from xxxMNLoop
  511. \***************************************************************************/
  512. void xxxEndMenuLoop (PMENUSTATE pMenuState, PPOPUPMENU ppopupmenu)
  513. {
  514. UserAssert(IsRootPopupMenu(ppopupmenu));
  515. if (ppopupmenu->fIsTrackPopup) {
  516. if (!ppopupmenu->fInCancel) {
  517. xxxMNDismiss(pMenuState);
  518. }
  519. } else {
  520. if (pMenuState->fUnderline) {
  521. TL tlpwnd;
  522. ThreadLock(ppopupmenu->spwndNotify, &tlpwnd);
  523. xxxDrawMenuBarUnderlines(ppopupmenu->spwndNotify, FALSE);
  524. ThreadUnlock(&tlpwnd);
  525. }
  526. if (!pMenuState->fInEndMenu) {
  527. xxxEndMenu(pMenuState);
  528. }
  529. }
  530. /*
  531. * If this is a modeless menu, make sure that the notification
  532. * window caption is drawn in the proper state
  533. */
  534. if (pMenuState->fModelessMenu && (ppopupmenu->spwndNotify != NULL)) {
  535. PWND pwndNotify = ppopupmenu->spwndNotify;
  536. PTHREADINFO pti = GETPTI(pwndNotify);
  537. BOOL fFrameOn = (pti->pq == gpqForeground)
  538. && (pti->pq->spwndActive == pwndNotify);
  539. TL tlpwndNotify;
  540. if (fFrameOn ^ !!TestWF(pwndNotify, WFFRAMEON)) {
  541. ThreadLockAlways(pwndNotify, &tlpwndNotify);
  542. xxxDWP_DoNCActivate(pwndNotify,
  543. (fFrameOn ? NCA_ACTIVE : NCA_FORCEFRAMEOFF),
  544. HRGN_FULL);
  545. ThreadUnlock(&tlpwndNotify);
  546. }
  547. }
  548. }
  549. /***************************************************************************\
  550. * xxxMenuLoop
  551. *
  552. * The menu processing entry point.
  553. * assumes: pMenuState->spwndMenu is the window which is the owner of the menu
  554. * we are processing.
  555. *
  556. * History:
  557. \***************************************************************************/
  558. int xxxMNLoop(
  559. PPOPUPMENU ppopupmenu,
  560. PMENUSTATE pMenuState,
  561. LPARAM lParam,
  562. BOOL fDblClk)
  563. {
  564. int hit;
  565. MSG msg;
  566. BOOL fSendIdle = TRUE;
  567. BOOL fInQueue = FALSE;
  568. DWORD menuState;
  569. PTHREADINFO pti;
  570. TL tlpwndT;
  571. UserAssert(IsRootPopupMenu(ppopupmenu));
  572. pMenuState->fInsideMenuLoop = TRUE;
  573. pMenuState->cmdLast = 0;
  574. pti = PtiCurrent();
  575. pMenuState->ptMouseLast.x = pti->ptLast.x;
  576. pMenuState->ptMouseLast.y = pti->ptLast.y;
  577. /*
  578. * Set flag to false, so that we can track if windows have
  579. * been activated since entering this loop.
  580. */
  581. pti->pq->QF_flags &= ~QF_ACTIVATIONCHANGE;
  582. /*
  583. * Were we called from xxxMenuKeyFilter? If not, simulate a LBUTTONDOWN
  584. * message to bring up the popup.
  585. */
  586. if (!pMenuState->fMenuStarted) {
  587. if (_GetKeyState(((ppopupmenu->fRightButton) ?
  588. VK_RBUTTON : VK_LBUTTON)) >= 0) {
  589. /*
  590. * We think the mouse button should be down but the call to get key
  591. * state says different so we need to get outta menu mode. This
  592. * happens if clicking on the menu causes a sys modal message box to
  593. * come up before we can enter this stuff. For example, run
  594. * winfile, click on drive a: to see its tree. Activate some other
  595. * app, then open drive a: and activate winfile by clicking on the
  596. * menu. This causes a sys modal msg box to come up just before
  597. * entering menu mode. The user may have the mouse button up but
  598. * menu mode code thinks it is down...
  599. */
  600. /*
  601. * Need to notify the app we are exiting menu mode because we told
  602. * it we were entering menu mode just before entering this function
  603. * in xxxSysCommand()...
  604. */
  605. if (!ppopupmenu->fNoNotify) {
  606. ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
  607. xxxSendNotifyMessage(ppopupmenu->spwndNotify, WM_EXITMENULOOP,
  608. ((ppopupmenu->fIsTrackPopup && !ppopupmenu->fIsSysMenu) ? TRUE : FALSE), 0);
  609. ThreadUnlock(&tlpwndT);
  610. }
  611. goto ExitMenuLoop;
  612. }
  613. /*
  614. * Simulate a WM_LBUTTONDOWN message.
  615. */
  616. if (!ppopupmenu->fIsTrackPopup) {
  617. /*
  618. * For TrackPopupMenus, we do it in the TrackPopupMenu function
  619. * itself so we don't want to do it again.
  620. */
  621. if (!xxxMNStartMenu(ppopupmenu, MOUSEHOLD)) {
  622. goto ExitMenuLoop;
  623. }
  624. }
  625. if ((ppopupmenu->fRightButton)) {
  626. msg.message = (fDblClk ? WM_RBUTTONDBLCLK : WM_RBUTTONDOWN);
  627. msg.wParam = MK_RBUTTON;
  628. } else {
  629. msg.message = (fDblClk ? WM_LBUTTONDBLCLK : WM_LBUTTONDOWN);
  630. msg.wParam = MK_LBUTTON;
  631. }
  632. msg.lParam = lParam;
  633. msg.hwnd = HW(ppopupmenu->spwndPopupMenu);
  634. xxxHandleMenuMessages(&msg, pMenuState, ppopupmenu);
  635. }
  636. /*
  637. * If this is a modeless menu, release capture, mark it in the menu state
  638. * and return. Decrement foreground lock count.
  639. */
  640. if (pMenuState->fModelessMenu) {
  641. xxxMNReleaseCapture();
  642. DecSFWLockCount();
  643. DBGDecModalMenuCount();
  644. return 0;
  645. }
  646. while (pMenuState->fInsideMenuLoop) {
  647. /*
  648. * Is a message waiting for us?
  649. */
  650. BOOL fPeek = xxxPeekMessage(&msg, NULL, 0, 0, PM_NOYIELD | PM_NOREMOVE);
  651. Validateppopupmenu(ppopupmenu);
  652. if (fPeek) {
  653. /*
  654. * Bail if we have been forced out of menu loop
  655. */
  656. if (ExitMenuLoop (pMenuState, ppopupmenu)) {
  657. goto ExitMenuLoop;
  658. }
  659. /*
  660. * Since we could have blocked in xxxWaitMessage (see last line
  661. * of loop) or xxxPeekMessage, reset the cached copy of
  662. * ptiCurrent()->pq: It could have changed if someone did a
  663. * DetachThreadInput() while we were away.
  664. */
  665. if ((!ppopupmenu->fIsTrackPopup &&
  666. pti->pq->spwndActive != ppopupmenu->spwndNotify &&
  667. ((pti->pq->spwndActive == NULL) || !_IsChild(pti->pq->spwndActive, ppopupmenu->spwndNotify)))) {
  668. /*
  669. * End menu processing if we are no longer the active window.
  670. * This is needed in case a system modal dialog box pops up
  671. * while we are tracking the menu code for example. It also
  672. * helps out Tracer if a macro is executed while a menu is down.
  673. */
  674. /*
  675. * Also, end menu processing if we think the mouse button is
  676. * down but it really isn't. (Happens if a sys modal dialog int
  677. * time dlg box comes up while we are in menu mode.)
  678. */
  679. goto ExitMenuLoop;
  680. }
  681. if (ppopupmenu->fIsMenuBar && msg.message == WM_LBUTTONDBLCLK) {
  682. /*
  683. * Was the double click on the system menu or caption?
  684. */
  685. hit = FindNCHit(ppopupmenu->spwndNotify, (LONG)msg.lParam);
  686. if (hit == HTCAPTION) {
  687. PWND pwnd;
  688. PMENU pmenu;
  689. /*
  690. * Get the message out of the queue since we're gonna
  691. * process it.
  692. */
  693. xxxPeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE);
  694. if (ExitMenuLoop (pMenuState, ppopupmenu)) {
  695. goto ExitMenuLoop;
  696. } else {
  697. pwnd = ppopupmenu->spwndNotify;
  698. ThreadLockAlways(pwnd, &tlpwndT);
  699. pmenu = xxxGetSysMenuHandle(pwnd);
  700. UserAssert(pwnd == ppopupmenu->spwndNotify);
  701. menuState = _GetMenuState(pmenu, SC_RESTORE & 0x0000FFF0,
  702. MF_BYCOMMAND);
  703. /*
  704. * Only send the sys command if the item is valid. If
  705. * the item doesn't exist or is disabled, then don't
  706. * post the syscommand. Note that for win2 apps, we
  707. * always send the sys command if it is a child window.
  708. * This is so hosebag apps can change the sys menu.
  709. */
  710. if (!(menuState & MFS_GRAYED)) {
  711. _PostMessage(pwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
  712. }
  713. /*
  714. * Get out of menu mode.
  715. */
  716. ThreadUnlock(&tlpwndT);
  717. goto ExitMenuLoop;
  718. }
  719. }
  720. }
  721. fInQueue = (msg.message == WM_LBUTTONDOWN ||
  722. msg.message == WM_RBUTTONDOWN ||
  723. msg.message == WM_NCLBUTTONDOWN ||
  724. msg.message == WM_NCRBUTTONDOWN);
  725. if (!fInQueue) {
  726. /*
  727. * Note that we call xxxPeekMessage() with the filter
  728. * set to the message we got from xxxPeekMessage() rather
  729. * than simply 0, 0. This prevents problems when
  730. * xxxPeekMessage() returns something like a WM_TIMER,
  731. * and after we get here to remove it a WM_LBUTTONDOWN,
  732. * or some higher-priority input message, gets in the
  733. * queue and gets removed accidently. Basically we want
  734. * to be sure we remove the right message in this case.
  735. * NT bug 3852 was caused by this problem.
  736. * Set the TIF_IGNOREPLAYBACKDELAY bit in case journal playback
  737. * is happening: this allows us to proceed even if the hookproc
  738. * incorrectly returns a delay now. The bit will be cleared if
  739. * this happens, so we can see why the Peek-Remove below fails.
  740. * Lotus' Freelance Graphics tutorial does such bad journalling
  741. */
  742. pti->TIF_flags |= TIF_IGNOREPLAYBACKDELAY;
  743. if (!xxxPeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE)) {
  744. if (pti->TIF_flags & TIF_IGNOREPLAYBACKDELAY) {
  745. pti->TIF_flags &= ~TIF_IGNOREPLAYBACKDELAY;
  746. /*
  747. * It wasn't a bad journal playback: something else
  748. * made the previously peeked message disappear before
  749. * we could peek it again to remove it.
  750. */
  751. RIPMSG1(RIP_WARNING, "Disappearing msg 0x%08lx", msg.message);
  752. goto NoMsg;
  753. }
  754. }
  755. pti->TIF_flags &= ~TIF_IGNOREPLAYBACKDELAY;
  756. }
  757. if (!_CallMsgFilter(&msg, MSGF_MENU)) {
  758. if (!xxxHandleMenuMessages(&msg, pMenuState, ppopupmenu)) {
  759. xxxTranslateMessage(&msg, 0);
  760. xxxDispatchMessage(&msg);
  761. }
  762. Validateppopupmenu(ppopupmenu);
  763. if (ExitMenuLoop (pMenuState, ppopupmenu)) {
  764. goto ExitMenuLoop;
  765. }
  766. if (pti->pq->QF_flags & QF_ACTIVATIONCHANGE) {
  767. /*
  768. * Run away and exit menu mode if another window has become
  769. * active while a menu was up.
  770. */
  771. RIPMSG0(RIP_WARNING, "Exiting menu mode: another window activated");
  772. goto ExitMenuLoop;
  773. }
  774. #if DBG
  775. /*
  776. * Nobody should be able to steal capture from us.
  777. */
  778. if (!pMenuState->fInDoDragDrop) {
  779. UserAssert(pti->pq->QF_flags & QF_CAPTURELOCKED);
  780. UserAssert(pti->pq->spwndCapture == ppopupmenu->spwndNotify);
  781. }
  782. #endif
  783. /*
  784. * If we get a system timer, then it's like we're idle
  785. */
  786. if (msg.message == WM_SYSTIMER) {
  787. goto NoMsg;
  788. }
  789. /*
  790. * Don't set fSendIdle if we got these messages
  791. */
  792. if ((msg.message == WM_TIMER) || (msg.message == WM_PAINT)) {
  793. continue;
  794. }
  795. } else {
  796. if (fInQueue)
  797. xxxPeekMessage(&msg, NULL, msg.message, msg.message,
  798. PM_REMOVE);
  799. }
  800. /*
  801. * Reenable WM_ENTERIDLE messages.
  802. */
  803. fSendIdle = TRUE;
  804. } else {
  805. NoMsg:
  806. /*
  807. * Bail if we have been forced out of menu loop
  808. */
  809. if (ExitMenuLoop (pMenuState, ppopupmenu)) {
  810. goto ExitMenuLoop;
  811. }
  812. UserAssert((ppopupmenu->spwndActivePopup == NULL)
  813. || (TestWF(ppopupmenu->spwndActivePopup, WFVISIBLE)));
  814. /*
  815. * If a hierarchical popup has been destroyed, this is a
  816. * good time to flush ppmDelayedFree
  817. */
  818. if (ppopupmenu->fFlushDelayedFree) {
  819. MNFlushDestroyedPopups (ppopupmenu, FALSE);
  820. ppopupmenu->fFlushDelayedFree = FALSE;
  821. }
  822. /*
  823. * We need to send the WM_ENTERIDLE message only the first time
  824. * there are no messages for us to process. Subsequent times we
  825. * need to yield via WaitMessage(). This will allow other tasks to
  826. * get some time while we have a menu down.
  827. */
  828. if (fSendIdle) {
  829. if (ppopupmenu->spwndNotify != NULL) {
  830. ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndT);
  831. xxxSendMessage(ppopupmenu->spwndNotify, WM_ENTERIDLE, MSGF_MENU,
  832. (LPARAM)HW(ppopupmenu->spwndActivePopup));
  833. ThreadUnlock(&tlpwndT);
  834. }
  835. fSendIdle = FALSE;
  836. } else {
  837. /*
  838. * If we're animating, sleep only 1 ms to reduce the chance
  839. * of jerky animation.
  840. * When not animating, this is the same as a xxxWaitMessage call
  841. */
  842. #ifdef MESSAGE_PUMP_HOOK
  843. xxxWaitMessageEx(QS_ALLINPUT | QS_EVENT, pMenuState->hdcWndAni != NULL);
  844. #else
  845. xxxSleepThread(QS_ALLINPUT | QS_EVENT, (pMenuState->hdcWndAni != NULL), TRUE);
  846. #endif
  847. }
  848. } /* if (PeekMessage(&msg, NULL, 0, 0, PM_NOYIELD)) else */
  849. } /* end while (fInsideMenuLoop) */
  850. ExitMenuLoop:
  851. pMenuState->fInsideMenuLoop = FALSE;
  852. pMenuState->fModelessMenu = FALSE;
  853. /*
  854. * Make sure that the menu has been ended/canceled
  855. */
  856. xxxEndMenuLoop (pMenuState, ppopupmenu);
  857. xxxMNReleaseCapture();
  858. // Throw in an extra peek here when we exit the menu loop to ensure that the input queue
  859. // for this thread gets unlocked if there is no more input left for him.
  860. xxxPeekMessage(&msg, NULL, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_NOYIELD | PM_NOREMOVE);
  861. return(pMenuState->cmdLast);
  862. } /* xxxMenuLoop() */