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.

4806 lines
153 KiB

  1. /**************************** Module Header ********************************\
  2. * Module Name: menu.c
  3. *
  4. * Copyright (c) 1985 - 1999, Microsoft Corporation
  5. *
  6. * Keyboard Accelerator Routines
  7. *
  8. * History:
  9. * 05-25-91 Mikehar Ported from Win3.1
  10. \***************************************************************************/
  11. #include "precomp.h"
  12. #pragma hdrstop
  13. /***********************************************************************\
  14. * MNGetpItemIndex
  15. *
  16. * 11/19/96 GerardoB Created
  17. \***********************************************************************/
  18. #if DBG
  19. UINT DBGMNGetpItemIndex(
  20. PMENU pmenu,
  21. PITEM pitem)
  22. {
  23. UINT uiPos;
  24. UserAssert((ULONG_PTR)pitem >= (ULONG_PTR)pmenu->rgItems);
  25. uiPos = _MNGetpItemIndex(pmenu, pitem);
  26. UserAssert(uiPos < pmenu->cItems);
  27. return uiPos;
  28. }
  29. #endif // DBG
  30. /**************************************************************************\
  31. * xxxMNDismiss
  32. *
  33. * 12/03/96 GerardoB Created
  34. \**************************************************************************/
  35. VOID xxxMNDismiss(
  36. PMENUSTATE pMenuState)
  37. {
  38. xxxMNCancel(pMenuState, 0, 0, 0);
  39. }
  40. /***************************************************************************\
  41. * MNFadeSelection
  42. *
  43. * 2/5/1998 vadimg created
  44. \***************************************************************************/
  45. BOOL MNFadeSelection(
  46. PMENU pmenu,
  47. PITEM pitem)
  48. {
  49. PWND pwnd;
  50. HDC hdc;
  51. RECT rc;
  52. PPOPUPMENU ppopup;
  53. if (!TestALPHA(SELECTIONFADE))
  54. return FALSE;
  55. /*
  56. * Don't fade the selection if the user is using the keyboard or journalling. These are performance scenarios
  57. */
  58. if (glinp.dwFlags & (LINP_KEYBOARD | LINP_JOURNALLING)) {
  59. return FALSE;
  60. }
  61. /*
  62. * Get the window for the currently active popup menu.
  63. */
  64. if ((ppopup = MNGetPopupFromMenu(pmenu, NULL)) == NULL)
  65. return FALSE;
  66. if ((pwnd = ppopup->spwndPopupMenu) == NULL)
  67. return FALSE;
  68. rc.left = pwnd->rcClient.left + pitem->xItem;
  69. rc.top = pwnd->rcClient.top + pitem->yItem;
  70. rc.right = rc.left + pitem->cxItem;
  71. rc.bottom = rc.top + pitem->cyItem;
  72. /*
  73. * Initialize the fade animation and get the DC to draw the selection into.
  74. */
  75. if ((hdc = CreateFade(NULL, &rc, CMS_SELECTIONFADE, 0)) == NULL)
  76. return FALSE;
  77. /*
  78. * Read the current menu selection right from the screen, since the menu
  79. * is still visible and it's always on top. In the worst case we could
  80. * offset the origin of the DC and call xxxDrawMenuItem, but just reading
  81. * from the screen is much faster.
  82. */
  83. GreBitBlt(hdc, 0, 0, pitem->cxItem, pitem->cyItem, gpDispInfo->hdcScreen,
  84. rc.left, rc.top, SRCCOPY, 0);
  85. ShowFade();
  86. return TRUE;
  87. }
  88. /**************************************************************************\
  89. * xxxMNDismissWithNotify
  90. *
  91. * Generates parameters for WM_COMMAND or WM_SYSCOMMAND message.
  92. *
  93. * 12/03/96 GerardoB Created
  94. \**************************************************************************/
  95. VOID xxxMNDismissWithNotify(
  96. PMENUSTATE pMenuState,
  97. PMENU pmenu,
  98. PITEM pitem,
  99. UINT uPos,
  100. LPARAM lParam)
  101. {
  102. UINT uMsg;
  103. UINT uCmd;
  104. if (pMenuState->pGlobalPopupMenu->fIsSysMenu) {
  105. uMsg = WM_SYSCOMMAND;
  106. uCmd = pitem->wID;
  107. /* lParam set by caller */
  108. } else if (pMenuState->fNotifyByPos) {
  109. uMsg = WM_MENUCOMMAND;
  110. uCmd = uPos;
  111. lParam = (LPARAM)PtoHq(pmenu);
  112. } else {
  113. uMsg = WM_COMMAND;
  114. uCmd = pitem->wID;
  115. lParam = 0;
  116. }
  117. /*
  118. * The menu is about to go away, see if we want to fade out the selection.
  119. */
  120. if (MNFadeSelection(pmenu, pitem)) {
  121. StartFade();
  122. }
  123. /*
  124. * Dismiss the menu.
  125. */
  126. xxxMNCancel(pMenuState, uMsg, uCmd, lParam);
  127. }
  128. /**************************************************************************\
  129. * MNGetpItem
  130. *
  131. * 11/15/96 GerardoB Created
  132. \**************************************************************************/
  133. PITEM MNGetpItem(
  134. PPOPUPMENU ppopup,
  135. UINT uIndex)
  136. {
  137. if ((ppopup == NULL) || (uIndex >= ppopup->spmenu->cItems)) {
  138. return NULL;
  139. }
  140. return ppopup->spmenu->rgItems + uIndex;
  141. }
  142. /***************************************************************************\
  143. * xxxMNSetCapture
  144. *
  145. * History:
  146. * 11/18/96 GerardoB Created
  147. \***************************************************************************/
  148. VOID xxxMNSetCapture(
  149. PPOPUPMENU ppopup)
  150. {
  151. PTHREADINFO ptiCurrent = PtiCurrent();
  152. /*
  153. * Set the capture and lock it so no one will be able to steal it
  154. * from us.
  155. */
  156. xxxCapture(ptiCurrent, ppopup->spwndNotify, SCREEN_CAPTURE);
  157. #if DBG
  158. if (ptiCurrent->pq->spwndCapture != ppopup->spwndNotify) {
  159. RIPMSG2(RIP_WARNING, "xxxMNSetCapture: spwndCapture (%p) != spwndNotify (%p)", ptiCurrent->pq->spwndCapture, ppopup->spwndNotify);
  160. }
  161. #endif
  162. ptiCurrent->pq->QF_flags |= QF_CAPTURELOCKED;
  163. ptiCurrent->pMenuState->fSetCapture = TRUE;
  164. #if DBG
  165. /*
  166. * Unless we're in the foreground, this menu mode won't go away
  167. * when the user clicks outside the menu. This is because only
  168. * the foreground queue capture sees clicks outside its windows.
  169. */
  170. if (ptiCurrent->pq != gpqForeground) {
  171. RIPMSG0(RIP_WARNING, "xxxMNSetCapture: Menu mode is not in foreground queue");
  172. }
  173. #endif
  174. }
  175. /***************************************************************************\
  176. * xxxMNReleaseCapture
  177. *
  178. * History:
  179. * 11/18/96 GerardoB Created
  180. \***************************************************************************/
  181. VOID xxxMNReleaseCapture(
  182. VOID)
  183. {
  184. PTHREADINFO ptiCurrent = PtiCurrent();
  185. /*
  186. * Bail if we didn't set capture.
  187. */
  188. if ((ptiCurrent->pMenuState == NULL) ||
  189. (!ptiCurrent->pMenuState->fSetCapture)) {
  190. return;
  191. }
  192. ptiCurrent->pMenuState->fSetCapture = FALSE;
  193. /*
  194. * Unlock capture and release it.
  195. */
  196. PtiCurrent()->pq->QF_flags &= ~QF_CAPTURELOCKED;
  197. xxxReleaseCapture();
  198. }
  199. /***************************************************************************\
  200. * MNCheckButtonDownState
  201. *
  202. * History:
  203. * 11/14/96 GerardoB Created
  204. \***************************************************************************/
  205. VOID MNCheckButtonDownState(
  206. PMENUSTATE pMenuState)
  207. {
  208. /*
  209. * Modeless menus don't capture the mouse so when a mouse down
  210. * goes off the window, we need to keep watching its state.
  211. *
  212. * We also might not see the button up when going on DoDragDrop loop.
  213. */
  214. UserAssert(pMenuState->fDragAndDrop || pMenuState->fModelessMenu);
  215. pMenuState->fButtonDown = ((_GetKeyState(pMenuState->vkButtonDown) & 0x8000) != 0);
  216. if (!pMenuState->fButtonDown) {
  217. pMenuState->fDragging =
  218. pMenuState->fIgnoreButtonUp = FALSE;
  219. UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
  220. }
  221. }
  222. /***************************************************************************\
  223. * GetMenuStateWindow
  224. *
  225. * This function is called when we need to post a message to the menu loop.
  226. * The actual pwnd is not important since we just want to reach
  227. * xxxHandleMenuMessages or xxxMenuWindowProc. So we just pick a window that
  228. * has a good chance to be around as long as we are in menu mode.
  229. *
  230. * History:
  231. * 10/31/96 GerardoB Created
  232. \***************************************************************************/
  233. PWND GetMenuStateWindow(
  234. PMENUSTATE pMenuState)
  235. {
  236. if (pMenuState == NULL) {
  237. return NULL;
  238. } else if (pMenuState->pGlobalPopupMenu->fIsTrackPopup) {
  239. return pMenuState->pGlobalPopupMenu->spwndPopupMenu;
  240. } else if (pMenuState->pGlobalPopupMenu->spwndNextPopup != NULL) {
  241. return pMenuState->pGlobalPopupMenu->spwndNextPopup;
  242. } else {
  243. return pMenuState->pGlobalPopupMenu->spwndActivePopup;
  244. }
  245. }
  246. /***************************************************************************\
  247. * UnlockPopupMenuWindow
  248. *
  249. * This function is called when locking/unlocking a menu into a popup structure.
  250. * It makes sure that pmenu doesn't keep the notification window locked
  251. * unneccessarily.
  252. *
  253. * It unlocks pmenu->spwndNotify if the menu it's not locked into pmenu->spwndNotify
  254. * itself AND it's currently locked to pwnd.
  255. *
  256. * It's also unlocked if pmenu->spwndNotify is marked as destroyed.
  257. *
  258. * History:
  259. * 10/15/96 GerardoB Created
  260. \***************************************************************************/
  261. VOID UnlockPopupMenuWindow(
  262. PMENU pmenu,
  263. PWND pwnd)
  264. {
  265. /*
  266. * Bail if there's nothing to unlock.
  267. */
  268. if ((pmenu == NULL) || (pmenu->spwndNotify == NULL)) {
  269. return;
  270. }
  271. /*
  272. * if pmenu->spwndNotify owns the menu, bail
  273. */
  274. if ((pmenu == pmenu->spwndNotify->spmenu)
  275. || (pmenu == pmenu->spwndNotify->spmenuSys)) {
  276. return;
  277. }
  278. /*
  279. * If pwnd doesn't own the menu, and pmenu->spwndNotify is not destroyed, bail.
  280. */
  281. if ((pwnd != pmenu->spwndNotify)
  282. && !TestWF(pmenu->spwndNotify, WFDESTROYED)) {
  283. return;
  284. }
  285. /*
  286. * Unlock it
  287. */
  288. Unlock(&pmenu->spwndNotify);
  289. }
  290. /***************************************************************************\
  291. * LockPopupMenu
  292. *
  293. * Locks a given menu into a popup strucuture and makes the
  294. * popup's notification window the owner of the menu.
  295. *
  296. * History:
  297. * 10/15/96 GerardoB Created
  298. \***************************************************************************/
  299. PVOID LockPopupMenu(
  300. PPOPUPMENU ppopup,
  301. PMENU *pspmenu,
  302. PMENU pmenu)
  303. {
  304. /*
  305. * If you hit this assertion, you're probably not passing the right thing.
  306. */
  307. UserAssert((pspmenu == &ppopup->spmenu) || (pspmenu == &ppopup->spmenuAlternate));
  308. Validateppopupmenu(ppopup);
  309. /*
  310. * This won't work properly if the popup hasn't locked the notification
  311. * window.
  312. */
  313. UserAssert(ppopup->spwndNotify != NULL);
  314. /*
  315. * When using modeless menus, menus can be shared by several active popups.
  316. * If the menu has owner draw items, the app better knows how to draw them
  317. * correctly. This shouldn't happen with modal menus though.
  318. */
  319. #if DBG
  320. if ((*pspmenu != NULL)
  321. && ((*pspmenu)->spwndNotify != NULL)
  322. && ((*pspmenu)->spwndNotify != ppopup->spwndNotify)) {
  323. RIPMSG3(RIP_WARNING, "LockPopupMenu: Current Menu %#p shared by %#p and %#p",
  324. *pspmenu, (*pspmenu)->spwndNotify, ppopup->spwndNotify);
  325. }
  326. #endif
  327. /*
  328. * Unlock the current's menu spwndNotify if needed
  329. */
  330. UnlockPopupMenuWindow(*pspmenu, ppopup->spwndNotify);
  331. /*
  332. * Lock the notification window into the menu structure
  333. */
  334. if (pmenu != NULL) {
  335. /*
  336. * Display a warning if this menu is being shared.
  337. */
  338. #if DBG
  339. if ((pmenu->spwndNotify != NULL)
  340. && (pmenu->spwndNotify != ppopup->spwndNotify)
  341. && (pmenu != pmenu->spwndNotify->head.rpdesk->spmenuDialogSys)) {
  342. RIPMSG3(RIP_WARNING, "LockPopupMenu: New Menu %#p shared by %#p and %#p",
  343. pmenu, pmenu->spwndNotify, ppopup->spwndNotify);
  344. }
  345. #endif
  346. /*
  347. * spwndNotify "owns" this menu now.
  348. */
  349. Lock(&pmenu->spwndNotify, ppopup->spwndNotify);
  350. }
  351. /*
  352. * Lock the menu into the popup structure (unlock the previous one)
  353. */
  354. return Lock(pspmenu, pmenu);
  355. }
  356. /***************************************************************************\
  357. * UnlockPopupMenu
  358. *
  359. * Unlocks a given menu from a popup strucuture and makes sure that the
  360. * menu is no longer "owned" by the popup's notification window; if needed.
  361. *
  362. * History:
  363. * 10/15/96 GerardoB Created
  364. \***************************************************************************/
  365. PVOID UnlockPopupMenu(
  366. PPOPUPMENU ppopup,
  367. PMENU * pspmenu)
  368. {
  369. /*
  370. * If you hit this assertion, you're probably not passing the right thing.
  371. */
  372. UserAssert((pspmenu == &ppopup->spmenu) || (pspmenu == &ppopup->spmenuAlternate));
  373. /*
  374. * If nothing is locked, bail.
  375. */
  376. if (*pspmenu == NULL) {
  377. return NULL;
  378. }
  379. /*
  380. * This won't work properly if the popup already unlocked the notification
  381. * window. However, this can happen with the root popup if the
  382. * notification window gets destroyed while in menu mode.
  383. */
  384. UserAssert((ppopup->spwndNotify != NULL) || IsRootPopupMenu(ppopup));
  385. /*
  386. * When using modeless menus, menus can be shared by several active
  387. * popups/notification windows. If the menu has owner draw items,
  388. * the app better knows how to paint them right. It shouldn't
  389. * happen with modal menus though.
  390. */
  391. #if DBG
  392. if (((*pspmenu)->spwndNotify != NULL)
  393. && (ppopup->spwndNotify != NULL)
  394. && (ppopup->spwndNotify != (*pspmenu)->spwndNotify)) {
  395. RIPMSG3(RIP_WARNING, "UnlockPopupMenu: Menu %#p shared by %#p and %#p",
  396. *pspmenu, (*pspmenu)->spwndNotify, ppopup->spwndNotify);
  397. }
  398. #endif
  399. /*
  400. * Unlock the menu's spwndNotify if needed
  401. */
  402. UnlockPopupMenuWindow(*pspmenu, ppopup->spwndNotify);
  403. /*
  404. * Unlock the menu from the popup structure
  405. */
  406. return Unlock(pspmenu);
  407. }
  408. /***************************************************************************\
  409. * LockWndMenu
  410. *
  411. * Locks a given menu into a window structure and locks the window into
  412. * the menu strucuture.
  413. *
  414. * History:
  415. * 10/15/96 GerardoB Created
  416. \***************************************************************************/
  417. PVOID LockWndMenu(
  418. PWND pwnd,
  419. PMENU *pspmenu,
  420. PMENU pmenu)
  421. {
  422. /*
  423. * If you hit this assertion, you're probably not passing the right thing
  424. */
  425. UserAssert((pspmenu == &pwnd->spmenu) || (pspmenu == &pwnd->spmenuSys));
  426. /*
  427. * If the current menu is owned by this window, unlock it
  428. */
  429. if ((*pspmenu != NULL) && ((*pspmenu)->spwndNotify == pwnd)) {
  430. Unlock(&((*pspmenu)->spwndNotify));
  431. }
  432. /*
  433. * If nobody owns the new menu, make this window the owner
  434. */
  435. if ((pmenu != NULL) && (pmenu->spwndNotify == NULL)) {
  436. Lock(&pmenu->spwndNotify, pwnd);
  437. }
  438. /*
  439. * Lock the menu into the window structure (unlock the previous menu)
  440. */
  441. return Lock(pspmenu, pmenu);
  442. }
  443. /***************************************************************************\
  444. * UnlockWndMenu
  445. *
  446. * Unlocks a given menu from a window strucutre and the window from the
  447. * menu strucuture
  448. *
  449. * History:
  450. * 10/15/96 GerardoB Created
  451. \***************************************************************************/
  452. PVOID UnlockWndMenu(
  453. PWND pwnd,
  454. PMENU *pspmenu)
  455. {
  456. /*
  457. * If you hit this assertion, you're probably not passing the right thing
  458. */
  459. UserAssert((pspmenu == &pwnd->spmenu) || (pspmenu == &pwnd->spmenuSys));
  460. /*
  461. * If nothing is locked, bail.
  462. */
  463. if (*pspmenu == NULL) {
  464. return NULL;
  465. }
  466. /*
  467. * If this window owns the menu, unlock it from the menu structure.
  468. */
  469. if (pwnd == (*pspmenu)->spwndNotify) {
  470. Unlock(&((*pspmenu)->spwndNotify));
  471. }
  472. /*
  473. * Unlock the menu from the window structure
  474. */
  475. return Unlock(pspmenu);
  476. }
  477. /***************************************************************************\
  478. * MNSetTop
  479. *
  480. * Sets the first visible item in a scrollable menu to the given iNewTop.
  481. * Returns TRUE if iTop was changed; FALSE if iNewTop is already the
  482. * first visible item.
  483. *
  484. * 08/13/96 GerardoB Ported From Memphis.
  485. \***************************************************************************/
  486. BOOL xxxMNSetTop(
  487. PPOPUPMENU ppopup,
  488. int iNewTop)
  489. {
  490. PMENU pMenu = ppopup->spmenu;
  491. int dy;
  492. if (iNewTop < 0) {
  493. iNewTop = 0;
  494. } else if (iNewTop > pMenu->iMaxTop) {
  495. iNewTop = pMenu->iMaxTop;
  496. }
  497. /*
  498. * If no change, done.
  499. */
  500. if (iNewTop == pMenu->iTop) {
  501. return FALSE;
  502. }
  503. #if DBG
  504. /*
  505. * We're going to scroll, so validate iMaxTop, cyMax and cyMenu.
  506. */
  507. UserAssert((pMenu->cyMax == 0) || (pMenu->cyMax >= pMenu->cyMenu));
  508. if ((UINT)pMenu->iMaxTop < pMenu->cItems) {
  509. PITEM pitemLast = pMenu->rgItems + pMenu->cItems - 1;
  510. PITEM pitemMaxTop = pMenu->rgItems + pMenu->iMaxTop;
  511. UINT uHeight = pitemLast->yItem + pitemLast->cyItem - pitemMaxTop->yItem;
  512. UserAssert(uHeight <= pMenu->cyMenu);
  513. /*
  514. * Let's guess a max item height.
  515. */
  516. UserAssert(pMenu->cyMenu - uHeight <= 2 * pitemLast->cyItem);
  517. } else {
  518. UserAssert((UINT)pMenu->iMaxTop < pMenu->cItems);
  519. }
  520. #endif
  521. /*
  522. * If we've made it this far, the new iTop WILL change -- thus if the
  523. * current iTop is at the top it won't be after this change -- same goes
  524. * for iTop at the bottom.
  525. */
  526. if (pMenu->dwArrowsOn == MSA_ATTOP) {
  527. pMenu->dwArrowsOn = MSA_ON;
  528. if (pMenu->hbrBack == NULL) {
  529. MNDrawArrow(NULL, ppopup, MFMWFP_UPARROW);
  530. }
  531. } else if (pMenu->dwArrowsOn == MSA_ATBOTTOM) {
  532. pMenu->dwArrowsOn = MSA_ON;
  533. if (pMenu->hbrBack == NULL) {
  534. MNDrawArrow(NULL, ppopup, MFMWFP_DOWNARROW);
  535. }
  536. }
  537. UserAssert((UINT)iNewTop < pMenu->cItems);
  538. dy = MNGetToppItem(pMenu)->yItem - (pMenu->rgItems + iNewTop)->yItem;
  539. if ((dy > 0 ? dy : -dy) > (int)pMenu->cyMenu) {
  540. xxxInvalidateRect(ppopup->spwndPopupMenu, NULL, TRUE);
  541. } else {
  542. xxxScrollWindowEx(ppopup->spwndPopupMenu, 0, dy, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE);
  543. }
  544. pMenu->iTop = iNewTop;
  545. if (iNewTop == 0) {
  546. pMenu->dwArrowsOn = MSA_ATTOP;
  547. if (pMenu->hbrBack == NULL) {
  548. MNDrawArrow(NULL, ppopup, MFMWFP_UPARROW);
  549. }
  550. } else if (iNewTop == pMenu->iMaxTop) {
  551. pMenu->dwArrowsOn = MSA_ATBOTTOM;
  552. if (pMenu->hbrBack == NULL) {
  553. MNDrawArrow(NULL, ppopup, MFMWFP_DOWNARROW);
  554. }
  555. }
  556. if (pMenu->hbrBack != NULL) {
  557. MNDrawFullNC(ppopup->spwndPopupMenu, NULL, ppopup);
  558. }
  559. return TRUE;
  560. }
  561. /***************************************************************************\
  562. * xxxMNDoScroll
  563. *
  564. * scrolls a scrollable menu (ppopup) if the given position (uArrow) is one of
  565. * the menu scroll arrows and sets a timer to auto-scroll when necessary;
  566. * returns FALSE if the given position was not a menu scroll arrow; returns
  567. * TRUE otherwise
  568. *
  569. * 08/13/96 GerardoB Ported From Memphis.
  570. \***************************************************************************/
  571. BOOL xxxMNDoScroll(
  572. PPOPUPMENU ppopup,
  573. UINT uArrow,
  574. BOOL fSetTimer)
  575. {
  576. int iScrollTop = ppopup->spmenu->iTop;
  577. if (uArrow == MFMWFP_UPARROW) {
  578. iScrollTop--;
  579. } else if (uArrow == MFMWFP_DOWNARROW) {
  580. iScrollTop++;
  581. } else {
  582. return FALSE;
  583. }
  584. if (!xxxMNSetTop(ppopup, iScrollTop)) {
  585. if (!fSetTimer) {
  586. _KillTimer(ppopup->spwndPopupMenu, uArrow);
  587. }
  588. } else {
  589. /*
  590. * Set this timer just like we do in the scrollbar code:
  591. * the first time we wait a little longer.
  592. */
  593. _SetTimer(ppopup->spwndPopupMenu, uArrow,
  594. (fSetTimer ? gpsi->dtScroll : gpsi->dtScroll / 4), NULL);
  595. }
  596. return TRUE;
  597. }
  598. /***************************************************************************\
  599. * MNCheckScroll
  600. *
  601. * Checks to see if the given menu (pMenu) can be displayed in it's entirety
  602. * or if it can't, in which case it sets the menu to be scrollable.
  603. *
  604. * 08/13/96 GerardoB Ported From Memphis.
  605. \***************************************************************************/
  606. int MNCheckScroll(
  607. PMENU pMenu,
  608. PMONITOR pMonitor)
  609. {
  610. int i;
  611. UINT cyMax;
  612. PITEM pItem;
  613. /*
  614. * Max height that fits on the monitor
  615. */
  616. cyMax = (pMonitor->rcMonitor.bottom - pMonitor->rcMonitor.top);
  617. /*
  618. * If the menu has a valid max height, use it
  619. */
  620. if ((pMenu->cyMax != 0) && (pMenu->cyMax < cyMax)) {
  621. cyMax = pMenu->cyMax;
  622. }
  623. /*
  624. * Bail if menu is either empty, multicolumn, or able to fit
  625. * without scrolling
  626. */
  627. if ((pMenu->rgItems == 0)
  628. || (pMenu->rgItems->cxItem != pMenu->cxMenu)
  629. || (pMenu->cyMenu + (2 * SYSMET(CYFIXEDFRAME)) <= cyMax)) {
  630. pMenu->dwArrowsOn = MSA_OFF;
  631. pMenu->iTop = 0;
  632. pMenu->iMaxTop = 0;
  633. return pMenu->cyMenu;
  634. }
  635. /*
  636. * Max client height
  637. */
  638. cyMax -= 2 * (SYSMET(CYFIXEDFRAME) + gcyMenuScrollArrow);
  639. /*
  640. * Determine the menu height
  641. * Find the first item that won't fit.
  642. */
  643. pItem = pMenu->rgItems;
  644. for (i = 0; i < (int)pMenu->cItems; i++, pItem++) {
  645. if (pItem->yItem > (UINT)cyMax) {
  646. break;
  647. }
  648. }
  649. if (i != 0) {
  650. pItem--;
  651. }
  652. pMenu->cyMenu = pItem->yItem;
  653. /*
  654. * compute the last possible top item when all remaining items are fully
  655. * visible
  656. */
  657. cyMax = 0;
  658. i = pMenu->cItems - 1;
  659. pItem = pMenu->rgItems + i;
  660. for (; i >= 0; i--, pItem--) {
  661. cyMax += pItem->cyItem;
  662. if (cyMax > pMenu->cyMenu) {
  663. break;
  664. }
  665. }
  666. if ((UINT)i != pMenu->cItems - 1) {
  667. i++;
  668. }
  669. pMenu->iMaxTop = i;
  670. /*
  671. * Update top item and scroll state
  672. */
  673. if (pMenu->iTop > i) {
  674. pMenu->iTop = i;
  675. }
  676. if (pMenu->iTop == i) {
  677. pMenu->dwArrowsOn = MSA_ATBOTTOM;
  678. } else if (pMenu->iTop == 0) {
  679. pMenu->dwArrowsOn = MSA_ATTOP;
  680. } else {
  681. pMenu->dwArrowsOn = MSA_ON;
  682. }
  683. /*
  684. * This is funtion is called by MN_SIZEWINDOW which doesn't check
  685. * if the scroll bars are present but simply adds (2 * SYSMET(CYFIXEDFRAME))
  686. * to calculate the window height. So we add the scrollbars height
  687. * here. (I believe MN_SIZEWINDOW is a private-but-publicly-known message)
  688. */
  689. return (pMenu->cyMenu + (2 * gcyMenuScrollArrow));
  690. }
  691. /***************************************************************************\
  692. * MNIsPopupItem
  693. *
  694. *
  695. \***************************************************************************/
  696. BOOL MNIsPopupItem(
  697. ITEM *lpItem)
  698. {
  699. return ((lpItem) && (lpItem->spSubMenu) && !TestMFS(lpItem, MFS_GRAYED));
  700. }
  701. /***************************************************************************\
  702. * Validateppopupmenu
  703. *
  704. * 05-15-96 GerardoB Created
  705. \***************************************************************************/
  706. #if DBG
  707. VOID Validateppopupmenu(
  708. PPOPUPMENU ppopupmenu)
  709. {
  710. UserAssert(ppopupmenu != NULL);
  711. try {
  712. UserAssert(!ppopupmenu->fFreed);
  713. /*
  714. * If this popup is being destroyed to soon, ppopupmenuRoot can be NULL
  715. */
  716. if (ppopupmenu->ppopupmenuRoot != NULL) {
  717. if (ppopupmenu->ppopupmenuRoot != ppopupmenu) {
  718. /*
  719. * This must be a hierarchical popup.
  720. */
  721. UserAssert(ppopupmenu->spwndPrevPopup != NULL);
  722. UserAssert(!ppopupmenu->fIsMenuBar && !ppopupmenu->fIsTrackPopup);
  723. Validateppopupmenu(ppopupmenu->ppopupmenuRoot);
  724. } else {
  725. /*
  726. * This must be the root popupmenu.
  727. */
  728. UserAssert(ppopupmenu->spwndPrevPopup == NULL);
  729. UserAssert(ppopupmenu->fIsMenuBar || ppopupmenu->fIsTrackPopup);
  730. }
  731. }
  732. /*
  733. * This can be NULL when called from xxxDeleteThreadInfo.
  734. */
  735. if (ppopupmenu->spwndPopupMenu != NULL) {
  736. UserAssert(ppopupmenu->spwndPopupMenu == RevalidateCatHwnd(HW(ppopupmenu->spwndPopupMenu)));
  737. }
  738. /*
  739. * This can be NULL when called from xxxDestroyWindow (spwndNotify)
  740. * or from xxxDeleteThreadInfo.
  741. */
  742. if (ppopupmenu->spwndNotify != NULL) {
  743. UserAssert(ppopupmenu->spwndNotify == RevalidateCatHwnd(HW(ppopupmenu->spwndNotify)));
  744. }
  745. } except (W32ExceptionHandler(FALSE, RIP_ERROR)) {
  746. RIPMSG1(RIP_ERROR, "Validateppopupmenu: Invalid popup: 0x%p", ppopupmenu);
  747. }
  748. }
  749. #endif // DBG
  750. /***************************************************************************\
  751. * xxxMNSwitchToAlternateMenu
  752. *
  753. * Switches to the alternate popupmenu. Returns TRUE if we switch,
  754. * else FALSE.
  755. *
  756. * History:
  757. * 05-25-91 Mikehar Ported from Win3.1
  758. \***************************************************************************/
  759. BOOL xxxMNSwitchToAlternateMenu(
  760. PPOPUPMENU ppopupmenu)
  761. {
  762. PMENU pmenuSwap = NULL;
  763. PMENUSTATE pMenuState;
  764. TL tlpwndPopupMenu;
  765. if (!ppopupmenu->fIsMenuBar || !ppopupmenu->spmenuAlternate) {
  766. /*
  767. * Do nothing if no menu or not top level menu bar.
  768. * ppopupmenu->spmenuAlternate can be NULL when an app has
  769. * either system menu or menu bar but not both. If that menu
  770. * has only one popup that it's not dropped, then hitting
  771. * VK_RIGHT or VK_LEFT causes xxxMNKeyDown to end up here.
  772. *
  773. * ppopupmenu->fIsMenuBar can be false when you drop the
  774. * system menu of an app with no menu bar; then hit VK_RIGHT
  775. * on an item that doesn't have a popup and you'll get here
  776. * There might be some other situations like this; in any case
  777. * the assertion's got to go.
  778. */
  779. return FALSE;
  780. }
  781. /*
  782. * If we're getting out of menu mode, do nothing.
  783. */
  784. if (ppopupmenu->fDestroyed) {
  785. return FALSE;
  786. }
  787. /*
  788. * Select no items in the current menu.
  789. */
  790. ThreadLock(ppopupmenu->spwndPopupMenu, &tlpwndPopupMenu);
  791. UserAssert(ppopupmenu->spwndPopupMenu != NULL);
  792. pMenuState = GetpMenuState(ppopupmenu->spwndPopupMenu);
  793. if (pMenuState == NULL) {
  794. RIPMSG0(RIP_ERROR, "xxxMNSwitchToAlternateMenu: pMenuState == NULL");
  795. ThreadUnlock(&tlpwndPopupMenu);
  796. return FALSE;
  797. }
  798. xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
  799. UserAssert(ppopupmenu->spmenu->spwndNotify == ppopupmenu->spmenuAlternate->spwndNotify);
  800. Lock(&pmenuSwap, ppopupmenu->spmenuAlternate);
  801. Lock(&ppopupmenu->spmenuAlternate, ppopupmenu->spmenu);
  802. Lock(&ppopupmenu->spmenu, pmenuSwap);
  803. Unlock(&pmenuSwap);
  804. if (!TestWF(ppopupmenu->spwndNotify, WFSYSMENU)) {
  805. pMenuState->fIsSysMenu = FALSE;
  806. } else if (ppopupmenu->spwndNotify->spmenuSys != NULL) {
  807. pMenuState->fIsSysMenu = (ppopupmenu->spwndNotify->spmenuSys ==
  808. ppopupmenu->spmenu);
  809. } else {
  810. pMenuState->fIsSysMenu = !!TestMF(ppopupmenu->spmenu, MFSYSMENU);
  811. }
  812. ppopupmenu->fIsSysMenu = pMenuState->fIsSysMenu;
  813. xxxWindowEvent(EVENT_SYSTEM_MENUEND, ppopupmenu->spwndNotify,
  814. (ppopupmenu->fIsSysMenu ? OBJID_MENU : OBJID_SYSMENU), INDEXID_CONTAINER, 0);
  815. xxxWindowEvent(EVENT_SYSTEM_MENUSTART, ppopupmenu->spwndNotify,
  816. (ppopupmenu->fIsSysMenu ? OBJID_SYSMENU : OBJID_MENU), INDEXID_CONTAINER, 0);
  817. ThreadUnlock(&tlpwndPopupMenu);
  818. return TRUE;
  819. }
  820. /***************************************************************************\
  821. * xxxMNDestroyHandler
  822. *
  823. * Cleans up after this menu.
  824. *
  825. * History:
  826. * 05-25-91 Mikehar Ported from Win3.1
  827. \***************************************************************************/
  828. VOID xxxMNDestroyHandler(
  829. PPOPUPMENU ppopupmenu)
  830. {
  831. PITEM pItem;
  832. TL tlpwndT;
  833. if (ppopupmenu == NULL) {
  834. /*
  835. * This can happen if WM_NCCREATE failed to allocate the ppopupmenu
  836. * in xxxMenuWindowProc.
  837. */
  838. RIPMSG0(RIP_WARNING, "xxxMNDestroyHandler: NULL \"ppopupmenu\"");
  839. return;
  840. }
  841. #if DBG
  842. Validateppopupmenu(ppopupmenu);
  843. #endif
  844. if (ppopupmenu->spwndNextPopup != NULL) {
  845. /*
  846. * We used to send the message to spwndNextPopup here. The message should
  847. * go to the current popup so it'll close spwndNextPopup (not to the next
  848. * to close its next, if any).
  849. *
  850. * I don't see how the current spwndPopupMenu can be NULL but we better
  851. * handle it since we never accessed it before. This menu code is tricky...
  852. */
  853. PWND pwnd;
  854. UserAssert(ppopupmenu->spwndPopupMenu != NULL);
  855. pwnd = (ppopupmenu->spwndPopupMenu != NULL ? ppopupmenu->spwndPopupMenu : ppopupmenu->spwndNextPopup);
  856. ThreadLockAlways(pwnd, &tlpwndT);
  857. xxxSendMessage(pwnd, MN_CLOSEHIERARCHY, 0, 0);
  858. ThreadUnlock(&tlpwndT);
  859. }
  860. if ((ppopupmenu->spmenu != NULL) && MNIsItemSelected(ppopupmenu)) {
  861. /*
  862. * Unset the hilite bit on the hilited item.
  863. */
  864. if (ppopupmenu->posSelectedItem < ppopupmenu->spmenu->cItems) {
  865. /*
  866. * This extra check saves Ambiente 1.02 -- they have a menu with
  867. * one item in it. When that command is chosen, the app proceeds
  868. * to remove that one item -- leaving us in the oh so strange state
  869. * of having a valid hMenu with a NULL rgItems.
  870. */
  871. pItem = &(ppopupmenu->spmenu->rgItems[ppopupmenu->posSelectedItem]);
  872. pItem->fState &= ~MFS_HILITE;
  873. }
  874. }
  875. if (ppopupmenu->fShowTimer) {
  876. _KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNSHOW);
  877. }
  878. if (ppopupmenu->fHideTimer) {
  879. _KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNHIDE);
  880. }
  881. /*
  882. * Send WM_UNINITMENUPOPUP so the menu owner can clean up.
  883. */
  884. if (ppopupmenu->fSendUninit
  885. && (ppopupmenu->spwndNotify != NULL)) {
  886. ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndT);
  887. xxxSendMessage(ppopupmenu->spwndNotify, WM_UNINITMENUPOPUP,
  888. (WPARAM)PtoH(ppopupmenu->spmenu),
  889. MAKELONG(0, (ppopupmenu->fIsSysMenu ? MF_SYSMENU: 0)));
  890. ThreadUnlock(&tlpwndT);
  891. }
  892. ppopupmenu->fDestroyed = TRUE;
  893. if (ppopupmenu->spwndPopupMenu != NULL) {
  894. ((PMENUWND)(ppopupmenu->spwndPopupMenu))->ppopupmenu = NULL;
  895. }
  896. if (!ppopupmenu->fDelayedFree) {
  897. MNFreePopup(ppopupmenu);
  898. } else if (ppopupmenu->ppopupmenuRoot != NULL) {
  899. ppopupmenu->ppopupmenuRoot->fFlushDelayedFree = TRUE;
  900. #if DBG
  901. {
  902. /*
  903. * If this is not the rootpopup, assert that this popup is
  904. * linked in the delayed free list.
  905. */
  906. if (!IsRootPopupMenu(ppopupmenu)) {
  907. BOOL fFound = FALSE;
  908. PPOPUPMENU ppm = ppopupmenu->ppopupmenuRoot;
  909. while (ppm->ppmDelayedFree != NULL) {
  910. if (ppm->ppmDelayedFree == ppopupmenu) {
  911. fFound = TRUE;
  912. break;
  913. }
  914. ppm = ppm->ppmDelayedFree;
  915. }
  916. UserAssert(fFound);
  917. }
  918. }
  919. #endif
  920. } else {
  921. UserAssertMsg1(FALSE, "Leaking ppopupmenu: %p?", ppopupmenu);
  922. }
  923. }
  924. /***************************************************************************\
  925. * xxxMNChar
  926. *
  927. * Handles char messages for the given menu. This procedure is called
  928. * directly if the menu char is for the top level menu bar else it is called
  929. * by the menu window proc on behalf of the window that should process the
  930. * key.
  931. *
  932. * History:
  933. * 05-25-91 Mikehar Ported from Win3.1
  934. \***************************************************************************/
  935. VOID xxxMNChar(
  936. PPOPUPMENU ppopupmenu,
  937. PMENUSTATE pMenuState,
  938. UINT character)
  939. {
  940. PMENU pMenu;
  941. UINT flags;
  942. LRESULT result;
  943. int item;
  944. INT matchType;
  945. BOOL fExecute = FALSE;
  946. TL tlpwndNotify;
  947. pMenu = ppopupmenu->spmenu;
  948. Validateppopupmenu(ppopupmenu);
  949. /*
  950. * If this comes in with a NULL pMenu, then we could have problems.
  951. * This could happen if the xxxMNStartMenuState never gets called
  952. * because the fInsideMenuLoop is set.
  953. *
  954. * This is placed in here temporarily until we can discover why
  955. * this pMenu isn't set. We will prevent the system from crashing
  956. * in the meantime.
  957. *
  958. * HACK: ChrisWil
  959. */
  960. if (pMenu == NULL) {
  961. UserAssert(pMenu);
  962. xxxMNDismiss(pMenuState);
  963. return;
  964. }
  965. /*
  966. * If we're getting out of menu mode, bail.
  967. */
  968. if (ppopupmenu->fDestroyed) {
  969. return;
  970. }
  971. item = xxxMNFindChar(pMenu, character,
  972. ppopupmenu->posSelectedItem, &matchType);
  973. if (item != MFMWFP_NOITEM) {
  974. int item1;
  975. int firstItem = item;
  976. /*
  977. * Find first ENABLED menu item with the given mnemonic 'character'
  978. * !!! If none found, exit menu loop !!!
  979. */
  980. while (pMenu->rgItems[item].fState & MFS_GRAYED) {
  981. item = xxxMNFindChar(pMenu, character, item, &matchType);
  982. if (item == firstItem) {
  983. xxxMNDismiss(pMenuState);
  984. return;
  985. }
  986. }
  987. item1 = item;
  988. /*
  989. * Find next ENABLED menu item with the given mnemonic 'character'
  990. * This is to see if we have a DUPLICATE MNEMONIC situation
  991. */
  992. do {
  993. item = xxxMNFindChar(pMenu, character, item, &matchType);
  994. } while ((pMenu->rgItems[item].fState & MFS_GRAYED) && (item != firstItem));
  995. if ((firstItem == item) || (item == item1))
  996. fExecute = TRUE;
  997. item = item1;
  998. }
  999. if ((item == MFMWFP_NOITEM) && ppopupmenu->fIsMenuBar && (character == TEXT(' '))) {
  1000. /*
  1001. * Handle the case of the user cruising through the top level menu bar
  1002. * without any popups dropped. We need to handle switching to and from
  1003. * the system menu.
  1004. */
  1005. if (ppopupmenu->fIsSysMenu) {
  1006. /*
  1007. * If we are on the system menu and user hits space, bring
  1008. * down thesystem menu.
  1009. */
  1010. item = 0;
  1011. fExecute = TRUE;
  1012. } else if (ppopupmenu->spmenuAlternate != NULL) {
  1013. /*
  1014. * We are not currently on the system menu but one exists. So
  1015. * switch to it and bring it down.
  1016. */
  1017. item = 0;
  1018. goto SwitchToAlternate;
  1019. }
  1020. }
  1021. if ((item == MFMWFP_NOITEM) && ppopupmenu->fIsMenuBar && ppopupmenu->spmenuAlternate) {
  1022. /*
  1023. * No matching item found on this top level menu (could be either the
  1024. * system menu or the menu bar). We need to check the other menu.
  1025. */
  1026. item = xxxMNFindChar(ppopupmenu->spmenuAlternate,
  1027. character, 0, &matchType);
  1028. if (item != MFMWFP_NOITEM) {
  1029. SwitchToAlternate:
  1030. if (xxxMNSwitchToAlternateMenu(ppopupmenu)) {
  1031. xxxMNChar(ppopupmenu, pMenuState, character);
  1032. }
  1033. return;
  1034. }
  1035. }
  1036. if (item == MFMWFP_NOITEM) {
  1037. flags = (ppopupmenu->fIsSysMenu) ? MF_SYSMENU : 0;
  1038. if (!ppopupmenu->fIsMenuBar) {
  1039. flags |= MF_POPUP;
  1040. }
  1041. ThreadLock(ppopupmenu->spwndNotify, &tlpwndNotify);
  1042. result = xxxSendMessage(ppopupmenu->spwndNotify, WM_MENUCHAR,
  1043. MAKELONG((WORD)character, (WORD)flags),
  1044. (LPARAM)PtoH(ppopupmenu->spmenu));
  1045. ThreadUnlock(&tlpwndNotify);
  1046. switch (HIWORD(result)) {
  1047. case MNC_IGNORE:
  1048. xxxMessageBeep(0);
  1049. /*
  1050. * If we're on the menu bar, cancel menu mode (fall through).
  1051. * We do this because you can really scare an end user
  1052. * who accidentally tapped the Alt key (causing us to go
  1053. * into "invisible" menu mode) and now can't type anything!
  1054. */
  1055. if (flags & MF_POPUP) {
  1056. return;
  1057. }
  1058. /*
  1059. * Fall through.
  1060. */
  1061. case MNC_CLOSE:
  1062. xxxMNDismiss(pMenuState);
  1063. return;
  1064. case MNC_EXECUTE:
  1065. fExecute = TRUE;
  1066. /* fall thru */
  1067. case MNC_SELECT:
  1068. item = (UINT)(short)LOWORD(result);
  1069. if ((WORD) item >= ppopupmenu->spmenu->cItems)
  1070. {
  1071. RIPMSG1(RIP_WARNING, "Invalid item number returned from WM_MENUCHAR %#lx", result);
  1072. return;
  1073. }
  1074. break;
  1075. }
  1076. }
  1077. if (item != MFMWFP_NOITEM) {
  1078. xxxMNSelectItem(ppopupmenu, pMenuState, item);
  1079. if (fExecute)
  1080. xxxMNKeyDown(ppopupmenu, pMenuState, VK_RETURN);
  1081. }
  1082. }
  1083. /***************************************************************************\
  1084. * GetMenuInheritedContextHelpId
  1085. *
  1086. * Given a ppopup, this function will see if that menu has a context help
  1087. * id and return it. If it does not have a context help id, it will look up
  1088. * in the parent menu, parent of the parent etc., all the way to the top
  1089. * top level menu bar till it finds a context help id and returns it. If no
  1090. * context help id is found, it returns a zero.
  1091. \***************************************************************************/
  1092. DWORD GetMenuInheritedContextHelpId(
  1093. PPOPUPMENU ppopup)
  1094. {
  1095. PWND pWnd;
  1096. /*
  1097. * If we are already at the menubar, simply return it's ContextHelpId
  1098. */
  1099. UserAssert(ppopup != NULL);
  1100. if (ppopup->fIsMenuBar) {
  1101. goto Exit_GMI;
  1102. }
  1103. while(TRUE) {
  1104. UserAssert(ppopup != NULL);
  1105. /*
  1106. * See if the given popup has a context help id.
  1107. */
  1108. if (ppopup->spmenu->dwContextHelpId) {
  1109. /* Found the context Id */
  1110. break;
  1111. }
  1112. /*
  1113. * Get the previous popup menu;
  1114. * Check if the previous menu is the menu bar.
  1115. */
  1116. if ( (ppopup->fHasMenuBar) &&
  1117. (ppopup->spwndPrevPopup == ppopup->spwndNotify)) {
  1118. ppopup = ppopup -> ppopupmenuRoot;
  1119. break;
  1120. } else {
  1121. /*
  1122. * See if this has a valid prevPopup; (it could be TrackPopup menu)
  1123. */
  1124. if ((pWnd = ppopup -> spwndPrevPopup) == NULL) {
  1125. return ((DWORD)0);
  1126. }
  1127. ppopup = ((PMENUWND)pWnd)->ppopupmenu;
  1128. }
  1129. }
  1130. Exit_GMI:
  1131. return ppopup->spmenu->dwContextHelpId;
  1132. }
  1133. /***************************************************************************\
  1134. * xxxMNKeyDown
  1135. *
  1136. * Handles a keydown for the given menu.
  1137. *
  1138. * History:
  1139. * 05-25-91 Mikehar Ported from Win3.1
  1140. \***************************************************************************/
  1141. VOID xxxMNKeyDown(
  1142. PPOPUPMENU ppopupmenu,
  1143. PMENUSTATE pMenuState,
  1144. UINT key)
  1145. {
  1146. LRESULT dwMDIMenu;
  1147. UINT item;
  1148. BOOL fHierarchyWasDropped = FALSE;
  1149. TL tlpwndT;
  1150. PPOPUPMENU ppopupSave;
  1151. BOOL bFakedKey;
  1152. UINT keyOrig = key;
  1153. /*
  1154. * Blow off keyboard if mouse down.
  1155. */
  1156. if ((pMenuState->fButtonDown) && (key != VK_F1)) {
  1157. /*
  1158. * Check if the user wants to cancel dragging.
  1159. */
  1160. if (pMenuState->fDragging && (key == VK_ESCAPE)) {
  1161. RIPMSG0(RIP_WARNING, "xxxMNKeyDown: ESC while dragging");
  1162. pMenuState->fIgnoreButtonUp = TRUE;
  1163. }
  1164. return;
  1165. }
  1166. switch (key) {
  1167. case VK_MENU:
  1168. case VK_F10:
  1169. /*
  1170. * Modeless don't go away when the menu key is hit. They just
  1171. * ignore it.
  1172. */
  1173. if (pMenuState->fModelessMenu) {
  1174. return;
  1175. }
  1176. xxxMNDismiss(pMenuState);
  1177. /*
  1178. * We're going to exit menu mode but the ALT key is down, so clear
  1179. * pMenuState->fUnderline to cause xxxMNLoop not to erase the
  1180. * underlines.
  1181. */
  1182. if (key == VK_MENU) {
  1183. pMenuState->fUnderline = FALSE;
  1184. }
  1185. return;
  1186. case VK_ESCAPE:
  1187. /*
  1188. * Escape key was hit. Get out of one level of menus. If no active
  1189. * popups or we are minimized and there are no active popups below
  1190. * this, we need to get out of menu mode. Otherwise, we popup up
  1191. * one level in the hierarchy.
  1192. */
  1193. if (ppopupmenu->fIsMenuBar ||
  1194. ppopupmenu == ppopupmenu->ppopupmenuRoot ||
  1195. TestWF(ppopupmenu->ppopupmenuRoot->spwndNotify, WFMINIMIZED)) {
  1196. xxxMNDismiss(pMenuState);
  1197. } else {
  1198. /*
  1199. * Pop back one level of menus.
  1200. */
  1201. if (ppopupmenu->fHasMenuBar &&
  1202. ppopupmenu->spwndPrevPopup == ppopupmenu->spwndNotify) {
  1203. PPOPUPMENU ppopupmenuRoot = ppopupmenu->ppopupmenuRoot;
  1204. ppopupmenuRoot->fDropNextPopup = FALSE;
  1205. #if 0
  1206. /*
  1207. * We are on a menu bar hierarchy and there is only one popup
  1208. * visible. We have to cancel this popup and put focus back on
  1209. * the menu bar.
  1210. */
  1211. if (_IsIconic(ppopupmenuRoot->spwndNotify)) {
  1212. /*
  1213. * However, if we are iconic there really is no menu
  1214. * bar so let's make it easier for users and get out
  1215. * of menu mode completely.
  1216. */
  1217. xxxMNDismiss(pMenuState);
  1218. } else
  1219. #endif
  1220. /*
  1221. * If the popup is closed, a modeless menu won't
  1222. * have a window to get the keys. So modeless menu
  1223. * cancel the menu at this point. Modal menus go
  1224. * to the menu bar.
  1225. */
  1226. if (pMenuState->fModelessMenu) {
  1227. xxxMNDismiss(pMenuState);
  1228. } else {
  1229. xxxMNCloseHierarchy(ppopupmenuRoot, pMenuState);
  1230. }
  1231. } else {
  1232. ThreadLock(ppopupmenu->spwndPrevPopup, &tlpwndT);
  1233. xxxSendMessage(ppopupmenu->spwndPrevPopup, MN_CLOSEHIERARCHY,
  1234. 0, 0);
  1235. ThreadUnlock(&tlpwndT);
  1236. }
  1237. }
  1238. return;
  1239. case VK_UP:
  1240. case VK_DOWN:
  1241. if (ppopupmenu->fIsMenuBar) {
  1242. /*
  1243. * If we are on the top level menu bar, try to open the popup if
  1244. * possible.
  1245. */
  1246. if (xxxMNOpenHierarchy(ppopupmenu, pMenuState) == (PWND)-1)
  1247. return;
  1248. } else {
  1249. item = MNFindNextValidItem(ppopupmenu->spmenu,
  1250. ppopupmenu->posSelectedItem, (key == VK_UP ? -1 : 1), 0);
  1251. xxxMNSelectItem(ppopupmenu, pMenuState, item);
  1252. }
  1253. return;
  1254. case VK_LEFT:
  1255. case VK_RIGHT:
  1256. bFakedKey = (!!ppopupmenu->fRtoL) ^ (!!TestWF(ppopupmenu->spwndPopupMenu, WEFLAYOUTRTL));
  1257. if (bFakedKey)
  1258. /*
  1259. * turn the keys around, we drew the menu backwards.
  1260. */
  1261. key = (key == VK_LEFT) ? VK_RIGHT : VK_LEFT;
  1262. if (!ppopupmenu->fIsMenuBar && (key == VK_RIGHT) &&
  1263. !ppopupmenu->spwndNextPopup) {
  1264. /*
  1265. * Try to open the hierarchy at this item if there is one.
  1266. */
  1267. if (xxxMNOpenHierarchy(ppopupmenu, pMenuState) == (PWND)-1)
  1268. return;
  1269. if (ppopupmenu->fHierarchyDropped) {
  1270. return;
  1271. }
  1272. }
  1273. if (ppopupmenu->spwndNextPopup) {
  1274. fHierarchyWasDropped = TRUE;
  1275. if ((key == VK_LEFT) && !ppopupmenu->fIsMenuBar) {
  1276. xxxMNCloseHierarchy(ppopupmenu, pMenuState);
  1277. return;
  1278. }
  1279. } else if (ppopupmenu->fDropNextPopup)
  1280. fHierarchyWasDropped = TRUE;
  1281. ppopupSave = ppopupmenu;
  1282. item = MNFindItemInColumn(ppopupmenu->spmenu,
  1283. ppopupmenu->posSelectedItem,
  1284. (key == VK_LEFT ? -1 : 1),
  1285. (ppopupmenu->fHasMenuBar &&
  1286. ppopupmenu == ppopupmenu->ppopupmenuRoot));
  1287. if (item == MFMWFP_NOITEM) {
  1288. /*
  1289. * No valid item found in the given direction so send it up to our
  1290. * parent to handle.
  1291. */
  1292. if (ppopupmenu->fHasMenuBar &&
  1293. ppopupmenu->spwndPrevPopup == ppopupmenu->spwndNotify) {
  1294. /*
  1295. * if we turned the key round, then turn it back again.
  1296. */
  1297. if (bFakedKey)
  1298. key = (key == VK_LEFT) ? VK_RIGHT : VK_LEFT;
  1299. /*
  1300. * Go to next/prev item in menu bar since a popup was down and
  1301. * no item on the popup to go to.
  1302. */
  1303. xxxMNKeyDown(ppopupmenu->ppopupmenuRoot, pMenuState, key);
  1304. return;
  1305. }
  1306. if (ppopupmenu == ppopupmenu->ppopupmenuRoot) {
  1307. if (!ppopupmenu->fIsMenuBar) {
  1308. /*
  1309. * No menu bar associated with this menu so do nothing.
  1310. */
  1311. return;
  1312. }
  1313. } else {
  1314. ThreadLock(ppopupmenu->spwndPrevPopup, &tlpwndT);
  1315. xxxSendMessage(ppopupmenu->spwndPrevPopup, WM_KEYDOWN, keyOrig, 0);
  1316. ThreadUnlock(&tlpwndT);
  1317. return;
  1318. }
  1319. }
  1320. if (!ppopupmenu->fIsMenuBar) {
  1321. if (item != MFMWFP_NOITEM) {
  1322. xxxMNSelectItem(ppopupmenu, pMenuState, item);
  1323. }
  1324. return;
  1325. } else {
  1326. /*
  1327. * Special handling if keydown occurred on a menu bar.
  1328. */
  1329. if (item == MFMWFP_NOITEM) {
  1330. if (TestWF(ppopupmenu->spwndNotify, WFSYSMENU)) {
  1331. PTHREADINFO ptiCurrent = PtiCurrent();
  1332. PWND pwndNextMenu;
  1333. PMENU pmenuNextMenu, pmenuUse;
  1334. MDINEXTMENU mnm;
  1335. TL tlpmenuNextMenu;
  1336. TL tlpwndNextMenu;
  1337. mnm.hmenuIn = (HMENU)0;
  1338. mnm.hmenuNext = (HMENU)0;
  1339. mnm.hwndNext = (HWND)0;
  1340. /*
  1341. * We are in the menu bar and need to go up to the system menu
  1342. * or go from the system menu to the menu bar.
  1343. */
  1344. pmenuNextMenu = ppopupmenu->fIsSysMenu ?
  1345. _GetSubMenu(ppopupmenu->spmenu, 0) :
  1346. ppopupmenu->spmenu;
  1347. mnm.hmenuIn = PtoH(pmenuNextMenu);
  1348. ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
  1349. dwMDIMenu = xxxSendMessage(ppopupmenu->spwndNotify,
  1350. WM_NEXTMENU, (WPARAM)keyOrig, (LPARAM)&mnm);
  1351. ThreadUnlock(&tlpwndT);
  1352. pwndNextMenu = RevalidateHwnd(mnm.hwndNext);
  1353. if (pwndNextMenu == NULL)
  1354. goto TryAlternate;
  1355. /*
  1356. * If this window belongs to another thread, we cannot
  1357. * use it. The menu loop won't get any messages
  1358. * directed to that thread.
  1359. */
  1360. if (GETPTI(pwndNextMenu) != ptiCurrent) {
  1361. RIPMSG1(RIP_WARNING, "xxxMNKeyDown: Ignoring mnm.hwndNext bacause it belongs to another thread: %#p", pwndNextMenu);
  1362. goto TryAlternate;
  1363. }
  1364. pmenuNextMenu = RevalidateHmenu(mnm.hmenuNext);
  1365. if (pmenuNextMenu == NULL)
  1366. goto TryAlternate;
  1367. ThreadLock(pmenuNextMenu, &tlpmenuNextMenu);
  1368. ThreadLock(pwndNextMenu, &tlpwndNextMenu);
  1369. /*
  1370. * If the system menu is for a minimized MDI child,
  1371. * make sure the menu is dropped to give the user a
  1372. * visual clue that they are in menu mode
  1373. */
  1374. if (TestWF(pwndNextMenu, WFMINIMIZED))
  1375. fHierarchyWasDropped = TRUE;
  1376. xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
  1377. pMenuState->fIsSysMenu = TRUE;
  1378. UnlockPopupMenu(ppopupmenu, &ppopupmenu->spmenuAlternate);
  1379. ppopupmenu->fToggle = FALSE;
  1380. /*
  1381. * GetSystemMenu(pwnd, FALSE) and pwnd->spmenuSys are
  1382. * NOT equivalent -- GetSystemMenu returns the 1st submenu
  1383. * of pwnd->spmenuSys -- make up for that here
  1384. */
  1385. pmenuUse = (((pwndNextMenu->spmenuSys != NULL)
  1386. && (_GetSubMenu(pwndNextMenu->spmenuSys, 0) == pmenuNextMenu))
  1387. ? pwndNextMenu->spmenuSys
  1388. : pmenuNextMenu);
  1389. /*
  1390. * We're going to change the notification window AND the menu.
  1391. * LockPopupMenu needs to unlock the current pmenu-spwndNotify
  1392. * but also lock the new pmenu-spwndNotify. Since we cannot
  1393. * give it the current AND the new pair, we unlock the
  1394. * current one first, switch the notification window and
  1395. * then call LockPopupMenu to lock the new pmenu-spwndNotify.
  1396. */
  1397. UserAssert(IsRootPopupMenu(ppopupmenu));
  1398. UnlockPopupMenu(ppopupmenu, &ppopupmenu->spmenu);
  1399. Lock(&ppopupmenu->spwndNotify, pwndNextMenu);
  1400. Lock(&ppopupmenu->spwndPopupMenu, pwndNextMenu);
  1401. LockPopupMenu(ppopupmenu, &ppopupmenu->spmenu, pmenuUse);
  1402. /*
  1403. * We just switched to a new notification window so
  1404. * we need to Adjust capture accordingly
  1405. */
  1406. if (!pMenuState->fModelessMenu) {
  1407. ptiCurrent->pq->QF_flags &= ~QF_CAPTURELOCKED;
  1408. xxxMNSetCapture(ppopupmenu);
  1409. }
  1410. if (!TestWF(pwndNextMenu, WFCHILD) &&
  1411. ppopupmenu->spmenu != NULL) {
  1412. /*
  1413. * This window has a system menu and a main menu bar
  1414. * Set the alternate menu to the appropriate menu
  1415. */
  1416. if (pwndNextMenu->spmenu == ppopupmenu->spmenu) {
  1417. LockPopupMenu(ppopupmenu, &ppopupmenu->spmenuAlternate,
  1418. pwndNextMenu->spmenuSys);
  1419. pMenuState->fIsSysMenu = FALSE;
  1420. } else {
  1421. LockPopupMenu(ppopupmenu, &ppopupmenu->spmenuAlternate,
  1422. pwndNextMenu->spmenu);
  1423. }
  1424. }
  1425. ThreadUnlock(&tlpwndNextMenu);
  1426. ThreadUnlock(&tlpmenuNextMenu);
  1427. ppopupmenu->fIsSysMenu = pMenuState->fIsSysMenu;
  1428. item = 0;
  1429. } else
  1430. TryAlternate:
  1431. if (xxxMNSwitchToAlternateMenu(ppopupmenu)) {
  1432. /*
  1433. * go to first or last menu item int ppopup->hMenu
  1434. * based on 'key'
  1435. */
  1436. int dir = (key == VK_RIGHT) ? 1 : -1;
  1437. item = MNFindNextValidItem(ppopupmenu->spmenu, MFMWFP_NOITEM, dir, 0);
  1438. }
  1439. }
  1440. if (item != MFMWFP_NOITEM) {
  1441. /*
  1442. * we found a new menu item to go to
  1443. * 1) close up the previous menu if it was dropped
  1444. * 2) select the new menu item to go to
  1445. * 3) drop the new menu if the previous menu was dropped
  1446. */
  1447. if (ppopupSave->spwndNextPopup)
  1448. xxxMNCloseHierarchy(ppopupSave, pMenuState);
  1449. xxxMNSelectItem(ppopupmenu, pMenuState, item);
  1450. if (fHierarchyWasDropped) {
  1451. DropHierarchy:
  1452. if (xxxMNOpenHierarchy(ppopupmenu, pMenuState) == (PWND)-1) {
  1453. return;
  1454. }
  1455. }
  1456. }
  1457. }
  1458. return;
  1459. case VK_RETURN:
  1460. {
  1461. BOOL fEnabled;
  1462. PITEM pItem;
  1463. if (ppopupmenu->posSelectedItem >= ppopupmenu->spmenu->cItems) {
  1464. xxxMNDismiss(pMenuState);
  1465. return;
  1466. }
  1467. pItem = ppopupmenu->spmenu->rgItems + ppopupmenu->posSelectedItem;
  1468. fEnabled = !(pItem->fState & MFS_GRAYED);
  1469. if ((pItem->spSubMenu != NULL) && fEnabled)
  1470. goto DropHierarchy;
  1471. /*
  1472. * If no item is selected, throw away menu and return.
  1473. */
  1474. if (fEnabled) {
  1475. xxxMNDismissWithNotify(pMenuState, ppopupmenu->spmenu, pItem, ppopupmenu->posSelectedItem, 0);
  1476. } else {
  1477. xxxMNDismiss(pMenuState);
  1478. }
  1479. return;
  1480. }
  1481. case VK_F1: /* Provide context sensitive help. */
  1482. {
  1483. PITEM pItem;
  1484. pItem = MNGetpItem(ppopupmenu, ppopupmenu->posSelectedItem);
  1485. if (pItem != NULL) {
  1486. ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
  1487. xxxSendHelpMessage(ppopupmenu->spwndNotify, HELPINFO_MENUITEM, pItem->wID,
  1488. PtoHq(ppopupmenu->spmenu),
  1489. GetMenuInheritedContextHelpId(ppopupmenu));
  1490. ThreadUnlock(&tlpwndT);
  1491. }
  1492. break;
  1493. }
  1494. }
  1495. }
  1496. /***************************************************************************\
  1497. * xxxMNPositionHierarchy
  1498. *
  1499. * Calculates the x.y postion to drop a hierarchy and returns the direction
  1500. * to be used when animating (PAS_* value).
  1501. *
  1502. * 11/19/96 GerardoB Extracted from xxxMNOpenHierarchy
  1503. \***************************************************************************/
  1504. UINT xxxMNPositionHierarchy(
  1505. PPOPUPMENU ppopup,
  1506. PITEM pitem,
  1507. int cx,
  1508. int cy,
  1509. int *px,
  1510. int *py,
  1511. PMONITOR *ppMonitor)
  1512. {
  1513. int x, y;
  1514. UINT uPAS;
  1515. PMONITOR pMonitor;
  1516. UserAssert(ppopup->fHierarchyDropped && (ppopup->spwndNextPopup != NULL));
  1517. if (ppopup->fIsMenuBar) {
  1518. /*
  1519. * This is a menu being dropped from the top menu bar. We need to
  1520. * position it differently than hierarchicals which are dropped from
  1521. * another popup.
  1522. */
  1523. BOOL fIconic = (TestWF(ppopup->spwndPopupMenu, WFMINIMIZED) != 0);
  1524. RECT rcWindow;
  1525. /*
  1526. * Menu bar popups animate down.
  1527. */
  1528. uPAS = PAS_DOWN;
  1529. CopyRect(&rcWindow, &ppopup->spwndPopupMenu->rcWindow);
  1530. if (fIconic && IsTrayWindow(ppopup->spwndPopupMenu)) {
  1531. xxxSendMinRectMessages(ppopup->spwndPopupMenu, &rcWindow);
  1532. }
  1533. /*
  1534. * x position
  1535. */
  1536. if (!SYSMET(MENUDROPALIGNMENT) && !TestMF(ppopup->spmenu,MFRTL)) {
  1537. if (fIconic) {
  1538. x = rcWindow.left;
  1539. } else {
  1540. x = rcWindow.left + pitem->xItem;
  1541. }
  1542. } else {
  1543. ppopup->fDroppedLeft = TRUE;
  1544. if (fIconic) {
  1545. x = rcWindow.right - cx;
  1546. } else {
  1547. x = rcWindow.left + pitem->xItem + pitem->cxItem - cx;
  1548. }
  1549. }
  1550. /*
  1551. * For a menu bar dropdown, pin to the monitor that owns the
  1552. * majority of the menu item. Otherwise, pin to the monitor that
  1553. * owns the minimized window (the tray rect for min-to-tray dudes).
  1554. */
  1555. if (!fIconic) {
  1556. /*
  1557. * Use rcWindow as scratch for the menu bar item rect. We want
  1558. * to pin this menu on whatever monitor owns most of the menu
  1559. * item clicked on.
  1560. */
  1561. rcWindow.left += pitem->xItem;
  1562. rcWindow.top += pitem->yItem;
  1563. rcWindow.right = rcWindow.left + pitem->cxItem;
  1564. rcWindow.bottom = rcWindow.top + pitem->cyItem;
  1565. }
  1566. pMonitor = _MonitorFromRect(&rcWindow, MONITOR_DEFAULTTOPRIMARY);
  1567. /*
  1568. * y position
  1569. */
  1570. if (!fIconic) {
  1571. y = rcWindow.bottom;
  1572. } else {
  1573. /*
  1574. * If the window is iconic, pop the menu up. Since we're
  1575. * minimized, the sysmenu button doesn't really exist.
  1576. */
  1577. y = rcWindow.top - cy;
  1578. if (y < pMonitor->rcMonitor.top) {
  1579. y = rcWindow.bottom;
  1580. }
  1581. }
  1582. /*
  1583. * Make sure the menu doesn't go off the right side of the monitor
  1584. */
  1585. x = min(x, pMonitor->rcMonitor.right - cx);
  1586. if (TestWF(ppopup->spwndPopupMenu, WEFLAYOUTRTL)) {
  1587. x = ppopup->spwndPopupMenu->rcWindow.right - x + ppopup->spwndPopupMenu->rcWindow.left - cx;
  1588. }
  1589. } else {
  1590. /* Now position the hierarchical menu window.
  1591. * We want to overlap by the amount of the frame, to help in the
  1592. * 3D illusion.
  1593. */
  1594. /*
  1595. * By default, hierachical popups animate to the right
  1596. */
  1597. uPAS = PAS_RIGHT;
  1598. x = ppopup->spwndPopupMenu->rcWindow.left + pitem->xItem + pitem->cxItem;
  1599. /* Note that we DO want the selections in the item and its popup to
  1600. * align horizontally.
  1601. */
  1602. y = ppopup->spwndPopupMenu->rcWindow.top + pitem->yItem;
  1603. if (ppopup->spmenu->dwArrowsOn != MSA_OFF) {
  1604. y += gcyMenuScrollArrow - MNGetToppItem(ppopup->spmenu)->yItem;
  1605. }
  1606. /*
  1607. * Try to make sure the menu doesn't go off right side of the
  1608. * monitor. If it does, drop it left, overlapping the checkmark
  1609. * area. Unless it would cover the previous menu...
  1610. *
  1611. * Use the monitor that the parent menu is on to keep all hierarchicals
  1612. * in the same place.
  1613. */
  1614. pMonitor = _MonitorFromWindow(
  1615. ppopup->spwndPopupMenu, MONITOR_DEFAULTTOPRIMARY);
  1616. if ((!!ppopup->fDroppedLeft) ^ (!!TestWF(ppopup->spwndPopupMenu, WEFLAYOUTRTL))) {
  1617. int xTmp;
  1618. /*
  1619. * If this menu has dropped left, see if our hierarchy can be made
  1620. * to drop to the left also.
  1621. */
  1622. xTmp = ppopup->spwndPopupMenu->rcWindow.left + SYSMET(CXFIXEDFRAME) - cx;
  1623. if (xTmp >= pMonitor->rcMonitor.left) {
  1624. x = xTmp;
  1625. uPAS = PAS_LEFT;
  1626. }
  1627. }
  1628. /*
  1629. * Make sure the menu doesn't go off right side of screen. Make it drop
  1630. * left if it does.
  1631. */
  1632. if (x + cx > pMonitor->rcMonitor.right) {
  1633. x = ppopup->spwndPopupMenu->rcWindow.left + SYSMET(CXFIXEDFRAME) - cx;
  1634. uPAS = PAS_LEFT;
  1635. }
  1636. if (TestWF(ppopup->spwndPopupMenu, WEFLAYOUTRTL)) {
  1637. uPAS ^= PAS_HORZ;
  1638. }
  1639. }
  1640. /*
  1641. * Does the menu extend beyond bottom of monitor?
  1642. */
  1643. UserAssert(pMonitor);
  1644. if (y + cy > pMonitor->rcMonitor.bottom) {
  1645. y -= cy;
  1646. /*
  1647. * Try to pop above menu bar first
  1648. */
  1649. if (ppopup->fIsMenuBar) {
  1650. y -= SYSMET(CYMENUSIZE);
  1651. if (y >= pMonitor->rcMonitor.top) {
  1652. uPAS = PAS_UP;
  1653. }
  1654. } else {
  1655. /*
  1656. * Account for nonclient frame above & below
  1657. */
  1658. y += pitem->cyItem + 2*SYSMET(CYFIXEDFRAME);
  1659. }
  1660. /*
  1661. * Make sure that starting point is on a monitor, and all of menu shows.
  1662. */
  1663. if ((y < pMonitor->rcMonitor.top) || (y + cy > pMonitor->rcMonitor.bottom)) {
  1664. /*
  1665. * Pin it to the bottom.
  1666. */
  1667. y = pMonitor->rcMonitor.bottom - cy;
  1668. }
  1669. }
  1670. /*
  1671. * Make sure Upper Left corner of menu is always visible.
  1672. */
  1673. x = max(x, pMonitor->rcMonitor.left);
  1674. y = max(y, pMonitor->rcMonitor.top);
  1675. /*
  1676. * Propagate position
  1677. */
  1678. *px = x;
  1679. *py = y;
  1680. *ppMonitor = pMonitor;
  1681. /*
  1682. * Return animation direction
  1683. */
  1684. return uPAS;
  1685. }
  1686. /***************************************************************************\
  1687. * xxxMNOpenHierarchy
  1688. *
  1689. * Drops one level of the hierarchy at the selection.
  1690. *
  1691. * History:
  1692. * 05-25-91 Mikehar Ported from Win3.1
  1693. \***************************************************************************/
  1694. PWND xxxMNOpenHierarchy(
  1695. PPOPUPMENU ppopupmenu,
  1696. PMENUSTATE pMenuState)
  1697. {
  1698. PWND ret = 0;
  1699. PITEM pItem;
  1700. PWND pwndHierarchy;
  1701. PPOPUPMENU ppopupmenuHierarchy;
  1702. LONG sizeHierarchy;
  1703. int xLeft;
  1704. int yTop;
  1705. int cxPopup, cyPopup;
  1706. TL tlpwndT;
  1707. TL tlpwndHierarchy;
  1708. PTHREADINFO ptiCurrent = PtiCurrent();
  1709. PDESKTOP pdesk = ptiCurrent->rpdesk;
  1710. BOOL fSendUninit = FALSE;
  1711. HMENU hmenuInit;
  1712. PMONITOR pMonitor;
  1713. if (ppopupmenu->posSelectedItem == MFMWFP_NOITEM) {
  1714. /*
  1715. * No selection so fail.
  1716. */
  1717. return NULL;
  1718. }
  1719. if (ppopupmenu->posSelectedItem >= ppopupmenu->spmenu->cItems)
  1720. return NULL;
  1721. if (ppopupmenu->fHierarchyDropped) {
  1722. if (ppopupmenu->fHideTimer) {
  1723. xxxMNCloseHierarchy(ppopupmenu,pMenuState);
  1724. } else {
  1725. /*
  1726. * Hierarchy already dropped. What are we doing here?
  1727. */
  1728. UserAssert(!ppopupmenu->fHierarchyDropped);
  1729. return NULL;
  1730. }
  1731. }
  1732. if (ppopupmenu->fShowTimer) {
  1733. _KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNSHOW);
  1734. ppopupmenu->fShowTimer = FALSE;
  1735. }
  1736. /*
  1737. * Get a pointer to the currently selected item in this menu.
  1738. */
  1739. pItem = &(ppopupmenu->spmenu->rgItems[ppopupmenu->posSelectedItem]);
  1740. if (pItem->spSubMenu == NULL)
  1741. goto Exit;
  1742. /*
  1743. * Send the initmenupopup message.
  1744. */
  1745. if (!ppopupmenu->fNoNotify) {
  1746. ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
  1747. /*
  1748. * WordPerfect's Grammatik app doesn't know that TRUE means NON-ZERO,
  1749. * not 1. So we must use 0 & 1 explicitly for fIsSysMenu here
  1750. * -- Win95B B#4947 -- 2/13/95 -- jeffbog
  1751. */
  1752. hmenuInit = PtoHq(pItem->spSubMenu);
  1753. xxxSendMessage(ppopupmenu->spwndNotify, WM_INITMENUPOPUP,
  1754. (WPARAM)hmenuInit, MAKELONG(ppopupmenu->posSelectedItem,
  1755. (ppopupmenu->fIsSysMenu ? 1: 0)));
  1756. ThreadUnlock(&tlpwndT);
  1757. fSendUninit = TRUE;
  1758. }
  1759. /*
  1760. * B#1517
  1761. * Check if we're still in menu loop
  1762. */
  1763. if (!pMenuState->fInsideMenuLoop) {
  1764. RIPMSG0(RIP_WARNING, "Menu loop ended unexpectedly by WM_INITMENUPOPUP");
  1765. ret = (PWND)-1;
  1766. goto Exit;
  1767. }
  1768. /*
  1769. * The WM_INITMENUPOPUP message may have resulted in a change to the
  1770. * menu. Make sure the selection is still valid.
  1771. */
  1772. if (ppopupmenu->posSelectedItem >= ppopupmenu->spmenu->cItems) {
  1773. /*
  1774. * Selection is out of range, so fail.
  1775. */
  1776. goto Exit;
  1777. }
  1778. /*
  1779. * Get a pointer to the currently selected item in this menu.
  1780. * Bug #17867 - the call can cause this thing to change, so reload it.
  1781. */
  1782. pItem = &(ppopupmenu->spmenu->rgItems[ppopupmenu->posSelectedItem]);
  1783. if (TestMFS(pItem, MFS_GRAYED) || (pItem->spSubMenu == NULL) || (pItem->spSubMenu->cItems == 0)) {
  1784. /*
  1785. * The item is disabled, no longer a popup, or empty so don't drop.
  1786. */
  1787. /*
  1788. * No items in menu.
  1789. */
  1790. goto Exit;
  1791. }
  1792. /*
  1793. * Let's make sure that the current thread is in menu mode and it uses
  1794. * this pMenuState. Otherwise the window we're about to create (or set
  1795. * the thread to) will point to a different pMenuState.
  1796. */
  1797. UserAssert(ptiCurrent->pMenuState == pMenuState);
  1798. ThreadLock(ppopupmenu->spwndNotify, &tlpwndT);
  1799. pwndHierarchy = xxxNVCreateWindowEx(
  1800. WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE,
  1801. (PLARGE_STRING)MENUCLASS, NULL,
  1802. WS_POPUP | WS_BORDER, 0, 0, 100, 100, ppopupmenu->spwndNotify,
  1803. NULL, (HANDLE)ppopupmenu->spwndNotify->hModule,
  1804. (LPVOID)pItem->spSubMenu, WINVER);
  1805. ThreadUnlock(&tlpwndT);
  1806. if (!pwndHierarchy) {
  1807. goto Exit;
  1808. }
  1809. /*
  1810. * Do this so old apps don't get weird borders on the popups of
  1811. * hierarchical items!
  1812. */
  1813. ClrWF(pwndHierarchy, WFOLDUI);
  1814. ppopupmenuHierarchy = ((PMENUWND)pwndHierarchy)->ppopupmenu;
  1815. /*
  1816. * Mark this as fDelayedFree and link it
  1817. */
  1818. ppopupmenuHierarchy->fDelayedFree = TRUE;
  1819. ppopupmenuHierarchy->ppmDelayedFree = ppopupmenu->ppopupmenuRoot->ppmDelayedFree;
  1820. ppopupmenu->ppopupmenuRoot->ppmDelayedFree = ppopupmenuHierarchy;
  1821. if (TestWF(ppopupmenu->spwndPopupMenu, WEFLAYOUTRTL)) {
  1822. SetWF(pwndHierarchy, WEFLAYOUTRTL);
  1823. } else {
  1824. ClrWF(pwndHierarchy, WEFLAYOUTRTL);
  1825. }
  1826. Lock(&(ppopupmenuHierarchy->spwndNotify), ppopupmenu->spwndNotify);
  1827. #if DBG
  1828. /*
  1829. * We should associate ppopupmenuHierarchy to the same menu we sent the
  1830. * WM_INITMsENUPOPUP message. Otherwise, the WM_UNINITMENUPOPUP
  1831. * will go to the wrong window. It would be the app's fault...
  1832. */
  1833. if (!ppopupmenu->fNoNotify && (hmenuInit != PtoHq(pItem->spSubMenu))) {
  1834. RIPMSG2(RIP_WARNING, "xxxMNOpenHierarchy: bad app changed submenu from %#p to %#p",
  1835. hmenuInit, PtoHq(pItem->spSubMenu));
  1836. }
  1837. #endif
  1838. LockPopupMenu(ppopupmenuHierarchy, &ppopupmenuHierarchy->spmenu, pItem->spSubMenu);
  1839. Lock(&(ppopupmenu->spwndNextPopup), pwndHierarchy);
  1840. ppopupmenu->posDropped = ppopupmenu->posSelectedItem;
  1841. Lock(&(ppopupmenuHierarchy->spwndPrevPopup), ppopupmenu->spwndPopupMenu);
  1842. ppopupmenuHierarchy->ppopupmenuRoot = ppopupmenu->ppopupmenuRoot;
  1843. ppopupmenuHierarchy->fHasMenuBar = ppopupmenu->fHasMenuBar;
  1844. ppopupmenuHierarchy->fIsSysMenu = ppopupmenu->fIsSysMenu;
  1845. ppopupmenuHierarchy->fNoNotify = ppopupmenu->fNoNotify;
  1846. ppopupmenuHierarchy->fSendUninit = TRUE;
  1847. ppopupmenuHierarchy->fRtoL = ppopupmenu->fRtoL;
  1848. ppopupmenuHierarchy->fDroppedLeft = ppopupmenu->fDroppedLeft;
  1849. /*
  1850. * The menu window has been created and intialized so if
  1851. * something fails, the WM_UNINITMENUPOPUP message will
  1852. * be sent from xxxMNDestroyHandler
  1853. */
  1854. fSendUninit = FALSE;
  1855. /*
  1856. * Set/clear the underline flag
  1857. */
  1858. if (pMenuState->fUnderline) {
  1859. SetMF(ppopupmenuHierarchy->spmenu, MFUNDERLINE);
  1860. } else {
  1861. ClearMF(ppopupmenuHierarchy->spmenu, MFUNDERLINE);
  1862. }
  1863. ppopupmenuHierarchy->fAboutToHide = FALSE;
  1864. /*
  1865. * Find the size of the menu window and actually size it (wParam = 1)
  1866. */
  1867. ThreadLock(pwndHierarchy, &tlpwndHierarchy);
  1868. sizeHierarchy = (LONG)xxxSendMessage(pwndHierarchy, MN_SIZEWINDOW, MNSW_SIZE, 0);
  1869. if (!sizeHierarchy) {
  1870. /*
  1871. * No size for this menu so zero it and blow off.
  1872. */
  1873. UserAssert(ppopupmenuHierarchy->fDelayedFree);
  1874. if (ThreadUnlock(&tlpwndHierarchy)) {
  1875. xxxDestroyWindow(pwndHierarchy);
  1876. }
  1877. Unlock(&ppopupmenu->spwndNextPopup);
  1878. goto Exit;
  1879. }
  1880. cxPopup = LOWORD(sizeHierarchy) + 2*SYSMET(CXFIXEDFRAME);
  1881. cyPopup = HIWORD(sizeHierarchy) + 2*SYSMET(CYFIXEDFRAME);
  1882. ppopupmenu->fHierarchyDropped = TRUE;
  1883. /*
  1884. * Find out the x,y position to drop the hierarchy and the animation
  1885. * direction
  1886. */
  1887. ppopupmenuHierarchy->iDropDir = xxxMNPositionHierarchy(
  1888. ppopupmenu, pItem, cxPopup, cyPopup, &xLeft, &yTop, &pMonitor);
  1889. if (ppopupmenu->fIsMenuBar && _GetAsyncKeyState(VK_LBUTTON) & 0x8000) {
  1890. /*
  1891. * If the menu had to be pinned to the bottom of the screen and
  1892. * the mouse button is down, make sure the mouse isn't over the
  1893. * menu rect.
  1894. */
  1895. RECT rc;
  1896. RECT rcParent;
  1897. int xrightdrop;
  1898. int xleftdrop;
  1899. /*
  1900. * Get rect of hierarchical
  1901. */
  1902. CopyOffsetRect(
  1903. &rc,
  1904. &pwndHierarchy->rcWindow,
  1905. xLeft - pwndHierarchy->rcWindow.left,
  1906. yTop - pwndHierarchy->rcWindow.top);
  1907. /*
  1908. * Get the rect of the menu bar popup item
  1909. */
  1910. rcParent.left = pItem->xItem + ppopupmenu->spwndPopupMenu->rcWindow.left;
  1911. rcParent.top = pItem->yItem + ppopupmenu->spwndPopupMenu->rcWindow.top;
  1912. rcParent.right = rcParent.left + pItem->cxItem;
  1913. rcParent.bottom = rcParent.top + pItem->cyItem;
  1914. if (IntersectRect(&rc, &rc, &rcParent)) {
  1915. /*
  1916. * Oh, oh... The cursor will sit right on top of a menu item.
  1917. * If the user up clicks, a menu will be accidently selected.
  1918. *
  1919. * Calc x position of hierarchical if we dropped it to the
  1920. * right/left of the menu bar item.
  1921. */
  1922. xrightdrop = ppopupmenu->spwndPopupMenu->rcWindow.left +
  1923. pItem->xItem + pItem->cxItem + cxPopup;
  1924. if (xrightdrop > pMonitor->rcMonitor.right) {
  1925. xrightdrop = 0;
  1926. }
  1927. xleftdrop = ppopupmenu->spwndPopupMenu->rcWindow.left +
  1928. pItem->xItem - cxPopup;
  1929. if (xleftdrop < pMonitor->rcMonitor.left) {
  1930. xleftdrop = 0;
  1931. }
  1932. if (((SYSMET(MENUDROPALIGNMENT) || TestMFT(pItem, MFT_RIGHTORDER))
  1933. && xleftdrop) || !xrightdrop) {
  1934. xLeft = ppopupmenu->spwndPopupMenu->rcWindow.left +
  1935. pItem->xItem - cxPopup;
  1936. ppopupmenuHierarchy->iDropDir = PAS_LEFT;
  1937. } else if (xrightdrop) {
  1938. xLeft = ppopupmenu->spwndPopupMenu->rcWindow.left +
  1939. pItem->xItem + pItem->cxItem;
  1940. ppopupmenuHierarchy->iDropDir = PAS_RIGHT;
  1941. }
  1942. /*
  1943. * If we're going to show the menu off the screen, move it to the
  1944. * right of the cursor. This might result in part of the
  1945. * menu being shown offscreen, but it's better then the entire
  1946. * thing being hidden (and will also ensure that the popup is not
  1947. * placed under the cursor). See bug #55045.
  1948. */
  1949. if (xLeft <= pMonitor->rcMonitor.left) {
  1950. xLeft = rcParent.right;
  1951. ppopupmenuHierarchy->iDropDir = PAS_LEFT;
  1952. }
  1953. }
  1954. }
  1955. /*
  1956. * Take care of fDropNextPopup (menu bar) or fDroppedLeft (popups)
  1957. * Set animation flag
  1958. */
  1959. if (ppopupmenu->fIsMenuBar) {
  1960. /*
  1961. * Only the first popup being dropped off the menu bar
  1962. * is animated.
  1963. */
  1964. if (!ppopupmenu->fDropNextPopup) {
  1965. ppopupmenuHierarchy->iDropDir |= PAS_OUT;
  1966. }
  1967. /*
  1968. * Propagate right-to-left direction.
  1969. */
  1970. if (ppopupmenu->fDroppedLeft || (ppopupmenuHierarchy->iDropDir == PAS_LEFT)) {
  1971. ppopupmenuHierarchy->fDroppedLeft = TRUE;
  1972. }
  1973. /*
  1974. * Once a popup is dropped from the menu bar, moving to the next
  1975. * item on the menu bar should drop the popup.
  1976. */
  1977. ppopupmenu->fDropNextPopup = TRUE;
  1978. } else {
  1979. /*
  1980. * Submenus always animate.
  1981. */
  1982. ppopupmenuHierarchy->iDropDir |= PAS_OUT;
  1983. /*
  1984. * Is this popup a lefty?
  1985. */
  1986. if (ppopupmenuHierarchy->iDropDir == PAS_LEFT) {
  1987. ppopupmenuHierarchy->fDroppedLeft = TRUE;
  1988. }
  1989. }
  1990. /*
  1991. * The previous active dude must be visible
  1992. */
  1993. UserAssert((ppopupmenu->ppopupmenuRoot->spwndActivePopup == NULL)
  1994. || TestWF(ppopupmenu->ppopupmenuRoot->spwndActivePopup, WFVISIBLE));
  1995. /*
  1996. * This is the new active popup
  1997. */
  1998. Lock(&(ppopupmenu->ppopupmenuRoot->spwndActivePopup), pwndHierarchy);
  1999. /*
  2000. * Paint the owner window before the popup menu comes up so that
  2001. * the proper bits are saved.
  2002. */
  2003. if (ppopupmenuHierarchy->spwndNotify != NULL) {
  2004. ThreadLockAlways(ppopupmenuHierarchy->spwndNotify, &tlpwndT);
  2005. xxxUpdateWindow(ppopupmenuHierarchy->spwndNotify);
  2006. ThreadUnlock(&tlpwndT);
  2007. }
  2008. /*
  2009. * If this is a drag and drop menu, then we need to register the window
  2010. * as a drop target.
  2011. */
  2012. if (pMenuState->fDragAndDrop) {
  2013. if (!NT_SUCCESS(xxxClientRegisterDragDrop(HW(pwndHierarchy)))) {
  2014. RIPMSG1(RIP_ERROR, "xxxMNOpenHierarchy: xxxClientRegisterDragDrop failed:%#p", pwndHierarchy);
  2015. }
  2016. }
  2017. /*
  2018. * Show the window. Modeless menus are not topmost and get activated.
  2019. * Modal menus are topmost but don't get activated.
  2020. */
  2021. PlayEventSound(USER_SOUND_MENUPOPUP);
  2022. xxxSetWindowPos(pwndHierarchy,
  2023. (pMenuState->fModelessMenu ? PWND_TOP : PWND_TOPMOST),
  2024. xLeft, yTop, 0, 0,
  2025. SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOOWNERZORDER
  2026. | (pMenuState->fModelessMenu ? 0 : SWP_NOACTIVATE));
  2027. xxxWindowEvent(EVENT_SYSTEM_MENUPOPUPSTART, pwndHierarchy, OBJID_CLIENT, INDEXID_CONTAINER, 0);
  2028. /*
  2029. * Select the first item IFF we're in keyboard mode. This fixes a
  2030. * surprising number of compatibility problems with keyboard macros,
  2031. * scripts, etc.
  2032. */
  2033. if (pMenuState->mnFocus == KEYBDHOLD) {
  2034. xxxSendMessage(pwndHierarchy, MN_SELECTITEM, 0, 0L);
  2035. }
  2036. /*
  2037. * This is needed so that popup menus are properly drawn on sys
  2038. * modal dialog boxes.
  2039. */
  2040. xxxUpdateWindow(pwndHierarchy);
  2041. ret = pwndHierarchy;
  2042. ThreadUnlock(&tlpwndHierarchy);
  2043. Exit:
  2044. /*
  2045. * send matching WM_UNINITMENUPOPUP if needed (i.e, something
  2046. * failed).
  2047. */
  2048. if (fSendUninit
  2049. && (ppopupmenu->spwndNotify != NULL)) {
  2050. ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndT);
  2051. xxxSendMessage(ppopupmenu->spwndNotify, WM_UNINITMENUPOPUP,
  2052. (WPARAM)hmenuInit,
  2053. MAKELONG(0, (ppopupmenu->fIsSysMenu ? MF_SYSMENU : 0)));
  2054. ThreadUnlock(&tlpwndT);
  2055. }
  2056. return ret;
  2057. }
  2058. /***************************************************************************\
  2059. * xxxMNHideNextHierarchy
  2060. *
  2061. * Closes any submenu coming off of this popup.
  2062. \***************************************************************************/
  2063. BOOL xxxMNHideNextHierarchy(
  2064. PPOPUPMENU ppopup)
  2065. {
  2066. if (ppopup->spwndNextPopup != NULL) {
  2067. TL tlpwndT;
  2068. ThreadLockAlways(ppopup->spwndNextPopup, &tlpwndT);
  2069. if (ppopup->spwndNextPopup != ppopup->spwndActivePopup)
  2070. xxxSendMessage(ppopup->spwndNextPopup, MN_CLOSEHIERARCHY, 0, 0L);
  2071. xxxSendMessage(ppopup->spwndNextPopup, MN_SELECTITEM, (WPARAM)-1, 0L);
  2072. ThreadUnlock(&tlpwndT);
  2073. return TRUE;
  2074. }
  2075. return FALSE;
  2076. }
  2077. /***************************************************************************\
  2078. * xxxMNCloseHierarchy
  2079. *
  2080. * Close all hierarchies from this window down.
  2081. *
  2082. * History:
  2083. * 05-25-91 Mikehar Ported from Win3.1
  2084. \***************************************************************************/
  2085. VOID xxxMNCloseHierarchy(
  2086. PPOPUPMENU ppopupmenu,
  2087. PMENUSTATE pMenuState)
  2088. {
  2089. TL tlpwndNext;
  2090. TL tlpwnd;
  2091. TL tlpopup;
  2092. PTHREADINFO ptiCurrent = PtiCurrent();
  2093. PDESKTOP pdesk;
  2094. PWND pwndNext;
  2095. Validateppopupmenu(ppopupmenu);
  2096. /*
  2097. * Terminate any animation
  2098. */
  2099. MNAnimate(pMenuState, FALSE);
  2100. /*
  2101. * If a hierarchy exists, close all childen below us. Do it in reversed
  2102. * order so savebits work out.
  2103. */
  2104. if (!ppopupmenu->fHierarchyDropped) {
  2105. /*
  2106. * Assert that there's no next or it might not get closed
  2107. */
  2108. UserAssert(ppopupmenu->spwndNextPopup == NULL);
  2109. return;
  2110. }
  2111. if (ppopupmenu->fHideTimer)
  2112. {
  2113. _KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNHIDE);
  2114. ppopupmenu->fHideTimer = FALSE;
  2115. }
  2116. pwndNext = ppopupmenu->spwndNextPopup;
  2117. if (pwndNext != NULL) {
  2118. ThreadLockAlways(pwndNext, &tlpwndNext);
  2119. xxxSendMessage(pwndNext, MN_CLOSEHIERARCHY, 0, 0);
  2120. /*
  2121. * If modeless menu, activate the this popup since we're about
  2122. * to destroy the current active one. We want to keep activation
  2123. * on a menu window so we can get the keys. Also, modeless menus
  2124. * are canceled when a non-menu window is activated in their queue
  2125. */
  2126. if (pMenuState->fModelessMenu
  2127. && pMenuState->fInsideMenuLoop
  2128. && !ppopupmenu->fIsMenuBar) {
  2129. ThreadLockAlways(ppopupmenu->spwndPopupMenu, &tlpwnd);
  2130. xxxActivateThisWindow(ppopupmenu->spwndPopupMenu, 0, 0);
  2131. ThreadUnlock(&tlpwnd);
  2132. }
  2133. xxxWindowEvent(EVENT_SYSTEM_MENUPOPUPEND, pwndNext, OBJID_CLIENT, INDEXID_CONTAINER, 0);
  2134. /*
  2135. * If the current thread is not in the right pdesk, then that could
  2136. * be the cause of the stuck menu bug.
  2137. * In other words, are we nuking this menu out of context?
  2138. */
  2139. UserAssert(ptiCurrent->pMenuState != NULL);
  2140. pdesk = ptiCurrent->rpdesk;
  2141. if (ThreadUnlock(&tlpwndNext)) {
  2142. xxxDestroyWindow(pwndNext);
  2143. }
  2144. Unlock(&ppopupmenu->spwndNextPopup);
  2145. ppopupmenu->fHierarchyDropped = FALSE;
  2146. }
  2147. if (ppopupmenu->fIsMenuBar) {
  2148. Unlock(&ppopupmenu->spwndActivePopup);
  2149. } else {
  2150. Lock(&(ppopupmenu->ppopupmenuRoot->spwndActivePopup),
  2151. ppopupmenu->spwndPopupMenu);
  2152. }
  2153. if (pMenuState->fInsideMenuLoop &&
  2154. (ppopupmenu->posSelectedItem != MFMWFP_NOITEM)) {
  2155. /*
  2156. * Send a menu select as if this item had just been selected. This
  2157. * allows people to easily update their menu status bars when a
  2158. * hierarchy from this item has been closed.
  2159. */
  2160. PWND pwnd = ppopupmenu->ppopupmenuRoot->spwndNotify;
  2161. if (pwnd) {
  2162. ThreadLockAlways(pwnd, &tlpwnd);
  2163. ThreadLockAlways(ppopupmenu->spwndPopupMenu, &tlpopup);
  2164. xxxSendMenuSelect(pwnd, ppopupmenu->spwndPopupMenu,
  2165. ppopupmenu->spmenu, ppopupmenu->posSelectedItem);
  2166. ThreadUnlock(&tlpopup);
  2167. ThreadUnlock(&tlpwnd);
  2168. }
  2169. }
  2170. }
  2171. /***************************************************************************\
  2172. * xxxMNDoubleClick
  2173. *
  2174. * If an item isn't a hierarchical, then the double-click works just like
  2175. * single click did. Otherwise, we traverse the submenu hierarchy to find
  2176. * a valid default element. If we reach a submenu that has no valid default
  2177. * subitems and it itself has a valid ID, that becomes the valid default
  2178. * element.
  2179. *
  2180. * Note: This function does not remove the double click message
  2181. * from the message queue, so the caller must do so.
  2182. *
  2183. * BOGUS
  2184. * How about opening the hierarchies if we don't find anything?
  2185. *
  2186. * Returns TRUE if handled.
  2187. \***************************************************************************/
  2188. BOOL xxxMNDoubleClick(
  2189. PMENUSTATE pMenuState,
  2190. PPOPUPMENU ppopup,
  2191. int idxItem)
  2192. {
  2193. PMENU pMenu;
  2194. PITEM pItem;
  2195. MSG msg;
  2196. UINT uPos;
  2197. /*
  2198. * This code to swallow double clicks isn't executed! MNLoop will
  2199. * swallow all double clicks for us. Swallow the up button for the
  2200. * double dude instead. Word will not be happy if they get a spurious
  2201. * WM_LBUTTONUP on the menu bar if their code to close the MDI child
  2202. * doesn't swallow it soon enough.
  2203. */
  2204. /*
  2205. * Eat the click.
  2206. */
  2207. if (xxxPeekMessage(&msg, NULL, 0, 0, PM_NOYIELD)) {
  2208. if ((msg.message == WM_LBUTTONUP) ||
  2209. (msg.message == WM_NCLBUTTONUP)) {
  2210. xxxPeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE);
  2211. }
  2212. #if DBG
  2213. else if (msg.message == WM_LBUTTONDBLCLK ||
  2214. msg.message == WM_NCLBUTTONDBLCLK)
  2215. {
  2216. UserAssertMsg0(FALSE, "xxxMNDoubleClick found a double click");
  2217. }
  2218. #endif
  2219. }
  2220. /*
  2221. * Get current item.
  2222. */
  2223. pMenu = ppopup->spmenu;
  2224. if ((pMenu==NULL) || ((UINT)idxItem >= pMenu->cItems)) {
  2225. xxxMNDoScroll(ppopup, ppopup->posSelectedItem, FALSE);
  2226. goto Done;
  2227. }
  2228. pItem = pMenu->rgItems + idxItem;
  2229. uPos = idxItem;
  2230. /*
  2231. * Do nothing if item is disabled.
  2232. */
  2233. if (pItem->fState & MFS_GRAYED) {
  2234. goto Done;
  2235. }
  2236. /*
  2237. * Traverse the hierarchy down as far as possible.
  2238. */
  2239. do
  2240. {
  2241. if (pItem->spSubMenu != NULL) {
  2242. /*
  2243. * The item is a popup menu, so continue traversing.
  2244. */
  2245. pMenu = pItem->spSubMenu;
  2246. idxItem = (UINT)_GetMenuDefaultItem(pMenu, MF_BYPOSITION, 0);
  2247. if (idxItem != -1) {
  2248. pItem = pMenu->rgItems + idxItem;
  2249. uPos = idxItem;
  2250. continue;
  2251. } else /* if (lpItem->wID == -1) How do we know this popup has an ID? */
  2252. break;
  2253. }
  2254. /*
  2255. * We've found a leaf node of some kind, either a MFS_DEFAULT popup
  2256. * with a valid cmd ID that has no valid MFS_DEFAULT children, or
  2257. * a real cmd with MFS_DEFAULT style.
  2258. *
  2259. * Exit menu mode and send command ID.
  2260. */
  2261. /*
  2262. * For old apps we need to generate a WM_MENUSELECT message first.
  2263. * Old apps, esp. Word 6.0, can't handle double-clicks on maximized
  2264. * child sys menus because they never get a WM_MENUSELECT for the
  2265. * item, unlike with normal keyboard/mouse choosing. We need to
  2266. * fake it so they won't fault. Several VB apps have a similar
  2267. * problem.
  2268. */
  2269. if (!TestWF(ppopup->ppopupmenuRoot->spwndNotify, WFWIN40COMPAT)) {
  2270. TL tlpwndNotify, tlpopup;
  2271. ThreadLock(ppopup->ppopupmenuRoot->spwndNotify, &tlpwndNotify);
  2272. ThreadLock(ppopup->spwndPopupMenu, &tlpopup);
  2273. xxxSendMenuSelect(ppopup->ppopupmenuRoot->spwndNotify,
  2274. ppopup->spwndPopupMenu, pMenu, idxItem);
  2275. ThreadUnlock(&tlpopup);
  2276. ThreadUnlock(&tlpwndNotify);
  2277. }
  2278. xxxMNDismissWithNotify(pMenuState, pMenu, pItem, uPos, 0);
  2279. return TRUE;
  2280. } while (TRUE);
  2281. Done:
  2282. return FALSE;
  2283. }
  2284. /***************************************************************************\
  2285. * xxxMNSelectItem
  2286. *
  2287. * Unselects the old selection, selects the item at itemPos and highlights it.
  2288. *
  2289. * MFMWFP_NOITEM if no item is to be selected.
  2290. *
  2291. * Returns the item flags of the item being selected.
  2292. *
  2293. * History:
  2294. * 05-25-91 Mikehar Ported from Win3.1
  2295. \***************************************************************************/
  2296. PITEM xxxMNSelectItem(
  2297. PPOPUPMENU ppopupmenu,
  2298. PMENUSTATE pMenuState,
  2299. UINT itemPos)
  2300. {
  2301. PITEM pItem = NULL;
  2302. TL tlpwndNotify;
  2303. TL tlpwndPopup;
  2304. TL tlpmenu;
  2305. PWND pwndNotify;
  2306. PMENU pmenu;
  2307. if (ppopupmenu->posSelectedItem == itemPos) {
  2308. /*
  2309. * If this item is already selectected, just return its flags.
  2310. */
  2311. if ((itemPos != MFMWFP_NOITEM) && (itemPos < ppopupmenu->spmenu->cItems)) {
  2312. return &(ppopupmenu->spmenu->rgItems[itemPos]);
  2313. }
  2314. return NULL;
  2315. }
  2316. /*
  2317. * Terminate any animation
  2318. */
  2319. MNAnimate(pMenuState, FALSE);
  2320. if (ppopupmenu->fShowTimer) {
  2321. _KillTimer(ppopupmenu->spwndPopupMenu, IDSYS_MNSHOW);
  2322. ppopupmenu->fShowTimer = FALSE;
  2323. }
  2324. ThreadLock(pmenu = ppopupmenu->spmenu, &tlpmenu);
  2325. ThreadLock(pwndNotify = ppopupmenu->spwndNotify, &tlpwndNotify);
  2326. if (ppopupmenu->fAboutToHide) {
  2327. PPOPUPMENU ppopupPrev = ((PMENUWND)(ppopupmenu->spwndPrevPopup))->ppopupmenu;
  2328. _KillTimer(ppopupPrev->spwndPopupMenu, IDSYS_MNHIDE);
  2329. ppopupPrev->fHideTimer = FALSE;
  2330. if (ppopupPrev->fShowTimer) {
  2331. _KillTimer(ppopupPrev->spwndPopupMenu, IDSYS_MNSHOW);
  2332. ppopupPrev->fShowTimer = FALSE;
  2333. }
  2334. if (ppopupPrev->posSelectedItem != ppopupPrev->posDropped) {
  2335. TL tlpmenuPopupMenuPrev;
  2336. ThreadLock(ppopupPrev->spmenu, &tlpmenuPopupMenuPrev);
  2337. if (ppopupPrev->posSelectedItem != MFMWFP_NOITEM) {
  2338. xxxMNInvertItem(ppopupPrev, ppopupPrev->spmenu,
  2339. ppopupPrev->posSelectedItem, ppopupPrev->spwndNotify, FALSE);
  2340. }
  2341. ppopupPrev->posSelectedItem = ppopupPrev->posDropped;
  2342. xxxMNInvertItem(ppopupPrev, ppopupPrev->spmenu,
  2343. ppopupPrev->posDropped, ppopupPrev->spwndNotify, TRUE);
  2344. ThreadUnlock(&tlpmenuPopupMenuPrev);
  2345. }
  2346. ppopupmenu->fAboutToHide = FALSE;
  2347. Lock(&ppopupmenu->ppopupmenuRoot->spwndActivePopup, ppopupmenu->spwndPopupMenu);
  2348. }
  2349. if (MNIsItemSelected(ppopupmenu)) {
  2350. /*
  2351. * Something else is selected so we need to unselect it.
  2352. */
  2353. if (ppopupmenu->spwndNextPopup) {
  2354. if (ppopupmenu->fIsMenuBar) {
  2355. xxxMNCloseHierarchy(ppopupmenu, pMenuState);
  2356. } else {
  2357. MNSetTimerToCloseHierarchy(ppopupmenu);
  2358. }
  2359. }
  2360. goto DeselectItem;
  2361. } else if (MNIsScrollArrowSelected(ppopupmenu)) {
  2362. _KillTimer(ppopupmenu->spwndPopupMenu, ppopupmenu->posSelectedItem);
  2363. DeselectItem:
  2364. xxxMNInvertItem(ppopupmenu, pmenu,
  2365. ppopupmenu->posSelectedItem, pwndNotify, FALSE);
  2366. }
  2367. ppopupmenu->posSelectedItem = itemPos;
  2368. if (itemPos != MFMWFP_NOITEM) {
  2369. /*
  2370. * If an item is selected, no autodismiss plus this means
  2371. * that the mouse is on the menu
  2372. */
  2373. pMenuState->fAboutToAutoDismiss =
  2374. pMenuState->fMouseOffMenu = FALSE;
  2375. if (pMenuState->fButtonDown) {
  2376. xxxMNDoScroll(ppopupmenu, itemPos, TRUE);
  2377. }
  2378. pItem = xxxMNInvertItem(ppopupmenu, pmenu,
  2379. itemPos, pwndNotify, TRUE);
  2380. ThreadUnlock(&tlpwndNotify);
  2381. ThreadUnlock(&tlpmenu);
  2382. return pItem;
  2383. } else {
  2384. /*
  2385. * Notify that nothing is now focused in this menu.
  2386. */
  2387. xxxWindowEvent(EVENT_OBJECT_FOCUS, ppopupmenu->spwndPopupMenu,
  2388. ((ppopupmenu->spwndNotify != ppopupmenu->spwndPopupMenu) ? OBJID_CLIENT :
  2389. (ppopupmenu->fIsSysMenu ? OBJID_SYSMENU : OBJID_MENU)), 0, 0);
  2390. }
  2391. ThreadUnlock(&tlpwndNotify);
  2392. ThreadUnlock(&tlpmenu);
  2393. if (ppopupmenu->spwndPrevPopup != NULL) {
  2394. PPOPUPMENU pp;
  2395. /*
  2396. * Get the popupMenu data for the previous menu
  2397. * Use the root popupMenu if the previous menu is the menu bar
  2398. */
  2399. if (ppopupmenu->fHasMenuBar && (ppopupmenu->spwndPrevPopup ==
  2400. ppopupmenu->spwndNotify)) {
  2401. pp = ppopupmenu->ppopupmenuRoot;
  2402. } else {
  2403. #ifdef HAVE_MN_GETPPOPUPMENU
  2404. TL tlpwndPrevPopup;
  2405. ThreadLock(ppopupmenu->spwndPrevPopup, &tlpwndPrevPopup);
  2406. pp = (PPOPUPMENU)xxxSendMessage(ppopupmenu->spwndPrevPopup,
  2407. MN_GETPPOPUPMENU, 0, 0L);
  2408. ThreadUnlock(&tlpwndPrevPopup);
  2409. #else
  2410. pp = ((PMENUWND)ppopupmenu->spwndPrevPopup)->ppopupmenu;
  2411. #endif
  2412. }
  2413. /*
  2414. * Generate a WM_MENUSELECT for the previous menu to re-establish
  2415. * it's current item as the SELECTED item
  2416. */
  2417. ThreadLock(pp->spwndNotify, &tlpwndNotify);
  2418. ThreadLock(pp->spwndPopupMenu, &tlpwndPopup);
  2419. xxxSendMenuSelect(pp->spwndNotify, pp->spwndPopupMenu, pp->spmenu, pp->posSelectedItem);
  2420. ThreadUnlock(&tlpwndPopup);
  2421. ThreadUnlock(&tlpwndNotify);
  2422. }
  2423. return NULL;
  2424. }
  2425. /***************************************************************************\
  2426. * MNItemHitTest
  2427. *
  2428. * Given a hMenu and a point in screen coordinates, returns the position
  2429. * of the item the point is in. Returns -1 if no item exists there.
  2430. *
  2431. \***************************************************************************/
  2432. UINT MNItemHitTest(
  2433. PMENU pMenu,
  2434. PWND pwnd,
  2435. POINT pt)
  2436. {
  2437. PITEM pItem;
  2438. UINT iItem;
  2439. RECT rect;
  2440. PTHREADINFO ptiCurrent = PtiCurrent();
  2441. if (pMenu->cItems == 0)
  2442. return MFMWFP_NOITEM;
  2443. /*
  2444. * This point is screen-relative. Menu bar coordinates relative
  2445. * to the window. But popup menu coordinates are relative to the client.
  2446. */
  2447. if (TestMF(pMenu, MFISPOPUP)) {
  2448. /*
  2449. * Bail if it's outside rcWindow
  2450. */
  2451. CopyInflateRect(&rect, &(pwnd->rcWindow),
  2452. -SYSMET(CXFIXEDFRAME), -SYSMET(CYFIXEDFRAME));
  2453. if (!PtInRect(&rect, pt)) {
  2454. return MFMWFP_NOITEM;
  2455. }
  2456. /* ScreenToClient */
  2457. if (TestWF(pwnd, WEFLAYOUTRTL)) {
  2458. pt.x = pwnd->rcClient.right - pt.x;
  2459. } else {
  2460. pt.x -= pwnd->rcClient.left;
  2461. }
  2462. pt.y -= pwnd->rcClient.top;
  2463. /*
  2464. * If on the non client area, then it's on the scroll arrows
  2465. */
  2466. if (pt.y < 0) {
  2467. return MFMWFP_UPARROW;
  2468. } else if (pt.y > (int)pMenu->cyMenu) {
  2469. return MFMWFP_DOWNARROW;
  2470. }
  2471. } else {
  2472. /* ScreenToWindow */
  2473. if (TestWF(pwnd, WEFLAYOUTRTL) &&
  2474. (
  2475. (ptiCurrent->pq->codeCapture == SCREEN_CAPTURE) || (ptiCurrent->pq->codeCapture == NO_CAP_SYS)
  2476. )
  2477. ) {
  2478. pt.x = pwnd->rcWindow.right - pt.x;
  2479. } else {
  2480. pt.x -= pwnd->rcWindow.left;
  2481. }
  2482. pt.y -= pwnd->rcWindow.top;
  2483. }
  2484. /*
  2485. * Step through all the items in the menu.
  2486. * If scrollable menu
  2487. */
  2488. if (pMenu->dwArrowsOn != MSA_OFF) {
  2489. UserAssert(TestMF(pMenu, MFISPOPUP));
  2490. pItem = MNGetToppItem(pMenu);
  2491. rect.left = rect.top = 0;
  2492. rect.right = pItem->cxItem;
  2493. rect.bottom = pItem->cyItem;
  2494. for (iItem = pMenu->iTop; (iItem < (int)pMenu->cItems) && (rect.top < (int)pMenu->cyMenu); iItem++) {
  2495. if (PtInRect(&rect, pt)) {
  2496. return iItem;
  2497. }
  2498. pItem++;
  2499. rect.top = rect.bottom;
  2500. rect.bottom += pItem->cyItem;
  2501. }
  2502. } else {
  2503. /*
  2504. * No scroll bars.
  2505. */
  2506. for (iItem = 0, pItem = pMenu->rgItems; iItem < pMenu->cItems; iItem++, pItem++) {
  2507. /* Is the mouse inside this item's rectangle? */
  2508. rect.left = pItem->xItem;
  2509. rect.top = pItem->yItem;
  2510. rect.right = pItem->xItem + pItem->cxItem;
  2511. rect.bottom = pItem->yItem + pItem->cyItem;
  2512. if (PtInRect(&rect, pt)) {
  2513. return iItem;
  2514. }
  2515. }
  2516. }
  2517. return MFMWFP_NOITEM;
  2518. }
  2519. /***************************************************************************\
  2520. * LockMFMWFPWindow
  2521. *
  2522. * This function is called when we need to save the return value of
  2523. * xxxMNFindWindowFromPoint.
  2524. *
  2525. * History:
  2526. * 11/14/96 GerardoB Created
  2527. \***************************************************************************/
  2528. VOID LockMFMWFPWindow(
  2529. PULONG_PTR puHitArea,
  2530. ULONG_PTR uNewHitArea)
  2531. {
  2532. /*
  2533. * Bail if there is nothing to do.
  2534. */
  2535. if (*puHitArea == uNewHitArea) {
  2536. return;
  2537. }
  2538. /*
  2539. * Unlock current hit area
  2540. */
  2541. UnlockMFMWFPWindow(puHitArea);
  2542. /*
  2543. * Lock new hit area
  2544. */
  2545. if (IsMFMWFPWindow(uNewHitArea)) {
  2546. Lock(puHitArea, (PWND)uNewHitArea);
  2547. } else {
  2548. *puHitArea = uNewHitArea;
  2549. }
  2550. }
  2551. /***************************************************************************\
  2552. * UnlockMFMWFPWindow
  2553. *
  2554. * You must call this if you ever called LockMFMWFPWindow.
  2555. *
  2556. * History:
  2557. * 11/14/96 GerardoB Created
  2558. \***************************************************************************/
  2559. VOID UnlockMFMWFPWindow(
  2560. PULONG_PTR puHitArea)
  2561. {
  2562. if (IsMFMWFPWindow(*puHitArea)) {
  2563. Unlock(puHitArea);
  2564. } else {
  2565. *puHitArea = MFMWFP_OFFMENU;
  2566. }
  2567. }
  2568. /***************************************************************************\
  2569. * IsMFMWFPWindow
  2570. *
  2571. * Test whether or not the return value of xxxMNFindWindowFromPoint is
  2572. * a window. Not that uHitArea could be an HWND or a PWND.
  2573. *
  2574. * History:
  2575. * 10-02-96 GerardoB Created
  2576. \***************************************************************************/
  2577. BOOL IsMFMWFPWindow(
  2578. ULONG_PTR uHitArea)
  2579. {
  2580. switch(uHitArea) {
  2581. case MFMWFP_OFFMENU:
  2582. case MFMWFP_NOITEM:
  2583. case MFMWFP_ALTMENU:
  2584. return FALSE;
  2585. default:
  2586. return TRUE;
  2587. }
  2588. }
  2589. /***************************************************************************\
  2590. * xxxMNFindWindowFromPoint
  2591. *
  2592. * Determines in which window the point lies.
  2593. *
  2594. * Returns
  2595. * - PWND of the hierarchical menu the point is on,
  2596. * - MFMWFP_ALTMENU if point lies on the alternate popup menu.
  2597. * - MFMWFP_NOITEM if there is no item at that point on the menu or the
  2598. * point lies on the menu bar.
  2599. * - MFMWFP_OFFMENU if point lies elsewhere.
  2600. *
  2601. * Returns in pIndex
  2602. * - the index of the item hit,
  2603. * - MFMWFP_NOITEM if there is no item at that point on the menu or
  2604. * point lies on the menu bar.
  2605. *
  2606. * History:
  2607. * 05-25-91 Mikehar Ported from Win3.1
  2608. * 8-11-92 Sanfords added MFMWFP_ constants
  2609. \***************************************************************************/
  2610. LONG_PTR xxxMNFindWindowFromPoint(
  2611. PPOPUPMENU ppopupmenu,
  2612. PUINT pIndex,
  2613. POINTS screenPt)
  2614. {
  2615. POINT pt;
  2616. RECT rect;
  2617. LONG_PTR longHit;
  2618. UINT itemHit;
  2619. PWND pwnd;
  2620. TL tlpwndT;
  2621. int cx;
  2622. *pIndex = 0;
  2623. if (ppopupmenu->spwndNextPopup) {
  2624. /*
  2625. * Check if this point is on any of our children before checking if it
  2626. * is on ourselves.
  2627. */
  2628. ThreadLockAlways(ppopupmenu->spwndNextPopup, &tlpwndT);
  2629. longHit = xxxSendMessage(ppopupmenu->spwndNextPopup,
  2630. MN_FINDMENUWINDOWFROMPOINT, (WPARAM)&itemHit,
  2631. MAKELONG(screenPt.x, screenPt.y));
  2632. ThreadUnlock(&tlpwndT);
  2633. /*
  2634. * If return value is an hwnd, convert to pwnd.
  2635. */
  2636. if (IsMFMWFPWindow(longHit)) {
  2637. longHit = (LONG_PTR)RevalidateHwnd((HWND)longHit);
  2638. }
  2639. if (longHit) {
  2640. /*
  2641. * Hit occurred on one of our children.
  2642. */
  2643. *pIndex = itemHit;
  2644. return longHit;
  2645. }
  2646. }
  2647. if (ppopupmenu->fIsMenuBar) {
  2648. int cBorders;
  2649. /*
  2650. * Check if this point is on the menu bar
  2651. */
  2652. pwnd = ppopupmenu->spwndNotify;
  2653. if (pwnd == NULL) {
  2654. return MFMWFP_OFFMENU;
  2655. }
  2656. pt.x = screenPt.x;
  2657. pt.y = screenPt.y;
  2658. if (ppopupmenu->fIsSysMenu) {
  2659. if (!_HasCaptionIcon(pwnd)) {
  2660. /*
  2661. * no system menu rect to click in if it doesn't have an icon
  2662. */
  2663. return 0L;
  2664. }
  2665. /*
  2666. * Check if this is a click on the system menu icon.
  2667. */
  2668. if (TestWF(pwnd, WFMINIMIZED)) {
  2669. /*
  2670. * If the window is minimized, then check if there was a hit in
  2671. * the client area of the icon's window.
  2672. */
  2673. /*
  2674. * Mikehar 5/27
  2675. * Don't know how this ever worked. If we are the system menu of an icon
  2676. * we want to punt the menus if the click occurs ANYWHERE outside of the
  2677. * menu.
  2678. * Johnc 03-Jun-1992 the next 4 lines were commented out for Mike's
  2679. * problem above but that made clicking on a minimized window with
  2680. * the system menu already up, bring down the menu and put it right
  2681. * up again (bug 10951) because the mnloop wouldn't swallow the mouse
  2682. * down click message. The problem Mike mentions no longer shows up.
  2683. */
  2684. if (PtInRect(&(pwnd->rcWindow), pt)) {
  2685. return MFMWFP_NOITEM;
  2686. }
  2687. /*
  2688. * It's an iconic window, so can't be hitting anywhere else.
  2689. */
  2690. return MFMWFP_OFFMENU;
  2691. }
  2692. /*
  2693. * Check if we are hitting on the system menu rectangle on the top
  2694. * left of windows.
  2695. */
  2696. rect.top = rect.left = 0;
  2697. rect.right = SYSMET(CXSIZE);
  2698. rect.bottom = SYSMET(CYSIZE);
  2699. cBorders = GetWindowBorders(pwnd->style, pwnd->ExStyle, TRUE, FALSE);
  2700. OffsetRect(&rect, pwnd->rcWindow.left + cBorders*SYSMET(CXBORDER),
  2701. pwnd->rcWindow.top + cBorders*SYSMET(CYBORDER));
  2702. /*
  2703. * Mirror the rect because the buttons in the left hand side of the window if it mirrored
  2704. */
  2705. if (TestWF(pwnd, WEFLAYOUTRTL)) {
  2706. cx = rect.right - rect.left;
  2707. rect.right = pwnd->rcWindow.right - (rect.left - pwnd->rcWindow.left);
  2708. rect.left = rect.right - cx;
  2709. }
  2710. if (PtInRect(&rect, pt)) {
  2711. *pIndex = 0;
  2712. return MFMWFP_NOITEM;
  2713. }
  2714. /*
  2715. * Check if we hit in the alternate menu if available.
  2716. */
  2717. if (ppopupmenu->spmenuAlternate) {
  2718. itemHit = MNItemHitTest(ppopupmenu->spmenuAlternate, pwnd, pt);
  2719. if (itemHit != MFMWFP_NOITEM) {
  2720. *pIndex = itemHit;
  2721. return MFMWFP_ALTMENU;
  2722. }
  2723. }
  2724. return MFMWFP_OFFMENU;
  2725. } else {
  2726. if (TestWF(ppopupmenu->spwndNotify, WFMINIMIZED)) {
  2727. /*
  2728. * If we are minimized, we can't hit on the main menu bar.
  2729. */
  2730. return MFMWFP_OFFMENU;
  2731. }
  2732. }
  2733. } else {
  2734. pwnd = ppopupmenu->spwndPopupMenu;
  2735. /*
  2736. * else this is a popup window and we need to check if we are hitting
  2737. * anywhere on this popup window.
  2738. */
  2739. pt.x = screenPt.x;
  2740. pt.y = screenPt.y;
  2741. if (!PtInRect(&pwnd->rcWindow, pt)) {
  2742. /*
  2743. * Point completely outside the popup menu window so return 0.
  2744. */
  2745. return MFMWFP_OFFMENU;
  2746. }
  2747. }
  2748. pt.x = screenPt.x;
  2749. pt.y = screenPt.y;
  2750. itemHit = MNItemHitTest(ppopupmenu->spmenu, pwnd, pt);
  2751. if (ppopupmenu->fIsMenuBar) {
  2752. /*
  2753. * If hit is on menu bar but no item is there, treat it as if the user
  2754. * hit nothing.
  2755. */
  2756. if (itemHit == MFMWFP_NOITEM) {
  2757. /*
  2758. * Check if we hit in the alternate menu if available.
  2759. */
  2760. if (ppopupmenu->spmenuAlternate) {
  2761. itemHit = MNItemHitTest(ppopupmenu->spmenuAlternate, pwnd, pt);
  2762. if (itemHit != MFMWFP_NOITEM) {
  2763. *pIndex = itemHit;
  2764. return MFMWFP_ALTMENU;
  2765. }
  2766. }
  2767. return MFMWFP_OFFMENU;
  2768. }
  2769. *pIndex = itemHit;
  2770. return MFMWFP_NOITEM;
  2771. } else {
  2772. /*
  2773. * If hit is on popup menu but no item is there, itemHit
  2774. * will be MFMWFP_NOITEM
  2775. */
  2776. *pIndex = itemHit;
  2777. return (LONG_PTR)pwnd;
  2778. }
  2779. return MFMWFP_OFFMENU;
  2780. }
  2781. /***************************************************************************\
  2782. * xxxMNCancel
  2783. *
  2784. * Should only be sent to the top most ppopupmenu/menu window in the
  2785. * hierarchy.
  2786. *
  2787. * History:
  2788. * 05-25-91 Mikehar Ported from Win3.1
  2789. \***************************************************************************/
  2790. VOID xxxMNCancel(
  2791. PMENUSTATE pMenuState,
  2792. UINT uMsg,
  2793. UINT cmd,
  2794. LPARAM lParam)
  2795. {
  2796. PPOPUPMENU ppopupmenu = pMenuState->pGlobalPopupMenu;
  2797. BOOL fSynchronous = ppopupmenu->fSynchronous;
  2798. BOOL fTrackFlagsSet = ppopupmenu->fIsTrackPopup;
  2799. BOOL fIsSysMenu = ppopupmenu->fIsSysMenu;
  2800. BOOL fIsMenuBar = ppopupmenu->fIsMenuBar;
  2801. BOOL fNotify = !ppopupmenu->fNoNotify;
  2802. PWND pwndT;
  2803. TL tlpwndT;
  2804. TL tlpwndPopupMenu;
  2805. Validateppopupmenu(ppopupmenu);
  2806. pMenuState->fInsideMenuLoop = FALSE;
  2807. pMenuState->fButtonDown = FALSE;
  2808. /*
  2809. * Mark the popup as destroyed so people will not use it anymore.
  2810. * This means that root popups can be marked as destroyed before
  2811. * actually being destroyed (nice and confusing).
  2812. */
  2813. ppopupmenu->fDestroyed = TRUE;
  2814. /*
  2815. * Only the menu loop owner can destroy the menu windows (i.e, xxxMNCloseHierarchy)
  2816. */
  2817. if (PtiCurrent() != pMenuState->ptiMenuStateOwner) {
  2818. RIPMSG1(RIP_WARNING, "xxxMNCancel: Thread %#p doesn't own the menu loop", PtiCurrent());
  2819. return;
  2820. }
  2821. /*
  2822. * If the menu loop is running on a thread different than the thread
  2823. * that owns spwndNotify, we can have two threads trying to cancel
  2824. * this popup at the same time.
  2825. */
  2826. if (ppopupmenu->fInCancel) {
  2827. RIPMSG1(RIP_WARNING, "xxxMNCancel: already in cancel. ppopupmenu:%#p", ppopupmenu);
  2828. return;
  2829. }
  2830. ppopupmenu->fInCancel = TRUE;
  2831. ThreadLock(ppopupmenu->spwndPopupMenu, &tlpwndPopupMenu);
  2832. /*
  2833. * Close all hierarchies from this point down.
  2834. */
  2835. xxxMNCloseHierarchy(ppopupmenu, pMenuState);
  2836. /*
  2837. * Unselect any items on this top level window
  2838. */
  2839. xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
  2840. pMenuState->fMenuStarted = FALSE;
  2841. pwndT = ppopupmenu->spwndNotify;
  2842. ThreadLock(pwndT, &tlpwndT);
  2843. xxxMNReleaseCapture();
  2844. if (fTrackFlagsSet) {
  2845. /*
  2846. * Send a POPUPEND so people watching see them paired
  2847. */
  2848. xxxWindowEvent(EVENT_SYSTEM_MENUPOPUPEND, ppopupmenu->spwndPopupMenu, OBJID_CLIENT, 0, 0);
  2849. xxxDestroyWindow(ppopupmenu->spwndPopupMenu);
  2850. }
  2851. if (pwndT == NULL) {
  2852. ThreadUnlock(&tlpwndT);
  2853. ThreadUnlock(&tlpwndPopupMenu);
  2854. return;
  2855. }
  2856. /*
  2857. * SMS_NOMENU hack so we can send MenuSelect messages with
  2858. * (loword(lparam) = -1) when
  2859. * the menu pops back up for the CBT people. In 3.0, all WM_MENUSELECT
  2860. * messages went through the message filter so go through the function
  2861. * SendMenuSelect. We need to do this in 3.1 since WordDefect for Windows
  2862. * depends on this.
  2863. */
  2864. xxxSendMenuSelect(pwndT, NULL, SMS_NOMENU, MFMWFP_NOITEM);
  2865. xxxWindowEvent(EVENT_SYSTEM_MENUEND, pwndT, (fIsSysMenu ?
  2866. OBJID_SYSMENU : (fIsMenuBar ? OBJID_MENU : OBJID_WINDOW)),
  2867. INDEXID_CONTAINER, 0);
  2868. if (fNotify) {
  2869. /*
  2870. * Notify app we are exiting the menu loop. Mainly for WinOldApp 386.
  2871. * wParam is 1 if a TrackPopupMenu else 0.
  2872. */
  2873. xxxSendMessage(pwndT, WM_EXITMENULOOP,
  2874. ((fTrackFlagsSet && !fIsSysMenu)? 1 : 0), 0);
  2875. }
  2876. if (uMsg != 0) {
  2877. PlayEventSound(USER_SOUND_MENUCOMMAND);
  2878. pMenuState->cmdLast = cmd;
  2879. if (!fSynchronous) {
  2880. if (!fIsSysMenu && fTrackFlagsSet && !TestWF(pwndT, WFWIN31COMPAT)) {
  2881. xxxSendMessage(pwndT, uMsg, cmd, lParam);
  2882. } else {
  2883. _PostMessage(pwndT, uMsg, cmd, lParam);
  2884. }
  2885. }
  2886. } else
  2887. pMenuState->cmdLast = 0;
  2888. ThreadUnlock(&tlpwndT);
  2889. ThreadUnlock(&tlpwndPopupMenu);
  2890. }
  2891. /***************************************************************************\
  2892. * xxxMNButtonDown
  2893. *
  2894. * Handles a mouse down on the menu associated with ppopupmenu at item index
  2895. * posItemHit. posItemHit could be MFMWFP_NOITEM if user hit on a menu where
  2896. * no item exists.
  2897. *
  2898. * History:
  2899. * 05-25-91 Mikehar Ported from Win3.1
  2900. \***************************************************************************/
  2901. VOID xxxMNButtonDown(
  2902. PPOPUPMENU ppopupmenu,
  2903. PMENUSTATE pMenuState,
  2904. UINT posItemHit, BOOL fClick)
  2905. {
  2906. PITEM pItem;
  2907. BOOL fOpenHierarchy;
  2908. /*
  2909. * A different item was hit than is currently selected, so select it
  2910. * and drop its menu if available. Make sure we toggle click state.
  2911. */
  2912. if (ppopupmenu->posSelectedItem != posItemHit) {
  2913. /*
  2914. * We are clicking on a new item, not moving the mouse over to it.
  2915. * So reset cancel toggle state. We don't want button up from
  2916. * this button down to cancel.
  2917. */
  2918. if (fClick) {
  2919. fOpenHierarchy = TRUE;
  2920. ppopupmenu->fToggle = FALSE;
  2921. }
  2922. else
  2923. {
  2924. fOpenHierarchy = (ppopupmenu->fDropNextPopup != 0);
  2925. }
  2926. /*
  2927. * If the item has a popup and isn't disabled, open it. Note that
  2928. * selecting this item will cancel any hierarchies associated with
  2929. * the previously selected item.
  2930. */
  2931. pItem = xxxMNSelectItem(ppopupmenu, pMenuState, posItemHit);
  2932. if (MNIsPopupItem(pItem) && fOpenHierarchy) {
  2933. /* Punt if menu was destroyed. */
  2934. if (xxxMNOpenHierarchy(ppopupmenu, pMenuState) == (PWND)-1) {
  2935. return;
  2936. }
  2937. }
  2938. } else {
  2939. /*
  2940. * We are moving over to the already-selected item. If we are
  2941. * clicking for real, reset cancel toggle state. We want button
  2942. * up to cancel if on same item. Otherwise, do nothing if just
  2943. * moving...
  2944. */
  2945. if (fClick) {
  2946. ppopupmenu->fToggle = TRUE;
  2947. }
  2948. if (!xxxMNHideNextHierarchy(ppopupmenu) && fClick && xxxMNOpenHierarchy(ppopupmenu, pMenuState))
  2949. ppopupmenu->fToggle = FALSE;
  2950. }
  2951. if (fClick) {
  2952. pMenuState->fButtonDown = TRUE;
  2953. xxxMNDoScroll(ppopupmenu, posItemHit, TRUE);
  2954. }
  2955. }
  2956. /***************************************************************************\
  2957. * MNSetTimerToAutoDissmiss
  2958. *
  2959. * History:
  2960. * 11/14/96 GerardoB Created
  2961. \***************************************************************************/
  2962. VOID MNSetTimerToAutoDismiss(
  2963. PMENUSTATE pMenuState,
  2964. PWND pwnd)
  2965. {
  2966. if (pMenuState->fAutoDismiss && !pMenuState->fAboutToAutoDismiss) {
  2967. if (_SetTimer(pwnd, IDSYS_MNAUTODISMISS, 16 * gdtMNDropDown, NULL)) {
  2968. pMenuState->fAboutToAutoDismiss = TRUE;
  2969. } else {
  2970. RIPMSG0(RIP_WARNING, "xxxMNMouseMove: Failed to set autodismiss timer");
  2971. }
  2972. }
  2973. }
  2974. /***************************************************************************\
  2975. * xxxMNMouseMove
  2976. *
  2977. * Handles a mouse move to the given point.
  2978. *
  2979. * History:
  2980. * 05-25-91 Mikehar Ported from Win3.1
  2981. \***************************************************************************/
  2982. VOID xxxMNMouseMove(
  2983. PPOPUPMENU ppopup,
  2984. PMENUSTATE pMenuState,
  2985. POINTS ptScreen)
  2986. {
  2987. LONG_PTR cmdHitArea;
  2988. UINT uFlags;
  2989. UINT cmdItem;
  2990. PWND pwnd;
  2991. TL tlpwndT;
  2992. if (!IsRootPopupMenu(ppopup)) {
  2993. RIPMSG0(RIP_ERROR,
  2994. "MenuMouseMoveHandler() called for a non top most menu");
  2995. return;
  2996. }
  2997. /*
  2998. * Ignore mouse moves that aren't really moves. MSTEST jiggles
  2999. * the mouse for some reason. And windows coming and going will
  3000. * force mouse moves, to reset the cursor.
  3001. */
  3002. if ((ptScreen.x == pMenuState->ptMouseLast.x) && (ptScreen.y == pMenuState->ptMouseLast.y))
  3003. return;
  3004. pMenuState->ptMouseLast.x = ptScreen.x;
  3005. pMenuState->ptMouseLast.y = ptScreen.y;
  3006. /*
  3007. * Find out where this mouse move occurred.
  3008. */
  3009. cmdHitArea = xxxMNFindWindowFromPoint(ppopup, &cmdItem, ptScreen);
  3010. /*
  3011. * If coming from an IDropTarget call out, remember the hit test
  3012. */
  3013. if (pMenuState->fInDoDragDrop) {
  3014. xxxMNUpdateDraggingInfo(pMenuState, cmdHitArea, cmdItem);
  3015. }
  3016. if (pMenuState->mnFocus == KEYBDHOLD) {
  3017. /*
  3018. * Ignore mouse moves when in keyboard mode if the mouse isn't over any
  3019. * menu at all. Also ignore mouse moves if over minimized window,
  3020. * because we pretend that its entire window is like system menu.
  3021. */
  3022. if ((cmdHitArea == MFMWFP_OFFMENU) ||
  3023. ((cmdHitArea == MFMWFP_NOITEM) && TestWF(ppopup->spwndNotify, WFMINIMIZED))) {
  3024. return;
  3025. }
  3026. pMenuState->mnFocus = MOUSEHOLD;
  3027. }
  3028. if (cmdHitArea == MFMWFP_ALTMENU) {
  3029. /*
  3030. * User clicked in the other menu so switch to it ONLY IF
  3031. * MOUSE IS DOWN. Usability testing proves that people frequently
  3032. * get kicked into the system menu accidentally when browsing the
  3033. * menu bar. We support the Win3.1 behavior when the mouse is
  3034. * down however.
  3035. */
  3036. if (pMenuState->fButtonDown) {
  3037. xxxMNSwitchToAlternateMenu(ppopup);
  3038. cmdHitArea = MFMWFP_NOITEM;
  3039. } else
  3040. goto OverNothing;
  3041. }
  3042. if (cmdHitArea == MFMWFP_NOITEM) {
  3043. /*
  3044. * Mouse move occurred to an item in the main menu bar. If the item
  3045. * is different than the one already selected, close up the current
  3046. * one, select the new one and drop its menu. But if the item is the
  3047. * same as the one currently selected, we need to pull up any popups
  3048. * if needed and just keep the current level visible. Hey, this is
  3049. * the same as a mousedown so lets do that instead.
  3050. */
  3051. xxxMNButtonDown(ppopup, pMenuState, cmdItem, FALSE);
  3052. return;
  3053. } else if (cmdHitArea != 0) {
  3054. /* This is a popup window we moved onto. */
  3055. pwnd = (PWND)(cmdHitArea);
  3056. ThreadLock(pwnd, &tlpwndT);
  3057. UserAssert(TestWF(pwnd, WFVISIBLE));
  3058. /*
  3059. * Modeless menus don't capture the mouse, so track it to know
  3060. * when it leaves the popup.
  3061. */
  3062. ppopup = ((PMENUWND)pwnd)->ppopupmenu;
  3063. if (pMenuState->fModelessMenu
  3064. && !pMenuState->fInDoDragDrop
  3065. && !ppopup->fTrackMouseEvent) {
  3066. TRACKMOUSEEVENT tme;
  3067. /* tme.cbSize = sizeof(TRACKMOUSEEVENT); Not checked on kernel side */
  3068. tme.dwFlags = TME_LEAVE;
  3069. tme.hwndTrack = PtoH(pwnd);
  3070. TrackMouseEvent(&tme);
  3071. ppopup->fTrackMouseEvent = TRUE;
  3072. /*
  3073. * We just entered this window so make sure the cursor
  3074. * is properly set.
  3075. */
  3076. xxxSendMessage(pwnd, WM_SETCURSOR, (WPARAM)HWq(pwnd), MAKELONG(MSGF_MENU, 0));
  3077. }
  3078. /*
  3079. * Select the item.
  3080. */
  3081. uFlags = (UINT)xxxSendMessage(pwnd, MN_SELECTITEM, (WPARAM)cmdItem, 0L);
  3082. if ((uFlags & MF_POPUP) && !(uFlags & MFS_GRAYED)) {
  3083. /*
  3084. * User moved back onto an item with a hierarchy. Hide the
  3085. * the dropped popup.
  3086. */
  3087. if (!xxxSendMessage(pwnd, MN_SETTIMERTOOPENHIERARCHY, 0, 0L)) {
  3088. xxxMNHideNextHierarchy(ppopup);
  3089. }
  3090. }
  3091. ThreadUnlock(&tlpwndT);
  3092. } else
  3093. OverNothing:
  3094. {
  3095. /* We moved off all menu windows... */
  3096. if (ppopup->spwndActivePopup != NULL) {
  3097. pwnd = ppopup->spwndActivePopup;
  3098. ThreadLock(pwnd, &tlpwndT);
  3099. xxxSendMessage(pwnd, MN_SELECTITEM, MFMWFP_NOITEM, 0L);
  3100. MNSetTimerToAutoDismiss(pMenuState, pwnd);
  3101. ThreadUnlock(&tlpwndT);
  3102. } else {
  3103. xxxMNSelectItem(ppopup, pMenuState, MFMWFP_NOITEM);
  3104. }
  3105. }
  3106. }
  3107. /***************************************************************************\
  3108. * xxxMNButtonUp
  3109. *
  3110. * Handles a mouse button up at the given point.
  3111. *
  3112. * History:
  3113. * 05-25-91 Mikehar Ported from Win3.1
  3114. \***************************************************************************/
  3115. VOID xxxMNButtonUp(
  3116. PPOPUPMENU ppopup,
  3117. PMENUSTATE pMenuState,
  3118. UINT posItemHit,
  3119. LPARAM lParam)
  3120. {
  3121. PITEM pItem;
  3122. if (!pMenuState->fButtonDown) {
  3123. /*
  3124. * Ignore if button was never down... Really shouldn't happen...
  3125. */
  3126. return;
  3127. }
  3128. if (posItemHit == MFMWFP_NOITEM) {
  3129. RIPMSG0(RIP_WARNING, "button up on no item");
  3130. goto ExitButtonUp;
  3131. }
  3132. if (ppopup->posSelectedItem != posItemHit) {
  3133. goto ExitButtonUp;
  3134. }
  3135. if (ppopup->fIsMenuBar) {
  3136. /*
  3137. * Handle button up in menubar specially.
  3138. */
  3139. if (ppopup->fHierarchyDropped) {
  3140. if (!ppopup->fToggle) {
  3141. goto ExitButtonUp;
  3142. } else {
  3143. /*
  3144. * Cancel menu now.
  3145. */
  3146. ppopup->fToggle = FALSE;
  3147. xxxMNDismiss(pMenuState);
  3148. return;
  3149. }
  3150. }
  3151. } else if (ppopup->fShowTimer) {
  3152. ppopup->fToggle = FALSE;
  3153. /*
  3154. * Open hierarchy on popup
  3155. */
  3156. xxxMNOpenHierarchy(ppopup, pMenuState);
  3157. goto ExitButtonUp;
  3158. }
  3159. /*
  3160. * If nothing is selected, get out. This occurs mainly on unbalanced
  3161. * multicolumn menus where one of the columns isn't completely full.
  3162. */
  3163. if (ppopup->posSelectedItem == MFMWFP_NOITEM)
  3164. goto ExitButtonUp;
  3165. if (ppopup->posSelectedItem >= ppopup->spmenu->cItems)
  3166. goto ExitButtonUp;
  3167. /*
  3168. * Get a pointer to the currently selected item in this menu.
  3169. */
  3170. pItem = &(ppopup->spmenu->rgItems[ppopup->posSelectedItem]);
  3171. /*
  3172. * Kick out of menu mode if user clicked on a non-separator, enabled,
  3173. * non-hierarchical item.
  3174. *
  3175. * BOGUS
  3176. * Why doesn't MFS_GRAYED check work for separators now? Find out later.
  3177. */
  3178. if (!(pItem->fType & MFT_SEPARATOR)
  3179. && !(pItem->fState & MFS_GRAYED)
  3180. && (pItem->spSubMenu == NULL)) {
  3181. xxxMNDismissWithNotify(pMenuState, ppopup->spmenu, pItem,
  3182. ppopup->posSelectedItem, lParam);
  3183. return;
  3184. }
  3185. ExitButtonUp:
  3186. pMenuState->fButtonDown =
  3187. pMenuState->fButtonAlwaysDown = FALSE;
  3188. }
  3189. /***************************************************************************\
  3190. *UINT MenuSetTimerToOpenHierarchy(PPOPUPMENU ppopupmenu)
  3191. * Given the current selection, set a timer to show this hierarchy if
  3192. * valid else return 0. If a timer should be set but couldn't return -1.
  3193. *
  3194. * History:
  3195. * 05-25-91 Mikehar Ported from Win3.1
  3196. \***************************************************************************/
  3197. UINT MNSetTimerToOpenHierarchy(
  3198. PPOPUPMENU ppopup)
  3199. {
  3200. PITEM pItem;
  3201. /*
  3202. * No selection so fail
  3203. */
  3204. if (ppopup->posSelectedItem == MFMWFP_NOITEM) {
  3205. return 0;
  3206. }
  3207. if (ppopup->posSelectedItem >= ppopup->spmenu->cItems) {
  3208. return 0;
  3209. }
  3210. /*
  3211. * Is item an enabled popup?
  3212. * Get a pointer to the currently selected item in this menu.
  3213. */
  3214. pItem = ppopup->spmenu->rgItems + ppopup->posSelectedItem;
  3215. if ((pItem->spSubMenu == NULL) || (pItem->fState & MFS_GRAYED)) {
  3216. return 0;
  3217. }
  3218. if (ppopup->fShowTimer
  3219. || (ppopup->fHierarchyDropped
  3220. && (ppopup->posSelectedItem == ppopup->posDropped))) {
  3221. /*
  3222. * A timer is already set or the hierarchy is already opened.
  3223. */
  3224. return 1;
  3225. }
  3226. if (!_SetTimer(ppopup->spwndPopupMenu, IDSYS_MNSHOW, gdtMNDropDown, NULL)) {
  3227. return (UINT)-1;
  3228. }
  3229. ppopup->fShowTimer = TRUE;
  3230. return 1;
  3231. }
  3232. /***************************************************************************\
  3233. * MNSetTimerToCloseHierarchy
  3234. *
  3235. \***************************************************************************/
  3236. UINT MNSetTimerToCloseHierarchy(
  3237. PPOPUPMENU ppopup)
  3238. {
  3239. if (!ppopup->fHierarchyDropped) {
  3240. return 0;
  3241. }
  3242. if (ppopup->fHideTimer) {
  3243. return 1;
  3244. }
  3245. if (!_SetTimer(ppopup->spwndPopupMenu, IDSYS_MNHIDE, gdtMNDropDown, NULL)) {
  3246. return (UINT)-1;
  3247. }
  3248. ppopup->fHideTimer = TRUE;
  3249. ppopup = ((PMENUWND)(ppopup->spwndNextPopup))->ppopupmenu;
  3250. ppopup->fAboutToHide = TRUE;
  3251. return 1;
  3252. }
  3253. /***************************************************************************\
  3254. * xxxCallHandleMenuMessages
  3255. *
  3256. * Modeless menus don't have a modal loop so we don't see the messages until
  3257. * they are dispatched to xxxMenuWindowProc. So we call this function to
  3258. * process the message just like we would in the modal case, only that
  3259. * the message has already been pulled out of the queue.
  3260. *
  3261. * This is also calledfrom xxxScanSysQueue to pass mouse messages on the menu
  3262. * bar or from xxxMNDragOver to upadate the mouse position when being draged over.
  3263. *
  3264. * History:
  3265. * 10/25/96 GerardoB Created
  3266. \***************************************************************************/
  3267. BOOL xxxCallHandleMenuMessages(
  3268. PMENUSTATE pMenuState,
  3269. PWND pwnd,
  3270. UINT message,
  3271. WPARAM wParam,
  3272. LPARAM lParam)
  3273. {
  3274. BOOL fHandled;
  3275. MSG msg;
  3276. CheckLock(pwnd);
  3277. UserAssert(pMenuState->fModelessMenu || pMenuState->fInDoDragDrop);
  3278. /*
  3279. * Since modeless menus don't capture the mouse, then we need to
  3280. * keep checking on the mouse button when the mouse is off the
  3281. * menu.
  3282. * Note that we do not set fMouseOffMenu if fInDoDragDrop is set
  3283. */
  3284. if (pMenuState->fMouseOffMenu && pMenuState->fButtonDown) {
  3285. UserAssert(!pMenuState->fInDoDragDrop && pMenuState->fModelessMenu);
  3286. MNCheckButtonDownState(pMenuState);
  3287. }
  3288. /*
  3289. * Setup the msg structure
  3290. */
  3291. msg.hwnd = HW(pwnd);
  3292. msg.message = message;
  3293. msg.wParam = wParam;
  3294. /*
  3295. * xxxHandleMenuMessages expects screen coordinates
  3296. */
  3297. if ((message >= WM_MOUSEFIRST) && (message <= WM_MOUSELAST)) {
  3298. msg.lParam = MAKELONG(GET_X_LPARAM(lParam) + pwnd->rcClient.left,
  3299. GET_Y_LPARAM(lParam) + pwnd->rcClient.top);
  3300. } else {
  3301. msg.lParam = lParam;
  3302. }
  3303. /*
  3304. * Not used by xxxHandleMenuMessages
  3305. */
  3306. msg.time = 0;
  3307. msg.pt.x = msg.pt.x = 0;
  3308. UserAssert(pMenuState->pGlobalPopupMenu != NULL);
  3309. pMenuState->fInCallHandleMenuMessages = TRUE;
  3310. fHandled = xxxHandleMenuMessages(&msg, pMenuState, pMenuState->pGlobalPopupMenu);
  3311. pMenuState->fInCallHandleMenuMessages = FALSE;
  3312. /*
  3313. * If the message was handled and this is a modeless menu,
  3314. * check to see if it's time to go.
  3315. */
  3316. if (fHandled
  3317. && pMenuState->fModelessMenu
  3318. && ExitMenuLoop (pMenuState, pMenuState->pGlobalPopupMenu)) {
  3319. xxxEndMenuLoop (pMenuState, pMenuState->pGlobalPopupMenu);
  3320. xxxMNEndMenuState(TRUE);
  3321. }
  3322. return fHandled;
  3323. }
  3324. /***************************************************************************\
  3325. *
  3326. * History:
  3327. * 05-25-91 Mikehar Ported from Win3.1
  3328. * 08-12-96 jparsons Catch NULL lParam on WM_CREATE [51986]
  3329. \***************************************************************************/
  3330. LRESULT xxxMenuWindowProc(
  3331. PWND pwnd,
  3332. UINT message,
  3333. WPARAM wParam,
  3334. LPARAM lParam)
  3335. {
  3336. BOOL fIsRecursedMenu;
  3337. LRESULT lRet;
  3338. PAINTSTRUCT ps;
  3339. PPOPUPMENU ppopupmenu;
  3340. PMENUSTATE pMenuState;
  3341. PMENU pmenu;
  3342. PITEM pItem;
  3343. TL tlpmenu;
  3344. TL tlpwndNotify;
  3345. PDESKTOP pdesk = pwnd->head.rpdesk;
  3346. POINT ptOrg;
  3347. HDC hdcAni;
  3348. CheckLock(pwnd);
  3349. VALIDATECLASSANDSIZE(pwnd, message, wParam, lParam, FNID_MENU, WM_NCCREATE);
  3350. /*
  3351. * If we're not in menu mode or this window is just being created,
  3352. * there are only few messages we care about.
  3353. */
  3354. pMenuState = GetpMenuState(pwnd);
  3355. ppopupmenu = ((PMENUWND)pwnd)->ppopupmenu;
  3356. pmenu = (ppopupmenu != NULL ? ppopupmenu->spmenu : NULL);
  3357. if ((pMenuState == NULL) || (pmenu == NULL)) {
  3358. switch (message) {
  3359. case WM_NCCREATE:
  3360. case WM_FINALDESTROY:
  3361. break;
  3362. case MN_SETHMENU:
  3363. if (ppopupmenu != NULL) {
  3364. break;
  3365. } else {
  3366. return 0;
  3367. }
  3368. default:
  3369. goto CallDWP;
  3370. }
  3371. } else {
  3372. /*
  3373. * TPM_RECURSE support: make sure we grab the proper pMenuState.
  3374. */
  3375. fIsRecursedMenu = ((ppopupmenu->ppopupmenuRoot != NULL)
  3376. && IsRecursedMenuState(pMenuState, ppopupmenu));
  3377. if (fIsRecursedMenu) {
  3378. while (IsRecursedMenuState(pMenuState, ppopupmenu)
  3379. && (pMenuState->pmnsPrev != NULL)) {
  3380. pMenuState = pMenuState->pmnsPrev;
  3381. }
  3382. UserAssert(pMenuState->pGlobalPopupMenu == ppopupmenu->ppopupmenuRoot);
  3383. }
  3384. Validateppopupmenu(ppopupmenu);
  3385. /*
  3386. * If this is a modeless menu, give xxxHandleMenuMessages the first
  3387. * shot at the message
  3388. */
  3389. if (pMenuState->fModelessMenu && !pMenuState->fInCallHandleMenuMessages) {
  3390. /*
  3391. * If this is a recursed menu, we don't want to process any
  3392. * input for it until the current menu goes away.
  3393. */
  3394. if (fIsRecursedMenu) {
  3395. if (((message >= WM_MOUSEFIRST) && (message <= WM_MOUSELAST))
  3396. || ((message >= WM_KEYFIRST) && (message <= WM_KEYLAST))
  3397. || ((message >= WM_NCMOUSEFIRST) && (message <= WM_NCMOUSELAST))) {
  3398. goto CallDWP;
  3399. }
  3400. } else {
  3401. if (xxxCallHandleMenuMessages(pMenuState, pwnd, message, wParam, lParam)) {
  3402. return 0;
  3403. }
  3404. }
  3405. }
  3406. }
  3407. switch (message) {
  3408. case WM_NCCREATE:
  3409. /*
  3410. * Ignore evil messages to prevent leaks.
  3411. * Use RIP_ERROR for a while to make sure to see if we're getting here
  3412. */
  3413. if (((PMENUWND)pwnd)->ppopupmenu != NULL) {
  3414. RIPMSG1(RIP_ERROR, "xxxMenuWindowProc: evil WM_NCCREATE. already initialized. pwnd:%p", pwnd);
  3415. return FALSE;
  3416. }
  3417. ppopupmenu = MNAllocPopup(TRUE);
  3418. if (ppopupmenu == NULL) {
  3419. return FALSE;
  3420. }
  3421. ((PMENUWND)pwnd)->ppopupmenu = ppopupmenu;
  3422. ppopupmenu->posSelectedItem = MFMWFP_NOITEM;
  3423. Lock(&(ppopupmenu->spwndPopupMenu), pwnd);
  3424. return TRUE;
  3425. case WM_NCCALCSIZE:
  3426. xxxDefWindowProc(pwnd, message, wParam, lParam);
  3427. if (pmenu->dwArrowsOn != MSA_OFF) {
  3428. InflateRect((PRECT)lParam, 0, -gcyMenuScrollArrow);
  3429. }
  3430. break;
  3431. case WM_ERASEBKGND:
  3432. if (pmenu->hbrBack != NULL) {
  3433. MNEraseBackground ((HDC) wParam, pmenu,
  3434. 0, 0,
  3435. pwnd->rcClient.right - pwnd->rcClient.left,
  3436. pwnd->rcClient.bottom - pwnd->rcClient.top);
  3437. return TRUE;
  3438. } else {
  3439. goto CallDWP;
  3440. }
  3441. break;
  3442. case WM_PRINT:
  3443. /*
  3444. * default processing of WM_PRINT does not handle custom non-
  3445. * client painting -- which scrollable menus have -- so take
  3446. * care of drawing nonclient area and then let DefWindowProc
  3447. * handle the rest
  3448. */
  3449. if ((lParam & PRF_NONCLIENT) && (pmenu->dwArrowsOn != MSA_OFF)) {
  3450. BOOL bMirrorThisDC = (wParam && TestWF(pwnd, WEFLAYOUTRTL) && !MIRRORED_HDC((HDC)wParam));
  3451. DWORD dwOldLayout;
  3452. if (bMirrorThisDC) {
  3453. dwOldLayout = GreSetLayout((HDC)wParam , pwnd->rcWindow.right - pwnd->rcWindow.left, LAYOUT_RTL);
  3454. }
  3455. MNDrawFullNC(pwnd, (HDC)wParam, ppopupmenu);
  3456. if (bMirrorThisDC) {
  3457. GreSetLayout((HDC)wParam , pwnd->rcWindow.right - pwnd->rcWindow.left, dwOldLayout);
  3458. }
  3459. GreGetWindowOrg((HDC)wParam, &ptOrg);
  3460. GreSetWindowOrg((HDC)wParam,
  3461. ptOrg.x - MNXBORDER,
  3462. ptOrg.y - MNYBORDER - gcyMenuScrollArrow,
  3463. NULL);
  3464. xxxDefWindowProc(pwnd, message, wParam, lParam & ~PRF_NONCLIENT);
  3465. GreSetWindowOrg((HDC)wParam, ptOrg.x, ptOrg.y, NULL);
  3466. } else {
  3467. if (MNIsFlatMenu()) {
  3468. /*
  3469. * Need to have DWP draw first so that WM_PRINTCLIENT gets sent
  3470. * to fill in the inside. After this is done, come back and
  3471. * redraw over the frame with the correct menu edge.
  3472. */
  3473. lRet = xxxDefWindowProc(pwnd, message, wParam, lParam);
  3474. MNDrawEdge(pmenu, (HDC)wParam, &pwnd->rcWindow, 0);
  3475. return lRet;
  3476. } else {
  3477. goto CallDWP;
  3478. }
  3479. }
  3480. break;
  3481. case WM_WINDOWPOSCHANGING:
  3482. if (!(((LPWINDOWPOS)lParam)->flags & SWP_SHOWWINDOW))
  3483. goto CallDWP;
  3484. if (!TestEffectUP(MENUANIMATION) || !(ppopupmenu->iDropDir & PAS_OUT)
  3485. || (glinp.dwFlags & (LINP_KEYBOARD | LINP_JOURNALLING))
  3486. || (GetAppCompatFlags2(VER40) & GACF2_ANIMATIONOFF)) {
  3487. NoAnimation:
  3488. ppopupmenu->iDropDir &= ~PAS_OUT;
  3489. goto CallDWP;
  3490. }
  3491. /*
  3492. * Create the animation bitmap.
  3493. */
  3494. pMenuState->cxAni = pwnd->rcWindow.right - pwnd->rcWindow.left;
  3495. pMenuState->cyAni = pwnd->rcWindow.bottom - pwnd->rcWindow.top;
  3496. if (TestALPHA(MENUFADE)) {
  3497. if ((hdcAni = CreateFade(pwnd, NULL, CMS_MENUFADE,
  3498. FADE_SHOW | FADE_MENU)) == NULL) {
  3499. goto NoAnimation;
  3500. }
  3501. } else {
  3502. if (!MNCreateAnimationBitmap(pMenuState, pMenuState->cxAni,
  3503. pMenuState->cyAni)) {
  3504. goto NoAnimation;
  3505. }
  3506. /*
  3507. * We shouldn't be animating at this time.
  3508. */
  3509. UserAssert(pMenuState->hdcWndAni == NULL);
  3510. /*
  3511. * This window must be the active popup
  3512. */
  3513. UserAssert(pMenuState->pGlobalPopupMenu->spwndActivePopup == pwnd);
  3514. /*
  3515. * Initialize animation info
  3516. */
  3517. pMenuState->hdcWndAni = _GetDCEx(pwnd, HRGN_FULL, DCX_WINDOW | DCX_USESTYLE | DCX_INTERSECTRGN);
  3518. pMenuState->iAniDropDir = ppopupmenu->iDropDir;
  3519. pMenuState->ixAni = (pMenuState->iAniDropDir & PAS_HORZ) ? 0 : pMenuState->cxAni;
  3520. pMenuState->iyAni = (pMenuState->iAniDropDir & PAS_VERT) ? 0 : pMenuState->cyAni;
  3521. hdcAni = pMenuState->hdcAni;
  3522. }
  3523. /*
  3524. * MFWINDOWDC is used by MNEraseBackground to determine where the
  3525. * brush org should be set.
  3526. */
  3527. SetMF(pmenu, MFWINDOWDC);
  3528. xxxSendMessage(pwnd, WM_PRINT, (WPARAM)hdcAni, PRF_CLIENT | PRF_NONCLIENT | PRF_ERASEBKGND);
  3529. ClearMF(pmenu, MFWINDOWDC);
  3530. /*
  3531. * While the window is still hidden, load the first fade animation
  3532. * frame to avoid flicker when the window is actually shown.
  3533. *
  3534. * There would still be flicker with slide animations, though. It
  3535. * could be fixed by using the window region, similar to
  3536. * AnimateWindow. For now, too many functions would become xxx, so
  3537. * let's not do it, unless it becomes a big issue.
  3538. */
  3539. if (TestFadeFlags(FADE_MENU)) {
  3540. ShowFade();
  3541. }
  3542. goto CallDWP;
  3543. case WM_WINDOWPOSCHANGED:
  3544. if (!(((LPWINDOWPOS)lParam)->flags & SWP_SHOWWINDOW))
  3545. goto CallDWP;
  3546. /*
  3547. * If not animating, nothing else to do here.
  3548. */
  3549. if (!(ppopupmenu->iDropDir & PAS_OUT))
  3550. goto CallDWP;
  3551. /*
  3552. * Start the animation cycle now.
  3553. */
  3554. if (TestFadeFlags(FADE_MENU)) {
  3555. StartFade();
  3556. } else {
  3557. pMenuState->dwAniStartTime = NtGetTickCount();
  3558. _SetTimer(pwnd, IDSYS_MNANIMATE, 1, NULL);
  3559. }
  3560. ppopupmenu->iDropDir &= ~PAS_OUT;
  3561. goto CallDWP;
  3562. case WM_NCPAINT:
  3563. if (ppopupmenu->iDropDir & PAS_OUT) {
  3564. /*
  3565. * When animating, validate itself to ensure no further drawing
  3566. * that is not related to the animation.
  3567. */
  3568. xxxValidateRect(pwnd, NULL);
  3569. } else {
  3570. /*
  3571. * If we have scroll bars, draw them
  3572. */
  3573. if (pmenu->dwArrowsOn != MSA_OFF) {
  3574. HDC hdc = _GetDCEx(pwnd, (HRGN)wParam,
  3575. DCX_USESTYLE | DCX_WINDOW | DCX_INTERSECTRGN | DCX_NODELETERGN | DCX_LOCKWINDOWUPDATE);
  3576. MNDrawFullNC(pwnd, hdc, ppopupmenu);
  3577. _ReleaseDC(hdc);
  3578. } else {
  3579. if (MNIsFlatMenu()) {
  3580. HDC hdc;
  3581. hdc = _GetDCEx(pwnd, (HRGN)wParam,
  3582. DCX_USESTYLE | DCX_WINDOW | DCX_INTERSECTRGN | DCX_NODELETERGN | DCX_LOCKWINDOWUPDATE);
  3583. MNDrawEdge(pmenu, hdc, &pwnd->rcWindow, 0);
  3584. _ReleaseDC(hdc);
  3585. } else {
  3586. goto CallDWP;
  3587. }
  3588. }
  3589. }
  3590. break;
  3591. case WM_PRINTCLIENT:
  3592. ThreadLock(pmenu, &tlpmenu);
  3593. xxxMenuDraw((HDC)wParam, pmenu);
  3594. ThreadUnlock(&tlpmenu);
  3595. break;
  3596. case WM_FINALDESTROY:
  3597. /*
  3598. * If we're animating, we must haved been killed in a rude way....
  3599. */
  3600. UserAssert((pMenuState == NULL) || (pMenuState->hdcWndAni == NULL));
  3601. /*
  3602. * If this is a drag and drop menu, then call RevokeDragDrop.
  3603. */
  3604. if ((pMenuState != NULL) && pMenuState->fDragAndDrop) {
  3605. if (!SUCCEEDED(xxxClientRevokeDragDrop(HW(pwnd)))) {
  3606. RIPMSG1(RIP_ERROR, "xxxMenuWindowProc: xxxClientRevokeRegisterDragDrop failed:%#p", pwnd);
  3607. }
  3608. }
  3609. xxxMNDestroyHandler(ppopupmenu);
  3610. return 0;
  3611. case WM_PAINT:
  3612. ThreadLock(pmenu, &tlpmenu);
  3613. xxxBeginPaint(pwnd, &ps);
  3614. xxxMenuDraw(ps.hdc, pmenu);
  3615. xxxEndPaint(pwnd, &ps);
  3616. ThreadUnlock(&tlpmenu);
  3617. break;
  3618. case WM_CHAR:
  3619. case WM_SYSCHAR:
  3620. xxxMNChar(ppopupmenu, pMenuState, (UINT)wParam);
  3621. break;
  3622. case WM_KEYDOWN:
  3623. case WM_SYSKEYDOWN:
  3624. xxxMNKeyDown(ppopupmenu, pMenuState, (UINT)wParam);
  3625. break;
  3626. case WM_TIMER:
  3627. switch (wParam) {
  3628. case IDSYS_MNSHOW:
  3629. /*
  3630. * Open the window and kill the show timer.
  3631. *
  3632. * Cancel any toggle state we might have. We don't
  3633. * want to dismiss this on button up if shown from
  3634. * button down.
  3635. */
  3636. ppopupmenu->fToggle = FALSE;
  3637. xxxMNOpenHierarchy(ppopupmenu, pMenuState);
  3638. break;
  3639. case IDSYS_MNHIDE:
  3640. ppopupmenu->fToggle = FALSE;
  3641. xxxMNCloseHierarchy(ppopupmenu,pMenuState);
  3642. break;
  3643. case IDSYS_MNUP:
  3644. case IDSYS_MNDOWN:
  3645. if (pMenuState->fButtonDown) {
  3646. xxxMNDoScroll(ppopupmenu, (UINT)wParam, FALSE);
  3647. } else {
  3648. _KillTimer(pwnd, (UINT)wParam);
  3649. }
  3650. break;
  3651. case IDSYS_MNANIMATE:
  3652. if (pMenuState->hdcWndAni != NULL) {
  3653. MNAnimate(pMenuState, TRUE);
  3654. } else {
  3655. /*
  3656. * This timer shouldn't be set. Left over in msg queue?
  3657. */
  3658. UserAssert(pMenuState->hdcWndAni != NULL);
  3659. }
  3660. break;
  3661. case IDSYS_MNAUTODISMISS:
  3662. /*
  3663. * This is a one shot timer, so kill it.
  3664. * Dismiss the popup if the flag hasn't been reset.
  3665. */
  3666. _KillTimer(pwnd, IDSYS_MNAUTODISMISS);
  3667. if (pMenuState->fAboutToAutoDismiss) {
  3668. goto EndMenu;
  3669. }
  3670. }
  3671. break;
  3672. /*
  3673. * Menu messages.
  3674. */
  3675. case MN_SETHMENU:
  3676. /*
  3677. * wParam - new hmenu to associate with this menu window
  3678. * Don't let them set the spmenu to NULL of we have to deal with
  3679. * that all over. Use RIP_ERROR for a while to make sure this is OK
  3680. */
  3681. if (wParam != 0) {
  3682. if ((wParam = (WPARAM)ValidateHmenu((HMENU)wParam)) == 0) {
  3683. break;
  3684. }
  3685. LockPopupMenu(ppopupmenu, &(ppopupmenu->spmenu), (PMENU)wParam);
  3686. } else {
  3687. RIPMSG1(RIP_ERROR, "xxxMenuWindowProc: MN_SETHMENU ignoring NULL wParam. pwnd:%p", pwnd);
  3688. }
  3689. break;
  3690. case MN_GETHMENU:
  3691. /*
  3692. * returns the hmenu associated with this menu window
  3693. */
  3694. return (LRESULT)PtoH(pmenu);
  3695. case MN_SIZEWINDOW:
  3696. {
  3697. /*
  3698. * Computes the size of the menu associated with this window and resizes
  3699. * it if needed. Size is returned x in loword, y in highword. wParam
  3700. * is 0 to just return new size. wParam is non zero if we should also resize
  3701. * window.
  3702. * When called by xxxMNUpdateShownMenu, we might need to redraw the
  3703. * frame (i.e, the scrollbars). So we check for MNSW_DRAWFRAME in wParam.
  3704. * If some app is sending this message and that bit is set, then we'll
  3705. * do some extra work, but I think everything should be cool.
  3706. */
  3707. int cx, cy;
  3708. PMONITOR pMonitor;
  3709. /*
  3710. * Call menucomputeHelper directly since this is the entry point for
  3711. * non toplevel menu bars.
  3712. */
  3713. if (pmenu == NULL)
  3714. break;
  3715. ThreadLockAlways(pmenu, &tlpmenu);
  3716. ThreadLock(ppopupmenu->spwndNotify, &tlpwndNotify);
  3717. UserAssert(pmenu->spwndNotify == ppopupmenu->spwndNotify);
  3718. xxxMNCompute(pmenu, ppopupmenu->spwndNotify, 0, 0, 0, 0);
  3719. ThreadUnlock(&tlpwndNotify);
  3720. ThreadUnlock(&tlpmenu);
  3721. pMonitor = _MonitorFromWindow(pwnd, MONITOR_DEFAULTTOPRIMARY);
  3722. cx = pmenu->cxMenu;
  3723. cy = MNCheckScroll(pmenu, pMonitor);
  3724. /*
  3725. * Size the window?
  3726. */
  3727. if (wParam != 0) {
  3728. LONG lPos;
  3729. int x, y;
  3730. DWORD dwFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER;
  3731. /*
  3732. * Need to redraw the frame?
  3733. */
  3734. if (wParam & MNSW_DRAWFRAME) {
  3735. dwFlags |= SWP_DRAWFRAME;
  3736. }
  3737. /*
  3738. * If the window is visible, it's being resized while
  3739. * shown. So make sure that it still fits on the screen
  3740. * (i.e, move it to the best pos).
  3741. */
  3742. if (TestWF(pwnd, WFVISIBLE)) {
  3743. lPos = FindBestPos(
  3744. pwnd->rcWindow.left,
  3745. pwnd->rcWindow.top,
  3746. cx,
  3747. cy,
  3748. NULL,
  3749. 0,
  3750. ppopupmenu,
  3751. pMonitor);
  3752. x = GET_X_LPARAM(lPos);
  3753. y = GET_Y_LPARAM(lPos);
  3754. } else {
  3755. dwFlags |= SWP_NOMOVE;
  3756. }
  3757. xxxSetWindowPos(
  3758. pwnd,
  3759. PWND_TOP,
  3760. x,
  3761. y,
  3762. cx + 2 * SYSMET(CXFIXEDFRAME), /* For shadow */
  3763. cy + 2 * SYSMET(CYFIXEDFRAME), /* For shadow */
  3764. dwFlags);
  3765. }
  3766. return MAKELONG(cx, cy);
  3767. }
  3768. case MN_OPENHIERARCHY:
  3769. {
  3770. PWND pwndT;
  3771. /*
  3772. * Opens one level of the hierarchy at the selected item, if
  3773. * present. Return 0 if error, else hwnd of opened hierarchy.
  3774. */
  3775. pwndT = xxxMNOpenHierarchy(ppopupmenu, pMenuState);
  3776. return (LRESULT)HW(pwndT);
  3777. }
  3778. case MN_CLOSEHIERARCHY:
  3779. xxxMNCloseHierarchy(ppopupmenu, pMenuState);
  3780. break;
  3781. case MN_SELECTITEM:
  3782. /*
  3783. * wParam - the item to select. Must be a valid
  3784. * Returns the item flags of the wParam (0 if failure)
  3785. */
  3786. if ((wParam >= pmenu->cItems) && (wParam < MFMWFP_MINVALID)) {
  3787. UserAssertMsg1(FALSE, "Bad wParam %x for MN_SELECTITEM", wParam);
  3788. break;
  3789. }
  3790. pItem = xxxMNSelectItem(ppopupmenu, pMenuState, (UINT)wParam);
  3791. if (pItem != NULL) {
  3792. return((LONG)(DWORD)(WORD)(pItem->fState |
  3793. ((pItem->spSubMenu != NULL) ? MF_POPUP : 0)));
  3794. }
  3795. break;
  3796. case MN_SELECTFIRSTVALIDITEM:
  3797. {
  3798. UINT item;
  3799. item = MNFindNextValidItem(pmenu, -1, 1, TRUE);
  3800. xxxSendMessage(pwnd, MN_SELECTITEM, item, 0L);
  3801. return (LONG)item;
  3802. }
  3803. case MN_CANCELMENUS:
  3804. /*
  3805. * Cancels all menus, unselects everything, destroys windows, and cleans
  3806. * everything up for this hierarchy. wParam is the command to send and
  3807. * lParam says if it is valid or not.
  3808. */
  3809. xxxMNCancel(pMenuState, (UINT)wParam, (BOOL)LOWORD(lParam), 0);
  3810. break;
  3811. case MN_FINDMENUWINDOWFROMPOINT:
  3812. /*
  3813. * lParam is point to search for from this hierarchy down.
  3814. * returns MFMWFP_* value or a pwnd.
  3815. */
  3816. lRet = xxxMNFindWindowFromPoint(ppopupmenu, (PUINT)wParam, MAKEPOINTS(lParam));
  3817. /*
  3818. * Convert return value to a handle.
  3819. */
  3820. if (IsMFMWFPWindow(lRet)) {
  3821. return (LRESULT)HW((PWND)lRet);
  3822. } else {
  3823. return lRet;
  3824. }
  3825. case MN_SHOWPOPUPWINDOW:
  3826. /*
  3827. * Forces the dropped down popup to be visible and if modeless, also active.
  3828. */
  3829. PlayEventSound(USER_SOUND_MENUPOPUP);
  3830. xxxShowWindow(pwnd, (pMenuState->fModelessMenu ? SW_SHOW : SW_SHOWNOACTIVATE));
  3831. break;
  3832. case MN_ACTIVATEPOPUP:
  3833. /*
  3834. * Activates a popup. This messages is posted in response to WM_ACTIVATEAPP
  3835. * or WM_ACTIVATE
  3836. */
  3837. UserAssert(pMenuState->fModelessMenu);
  3838. xxxActivateThisWindow(pwnd, 0, 0);
  3839. break;
  3840. case MN_ENDMENU:
  3841. /*
  3842. * End the menu. This message is posted to avoid ending the menu
  3843. * at randmom moments. By posting the message, the request is
  3844. * queued after any pending/current processing.
  3845. */
  3846. EndMenu:
  3847. xxxEndMenuLoop(pMenuState, pMenuState->pGlobalPopupMenu);
  3848. if (pMenuState->fModelessMenu) {
  3849. UserAssert(!pMenuState->fInCallHandleMenuMessages);
  3850. xxxMNEndMenuState(TRUE);
  3851. }
  3852. return 0;
  3853. case MN_DODRAGDROP:
  3854. /*
  3855. * Let the app know that the user is dragging.
  3856. */
  3857. if (pMenuState->fDragging
  3858. && (ppopupmenu->spwndNotify != NULL)
  3859. && IsMFMWFPWindow(pMenuState->uButtonDownHitArea)) {
  3860. /*
  3861. * Get the pmenu that contains the item being dragged
  3862. */
  3863. pmenu = (((PMENUWND)pMenuState->uButtonDownHitArea)->ppopupmenu)->spmenu;
  3864. /*
  3865. * If this is a modal menu, release the capture lock so
  3866. * DoDragDrop (if called) can get it.
  3867. */
  3868. if (!pMenuState->fModelessMenu) {
  3869. UserAssert(PtiCurrent()->pq->QF_flags & QF_CAPTURELOCKED);
  3870. PtiCurrent()->pq->QF_flags &= ~QF_CAPTURELOCKED;
  3871. }
  3872. LockMenuState(pMenuState);
  3873. ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndNotify);
  3874. /*
  3875. * Give them a chance to call DoDragDrop
  3876. */
  3877. pMenuState->fInDoDragDrop = TRUE;
  3878. lRet = xxxSendMessage(ppopupmenu->spwndNotify, WM_MENUDRAG,
  3879. pMenuState->uButtonDownIndex, (LPARAM)PtoH(pmenu));
  3880. pMenuState->fInDoDragDrop = FALSE;
  3881. if (lRet == MND_ENDMENU) {
  3882. /*
  3883. * Go away.
  3884. */
  3885. ThreadUnlock(&tlpwndNotify);
  3886. if (!xxxUnlockMenuState(pMenuState)) {
  3887. goto EndMenu;
  3888. } else {
  3889. return 0;
  3890. }
  3891. break;
  3892. } else {
  3893. /*
  3894. * If the user starts dragging, we always
  3895. * ignore the following button up
  3896. */
  3897. pMenuState->fIgnoreButtonUp = TRUE;
  3898. }
  3899. /*
  3900. * Check the button state since we might have not seen the button up
  3901. * If so, this will cancel the dragging state
  3902. */
  3903. MNCheckButtonDownState(pMenuState);
  3904. /*
  3905. * If this is a modal menu, make sure we recover capture
  3906. */
  3907. if (!pMenuState->fModelessMenu) {
  3908. xxxMNSetCapture(ppopupmenu);
  3909. }
  3910. ThreadUnlock(&tlpwndNotify);
  3911. xxxUnlockMenuState(pMenuState);
  3912. }
  3913. return 0;
  3914. case MN_BUTTONDOWN:
  3915. /*
  3916. * wParam is position (index) of item the button was clicked on.
  3917. * Must be a valid
  3918. */
  3919. if ((wParam >= pmenu->cItems) && (wParam < MFMWFP_MINVALID)) {
  3920. UserAssertMsg1(FALSE, "Bad wParam %x for MN_BUTTONDOWN", wParam);
  3921. break;
  3922. }
  3923. xxxMNButtonDown(ppopupmenu, pMenuState, (UINT)wParam, TRUE);
  3924. break;
  3925. case MN_MOUSEMOVE:
  3926. /*
  3927. * lParam is mouse move coordinate wrt screen.
  3928. */
  3929. xxxMNMouseMove(ppopupmenu, pMenuState, MAKEPOINTS(lParam));
  3930. break;
  3931. case MN_BUTTONUP:
  3932. /*
  3933. * wParam is position (index) of item the button was up clicked on.
  3934. */
  3935. if ((wParam >= pmenu->cItems) && (wParam < MFMWFP_MINVALID)) {
  3936. UserAssertMsg1(FALSE, "Bad wParam %x for MN_BUTTONUP", wParam);
  3937. break;
  3938. }
  3939. xxxMNButtonUp(ppopupmenu, pMenuState, (UINT)wParam, lParam);
  3940. break;
  3941. case MN_SETTIMERTOOPENHIERARCHY:
  3942. /*
  3943. * Given the current selection, set a timer to show this hierarchy if
  3944. * valid else return 0.
  3945. */
  3946. return (LONG)(WORD)MNSetTimerToOpenHierarchy(ppopupmenu);
  3947. case MN_DBLCLK:
  3948. //
  3949. // User double-clicked on item. wParamLo is the item.
  3950. //
  3951. xxxMNDoubleClick(pMenuState, ppopupmenu, (int)wParam);
  3952. break;
  3953. case WM_MOUSELEAVE:
  3954. UserAssert(pMenuState->fModelessMenu);
  3955. /*
  3956. * If we're in DoDragDrop loop, we don't track the mouse
  3957. * when it goes off the menu window
  3958. */
  3959. pMenuState->fMouseOffMenu = !pMenuState->fInDoDragDrop;
  3960. ppopupmenu->fTrackMouseEvent = FALSE;
  3961. /*
  3962. * See if we need to set the timer to autodismiss
  3963. */
  3964. MNSetTimerToAutoDismiss(pMenuState, pwnd);
  3965. /*
  3966. * If we left the active popup, remove the selection
  3967. */
  3968. if (ppopupmenu->spwndPopupMenu == pMenuState->pGlobalPopupMenu->spwndActivePopup) {
  3969. xxxMNSelectItem(ppopupmenu, pMenuState, MFMWFP_NOITEM);
  3970. }
  3971. break;
  3972. case WM_ACTIVATEAPP:
  3973. if (pMenuState->fModelessMenu
  3974. && (pwnd == pMenuState->pGlobalPopupMenu->spwndActivePopup)) {
  3975. /*
  3976. * If the application is getting activated, we post a message
  3977. * to let the dust settle and then re-activate spwndPopupActive
  3978. */
  3979. if (wParam) {
  3980. _PostMessage(pwnd, MN_ACTIVATEPOPUP, 0, 0);
  3981. /*
  3982. * If we're not in the foregruond queue, we want to keep
  3983. * the frame off.
  3984. * This flag will also tell us that if we lose activation
  3985. * while coming to the foregrund (later), we don't want
  3986. * to dismiss the menu.
  3987. */
  3988. pMenuState->fActiveNoForeground = (gpqForeground != PtiCurrent()->pq);
  3989. }
  3990. /*
  3991. * Make the notification window frame show that we're active/inactive.
  3992. * If the application is inactive but the user moves the mouse
  3993. * over the menu, then we can get this message when the first
  3994. * window in the app gets activated (i.e., the move causes a popup to
  3995. * be closed/opened). So turn on the frame only if we're in
  3996. * the foreground.
  3997. */
  3998. if (ppopupmenu->spwndNotify != NULL) {
  3999. ThreadLockAlways(ppopupmenu->spwndNotify, &tlpwndNotify);
  4000. xxxDWP_DoNCActivate(ppopupmenu->spwndNotify,
  4001. ((wParam && !pMenuState->fActiveNoForeground) ? NCA_ACTIVE : NCA_FORCEFRAMEOFF),
  4002. HRGN_FULL);
  4003. ThreadUnlock(&tlpwndNotify);
  4004. }
  4005. }
  4006. break;
  4007. case WM_ACTIVATE:
  4008. if (pMenuState->fModelessMenu) {
  4009. /*
  4010. * If activation is NOT going to a menu window or
  4011. * it's going to a recursed menu, bail
  4012. */
  4013. if ((LOWORD(wParam) == WA_INACTIVE)
  4014. && !pMenuState->fInCallHandleMenuMessages
  4015. && !pMenuState->pGlobalPopupMenu->fInCancel) {
  4016. lParam = (LPARAM)RevalidateHwnd((HWND)lParam);
  4017. if ((lParam != 0)
  4018. && ((GETFNID((PWND)lParam) != FNID_MENU)
  4019. || IsRecursedMenuState(pMenuState, ((PMENUWND)lParam)->ppopupmenu))) {
  4020. /*
  4021. * If we're just coming to the foreground, then
  4022. * activate the popup later and stay up.
  4023. */
  4024. if (pMenuState->fActiveNoForeground
  4025. && (gpqForeground == PtiCurrent()->pq)) {
  4026. pMenuState->fActiveNoForeground = FALSE;
  4027. _PostMessage(pwnd, MN_ACTIVATEPOPUP, 0, 0);
  4028. } else {
  4029. /*
  4030. * Since the menu window is active, ending the menu
  4031. * now would set a new active window, messing the
  4032. * current activation that sent us this message.
  4033. * so end the menu later.
  4034. */
  4035. _PostMessage(pwnd, MN_ENDMENU, 0, 0);
  4036. break;
  4037. }
  4038. }
  4039. }
  4040. goto CallDWP;
  4041. }
  4042. /*
  4043. * We must make sure that the menu window does not get activated.
  4044. * Powerpoint 2.00e activates it deliberately and this causes problems.
  4045. * We try to activate the previously active window in such a case.
  4046. * Fix for Bug #13961 --SANKAR-- 09/26/91--
  4047. */
  4048. /*
  4049. * In Win32, wParam has other information in the hi 16bits, so to
  4050. * prevent infinite recursion, we need to mask off those bits
  4051. * Fix for NT bug #13086 -- 23-Jun-1992 JonPa
  4052. *
  4053. */
  4054. if (LOWORD(wParam)) {
  4055. TL tlpwnd;
  4056. /*
  4057. * This is a super bogus hack. Let's start failing this for 5.0 apps.
  4058. */
  4059. if (Is500Compat(PtiCurrent()->dwExpWinVer)) {
  4060. RIPMSGF1(RIP_WARNING, "Menu window 0x%p activated", pwnd);
  4061. _PostMessage(pwnd, MN_ENDMENU, 0, 0);
  4062. break;
  4063. }
  4064. #if 0
  4065. /*
  4066. * Activate the previously active wnd
  4067. */
  4068. xxxActivateWindow(pwnd, AW_SKIP2);
  4069. #else
  4070. /*
  4071. * Try the previously active window.
  4072. */
  4073. if ((gpqForegroundPrev != NULL) &&
  4074. !FBadWindow(gpqForegroundPrev->spwndActivePrev) &&
  4075. !ISAMENU(gpqForegroundPrev->spwndActivePrev)) {
  4076. pwnd = gpqForegroundPrev->spwndActivePrev;
  4077. } else {
  4078. /*
  4079. * Find a new active window from the top-level window list.
  4080. * Bug 78131: Make sure we don't loop for ever. This is a pretty
  4081. * unusual scenario (in addtion, normally we should not hit this code path)
  4082. * So let's use a counter to rule out the possibility that another
  4083. * weird window configuration is going to make us loop for ever
  4084. */
  4085. PWND pwndMenu = pwnd;
  4086. UINT uCounter = 0;
  4087. do {
  4088. pwnd = NextTopWindow(PtiCurrent(), pwnd, NULL, 0);
  4089. if (pwnd && !FBadWindow(pwnd->spwndLastActive) &&
  4090. !ISAMENU(pwnd->spwndLastActive)) {
  4091. pwnd = pwnd->spwndLastActive;
  4092. uCounter = 0;
  4093. break;
  4094. }
  4095. } while ((pwnd != NULL) && (uCounter++ < 255));
  4096. /*
  4097. * If we couldn't find a window, just bail.
  4098. */
  4099. if (uCounter != 0) {
  4100. RIPMSG0(RIP_ERROR, "xxxMenuWindowProc: couldn't fix active window");
  4101. _PostMessage(pwndMenu, MN_ENDMENU, 0, 0);
  4102. break;
  4103. }
  4104. }
  4105. if (pwnd != NULL) {
  4106. PTHREADINFO pti = PtiCurrent();
  4107. ThreadLockAlwaysWithPti(pti, pwnd, &tlpwnd);
  4108. /*
  4109. * If GETPTI(pwnd) isn't pqCurrent this is a AW_SKIP* activation
  4110. * we'll want to a do a xxxSetForegroundWindow().
  4111. */
  4112. if (GETPTI(pwnd)->pq != pti->pq) {
  4113. /*
  4114. * Only allow this if we're on the current foreground queue.
  4115. */
  4116. if (gpqForeground == pti->pq) {
  4117. xxxSetForegroundWindow(pwnd, FALSE);
  4118. }
  4119. } else {
  4120. xxxActivateThisWindow(pwnd, 0, ATW_SETFOCUS);
  4121. }
  4122. ThreadUnlock(&tlpwnd);
  4123. }
  4124. #endif
  4125. }
  4126. break;
  4127. case WM_SIZE:
  4128. case WM_MOVE:
  4129. /*
  4130. * When a popup has been sized/moved, we need to make
  4131. * sure any dropped hierarchy is moved accordingly.
  4132. */
  4133. if (ppopupmenu->spwndNextPopup != NULL) {
  4134. pItem = MNGetpItem(ppopupmenu, ppopupmenu->posDropped);
  4135. if (pItem != NULL) {
  4136. int x, y;
  4137. PMONITOR pMonitorDummy;
  4138. /*
  4139. * If the dropped hierarchy needs to be recomputed, do it
  4140. */
  4141. #define pmenuNext (((PMENUWND)ppopupmenu->spwndNextPopup)->ppopupmenu->spmenu)
  4142. if (pmenuNext->cxMenu == 0) {
  4143. xxxSendMessage(ppopupmenu->spwndNextPopup, MN_SIZEWINDOW, MNSW_RETURNSIZE, 0L);
  4144. }
  4145. /*
  4146. * Find out the new position
  4147. */
  4148. xxxMNPositionHierarchy(ppopupmenu, pItem,
  4149. pmenuNext->cxMenu + (2 * SYSMET(CXFIXEDFRAME)),
  4150. pmenuNext->cyMenu + (2 * SYSMET(CXFIXEDFRAME)),
  4151. &x, &y, &pMonitorDummy);
  4152. /*
  4153. * Move it
  4154. */
  4155. ThreadLockAlways(ppopupmenu->spwndNextPopup, &tlpwndNotify);
  4156. xxxSetWindowPos(ppopupmenu->spwndNextPopup, NULL,
  4157. x, y, 0, 0,
  4158. SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING);
  4159. ThreadUnlock(&tlpwndNotify);
  4160. #undef pmenuNext
  4161. }
  4162. }
  4163. break;
  4164. case WM_NCHITTEST:
  4165. /*
  4166. * Since modeless menus don't capture the mouse, we
  4167. * process this message to make sure that we always receive
  4168. * a mouse move when the mouse in our window.
  4169. * This also causes us to receive the WM_MOUSELEAVE only when
  4170. * the mouse leaves the window and not just the client area.
  4171. */
  4172. if (pMenuState->fModelessMenu) {
  4173. ptOrg.x = GET_X_LPARAM(lParam);
  4174. ptOrg.y = GET_Y_LPARAM(lParam);
  4175. if (PtInRect(&pwnd->rcWindow, ptOrg)) {
  4176. return HTCLIENT;
  4177. } else {
  4178. return HTNOWHERE;
  4179. }
  4180. } else {
  4181. goto CallDWP;
  4182. }
  4183. default:
  4184. CallDWP:
  4185. return xxxDefWindowProc(pwnd, message, wParam, lParam);
  4186. }
  4187. return 0;
  4188. }