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

4518 lines
138 KiB

  1. #include "priv.h"
  2. #include "sccls.h"
  3. #include "menuband.h"
  4. #include "itbar.h"
  5. #include "bands.h"
  6. #include "isfband.h"
  7. #include "menubar.h"
  8. #include "dpastuff.h" // COrderList_*
  9. #include "theater.h"
  10. #include "resource.h"
  11. #include "oleacc.h"
  12. #include "apithk.h"
  13. #include "uemapp.h"
  14. #include "mnbase.h"
  15. #include "mnfolder.h"
  16. #include "mnstatic.h"
  17. #include "iaccess.h"
  18. #include "tbmenu.h"
  19. #include "mluisupp.h"
  20. // BUGBUG (lamadio): Conflicts with one defined in winuserp.h
  21. #undef WINEVENT_VALID //It's tripping on this...
  22. #include "winable.h"
  23. #define DM_MISC 0 // miscellany
  24. #define PF_USINGNTSD 0x00000400 // set this if you're debugging on ntsd
  25. // This must be reset to -1 on any WM_WININICHANGE. We do it in
  26. // shbrows2.cpp, but if there are no browser windows open when the
  27. // metric changes, we end up running around with a stale value. Oh well.
  28. long g_lMenuPopupTimeout = -1;
  29. static DWORD g_tlsMessageFilter = -1;
  30. // {AD35F50A-0CC0-11d3-AE2D-00C04F8EEA99}
  31. static const CLSID CLSID_MenuBandMetrics =
  32. { 0xad35f50a, 0xcc0, 0x11d3, { 0xae, 0x2d, 0x0, 0xc0, 0x4f, 0x8e, 0xea, 0x99
  33. } };
  34. // Registered window messages for the menuband
  35. UINT g_nMBPopupOpen = 0;
  36. UINT g_nMBFullCancel = 0;
  37. UINT g_nMBDragCancel = 0;
  38. UINT g_nMBAutomation = 0;
  39. UINT g_nMBExecute = 0;
  40. UINT g_nMBOpenChevronMenu = 0;
  41. HCURSOR g_hCursorArrow = NULL;
  42. //UINT g_nMBIgnoreNextDeselect = 0; // Dealt with in menuisf.cpp
  43. BOOL IsAncestor(HWND hwndChild, HWND hwndAncestor)
  44. {
  45. HWND hwnd = hwndChild;
  46. while (hwnd != hwndAncestor && hwnd != NULL)
  47. {
  48. hwnd = GetParent(hwnd);
  49. }
  50. return hwndAncestor == hwnd;
  51. }
  52. //=================================================================
  53. // Implementation of menuband message filter
  54. //=================================================================
  55. extern "C" void DumpMsg(LPCTSTR pszLabel, MSG * pmsg);
  56. // Just one of these, b/c we only need one message filter
  57. CMBMsgFilter g_msgfilter = { 0 };
  58. void FreeMessageFilter(CMBMsgFilter* that)
  59. {
  60. if (g_tlsMessageFilter != -1)
  61. {
  62. CMBMsgFilter* pmf = (CMBMsgFilter*)TlsGetValue(g_tlsMessageFilter);
  63. if (pmf == that)
  64. {
  65. TlsSetValue(g_tlsMessageFilter, NULL);
  66. }
  67. }
  68. }
  69. void CMBMsgFilter::AddRef()
  70. {
  71. _cRef++;
  72. }
  73. void CMBMsgFilter::Release()
  74. {
  75. _cRef--;
  76. if (_cRef <= 0 && _fAllocated)
  77. {
  78. FreeMessageFilter(this);
  79. delete this;
  80. }
  81. }
  82. void CMBMsgFilter::SetModal(BOOL fModal)
  83. {
  84. // There was an interesting problem:
  85. // Click on the Chevron menu. Right click Delete.
  86. // The menus were hosed
  87. // Why?
  88. // Well, I'll tell you:
  89. // We got a deactivate on the subclassed window. We have
  90. // 2 menus subclassing it: The Main menu, and the modal
  91. // chevron menu. Problem is, the main menu snagged the WM_ACTIVATE
  92. // and does a set context. This causes a Pop and releases the Message hook.
  93. // Since I still had a menu up, this caused havoc.
  94. // So I introduced a concept of a "Modal" menuband.
  95. // This says: "Ignore any request to change contexts until I'm done". When
  96. // that modal band is done, it sets the old context back in.
  97. // Seems like a hack, but we need a better underlying archtecture for
  98. // the message passing.
  99. _fModal = fModal;
  100. }
  101. void CMBMsgFilter::ReEngage(void* pvContext)
  102. {
  103. // We need to make sure that we don't dis/reengage when
  104. // switching contexts
  105. if (pvContext == _pvContext)
  106. _fEngaged = TRUE;
  107. }
  108. void CMBMsgFilter::DisEngage(void* pvContext)
  109. {
  110. if (pvContext == _pvContext)
  111. _fEngaged = FALSE;
  112. }
  113. int CMBMsgFilter::GetCount()
  114. {
  115. return FDSA_GetItemCount(&_fdsa);
  116. }
  117. int MsgFilter_GetCount()
  118. {
  119. return GetMessageFilter()->GetCount();
  120. }
  121. CMenuBand * CMBMsgFilter::_GetTopPtr(void)
  122. {
  123. CMenuBand * pmb = NULL;
  124. int cItems = FDSA_GetItemCount(&_fdsa);
  125. if (0 < cItems)
  126. {
  127. MBELEM * pmbelem = FDSA_GetItemPtr(&_fdsa, cItems-1, MBELEM);
  128. pmb = pmbelem->pmb;
  129. }
  130. return pmb;
  131. }
  132. CMenuBand * CMBMsgFilter::_GetBottomMostSelected(void)
  133. {
  134. // Ick, I can't believe I just did this. Mix COM and C++ identities... Yuck.
  135. CMenuBand* pmb = NULL;
  136. if (_pmb)
  137. {
  138. IUnknown_QueryService(SAFECAST(_pmb, IMenuBand*), SID_SMenuBandBottomSelected, CLSID_MenuBand, (void**)&pmb);
  139. // Since we have the C++ identity, release the COM identity.
  140. if (pmb)
  141. pmb->Release();
  142. }
  143. return pmb;
  144. }
  145. CMenuBand * CMBMsgFilter::_GetWindowOwnerPtr(HWND hwnd)
  146. {
  147. CMenuBand * pmb = NULL;
  148. int cItems = FDSA_GetItemCount(&_fdsa);
  149. if (0 < cItems)
  150. {
  151. // Go thru the list of bands on the stack and return the
  152. // one who owns the given window.
  153. int i;
  154. for (i = 0; i < cItems; i++)
  155. {
  156. MBELEM * pmbelem = FDSA_GetItemPtr(&_fdsa, i, MBELEM);
  157. if (pmbelem->pmb && S_OK == pmbelem->pmb->IsWindowOwner(hwnd))
  158. {
  159. pmb = pmbelem->pmb;
  160. break;
  161. }
  162. }
  163. }
  164. return pmb;
  165. }
  166. /*----------------------------------------------------------
  167. Purpose: Return menuband or NULL based upon hittest. pt must be
  168. in screen coords
  169. */
  170. CMenuBand * CMBMsgFilter::_HitTest(POINT pt, HWND * phwnd)
  171. {
  172. CMenuBand * pmb = NULL;
  173. HWND hwnd = NULL;
  174. int cItems = FDSA_GetItemCount(&_fdsa);
  175. if (0 < cItems)
  176. {
  177. // Go thru the list of bands on the stack and return the
  178. // one who owns the given window. Work backwards since the
  179. // later bands are on top (z-order), if the menus ever overlap.
  180. int i = cItems - 1;
  181. while (0 <= i)
  182. {
  183. MBELEM * pmbelem = FDSA_GetItemPtr(&_fdsa, i, MBELEM);
  184. RECT rc;
  185. // Do this dynamically because the hwndBar hasn't been positioned
  186. // until after this mbelem has been pushed onto the msg filter stack.
  187. GetWindowRect(pmbelem->hwndBar, &rc);
  188. if (PtInRect(&rc, pt))
  189. {
  190. pmb = pmbelem->pmb;
  191. hwnd = pmbelem->hwndTB;
  192. break;
  193. }
  194. i--;
  195. }
  196. }
  197. if (phwnd)
  198. *phwnd = hwnd;
  199. return pmb;
  200. }
  201. void CMBMsgFilter::RetakeCapture(void)
  202. {
  203. // The TrackPopupMenu submenus can steal the capture. Take
  204. // it back. Don't take it back if the we're in edit mode,
  205. // because the modal drag/drop loop has the capture at that
  206. // point.
  207. // We do not want to take capture unless we are engaged.
  208. // We need to do this because we are not handling mouse messages lower down
  209. // in the code. When we set the capture, the messages that we do not handle
  210. // trickle up to the top level menu, and can cause weird problems (Such
  211. // as signaling a "click out of bounds" or a context menu of the ITBar)
  212. if (_hwndCapture && !_fPreventCapture && _fEngaged)
  213. {
  214. TraceMsg(TF_MENUBAND, "CMBMsgFilter: Setting capture to %#lx", _hwndCapture);
  215. SetCapture(_hwndCapture);
  216. }
  217. }
  218. void CMBMsgFilter::SetHook(BOOL fSet, BOOL fDontIgnoreSysChar)
  219. {
  220. if (fDontIgnoreSysChar)
  221. _iSysCharStack += fSet? 1: -1;
  222. if (NULL == _hhookMsg && fSet)
  223. {
  224. TraceMsg(TF_MENUBAND, "CMBMsgFilter: Initialize");
  225. _hhookMsg = SetWindowsHookEx(WH_GETMESSAGE, GetMsgHook, HINST_THISDLL, GetCurrentThreadId());
  226. _fDontIgnoreSysChar = fDontIgnoreSysChar;
  227. }
  228. else if (!fSet && _iSysCharStack == 0)
  229. {
  230. TraceMsg(TF_MENUBAND, "CMBMsgFilter: Hook removed");
  231. if (_hhookMsg)
  232. {
  233. UnhookWindowsHookEx(_hhookMsg);
  234. _hhookMsg = NULL;
  235. }
  236. }
  237. }
  238. // 1) Set Deskbars on Both Monitors and set to chevron
  239. // 2) On Monitor #2 open a chevron
  240. // 3) On Monitor #1 open a chevron then open the Start Menu
  241. // Result: Start Menu does not work.
  242. // The reason is, we set the _fModal of the global message filter. This prevents context switches. Why?
  243. // The modal flag was invented to solve context switching problems with the browser frame. So what causes this?
  244. // Well, when switching from #2 to #3, we have not switched contexts. But since we got a click out of bounds, we collapse
  245. // the previous menu. When switching from #3 to #4, neither have the context, so things get messy.
  246. void CMBMsgFilter::ForceModalCollapse()
  247. {
  248. if (_fModal)
  249. {
  250. _fModal = FALSE;
  251. SetContext(NULL, TRUE);
  252. }
  253. }
  254. void CMBMsgFilter::SetContext(void* pvContext, BOOL fSet)
  255. {
  256. TraceMsg(TF_MENUBAND, "CMBMsgFilter::SetContext from 0x%x to 0x%x", _pvContext, pvContext);
  257. // When changing a menuband context, we need to pop all of the items
  258. // in the stack. This is to prevent a race condition that can occur.
  259. // We do not want to pop all of the items off the stack if we're setting the same context.
  260. // We do a set context on Activation, Both when we switch from one Browser frame to another
  261. // but also when right clicking or causing the Rename dialog to be displayed.
  262. BOOL fPop = FALSE;
  263. if (_fModal)
  264. return;
  265. // Are we setting a new context?
  266. if (fSet)
  267. {
  268. // Is this different than the one we've got?
  269. if (pvContext != _pvContext)
  270. {
  271. // Yes, then we need to pop off all of the old items.
  272. fPop = TRUE;
  273. }
  274. _pvContext = pvContext;
  275. }
  276. else
  277. {
  278. // Then we are trying to unset the message hook. Make sure it still belongs to
  279. // this context
  280. if (pvContext == _pvContext)
  281. {
  282. // This context is trying to unset itself, and no other context owns it.
  283. // remove all the old items.
  284. fPop = TRUE;
  285. }
  286. }
  287. if (fPop)
  288. {
  289. CMenuBand* pcmb = _GetTopPtr();
  290. if (pcmb)
  291. {
  292. PostMessage(pcmb->_pmbState->GetSubclassedHWND(), g_nMBFullCancel, 0, 0);
  293. // No release.
  294. if (FDSA_GetItemCount(&_fdsa) != 0)
  295. {
  296. CMBMsgFilter* pmf = GetMessageFilter();
  297. while (pmf->Pop(pvContext))
  298. ;
  299. }
  300. }
  301. }
  302. }
  303. /*----------------------------------------------------------
  304. Purpose: Push another menuband onto the message filter's stack
  305. */
  306. void CMBMsgFilter::Push(void* pvContext, CMenuBand * pmb, IUnknown * punkSite)
  307. {
  308. ASSERT(IS_VALID_CODE_PTR(pmb, CMenuBand));
  309. TraceMsg(TF_MENUBAND, "CMBMsgFilter::Push called from context 0x%x", pvContext);
  310. if (pmb && pvContext == _pvContext)
  311. {
  312. BOOL bRet = TRUE;
  313. HWND hwndBand;
  314. pmb->GetWindow(&hwndBand);
  315. // If the bar isn't available use the band window
  316. HWND hwndBar = hwndBand;
  317. IOleWindow * pow;
  318. IUnknown_QueryService(punkSite, SID_SMenuPopup, IID_IOleWindow,
  319. (LPVOID *)&pow);
  320. if (pow)
  321. {
  322. pow->GetWindow(&hwndBar);
  323. pow->Release();
  324. }
  325. if (NULL == _hhookMsg)
  326. {
  327. // We want to ignore the WM_SYSCHAR message in the message filter because
  328. // we are using the IsMenuMessage call instead of the global message hook.
  329. SetHook(TRUE, FALSE);
  330. TraceMsg(TF_MENUBAND, "CMBMsgFilter::push Setting hook from context 0x%x", pvContext);
  331. _fSetAtPush = TRUE;
  332. }
  333. if (!_fInitialized)
  334. {
  335. ASSERT(NULL == _hwndCapture);
  336. _hwndCapture = hwndBar;
  337. _fInitialized = TRUE;
  338. bRet = FDSA_Initialize(sizeof(MBELEM), CMBELEM_GROW, &_fdsa, _rgmbelem, CMBELEM_INIT);
  339. // We need to initialize this for the top level guy so that we have the correct positioning
  340. // from the start of this new set of bands. This is used to eliminate spurious WM_MOUSEMOVE
  341. // messages which cause problems. See _HandleMouseMessages for more information
  342. AcquireMouseLocation();
  343. }
  344. if (EVAL(bRet))
  345. {
  346. MBELEM mbelem = {0};
  347. TraceMsg(TF_MENUBAND, "CMBMsgFilter: Push (pmp:%#08lx) onto stack", SAFECAST(pmb, IMenuPopup *));
  348. pmb->AddRef();
  349. mbelem.pmb = pmb;
  350. mbelem.hwndTB = hwndBand;
  351. mbelem.hwndBar = hwndBar;
  352. FDSA_AppendItem(&_fdsa, &mbelem);
  353. CMenuBand* pmbTop = _GetTopPtr();
  354. if ((pmbTop && (pmbTop->GetFlags() & SMINIT_LEGACYMENU)) || NULL == GetCapture())
  355. RetakeCapture();
  356. }
  357. else
  358. {
  359. UnhookWindowsHookEx(_hhookMsg);
  360. _hhookMsg = NULL;
  361. _hwndCapture = NULL;
  362. }
  363. }
  364. }
  365. /*----------------------------------------------------------
  366. Purpose: Pop a menuband off the message filter stack
  367. Returns the number of bands left on the stack
  368. */
  369. int CMBMsgFilter::Pop(void* pvContext)
  370. {
  371. int nRet = 0;
  372. TraceMsg(TF_MENUBAND, "CMBMsgFilter::pop called from context 0x%x", pvContext);
  373. // This can be called from a context switch or when we're exiting menu mode,
  374. // so we'll switch off the fact that we clear _hhookMsg when we pop the top twice.
  375. if (pvContext == _pvContext && _hhookMsg)
  376. {
  377. int iItem = FDSA_GetItemCount(&_fdsa) - 1;
  378. MBELEM * pmbelem;
  379. ASSERT(0 <= iItem);
  380. pmbelem = FDSA_GetItemPtr(&_fdsa, iItem, MBELEM);
  381. if (EVAL(pmbelem->pmb))
  382. {
  383. TraceMsg(TF_MENUBAND, "CMBMsgFilter: Pop (pmb=%#08lx) off stack", SAFECAST(pmbelem->pmb, IMenuPopup *));
  384. pmbelem->pmb->Release();
  385. pmbelem->pmb = NULL;
  386. }
  387. FDSA_DeleteItem(&_fdsa, iItem);
  388. if (0 == iItem)
  389. {
  390. TraceMsg(TF_MENUBAND, "CMBMsgFilter::pop removing hook from context 0x%x", pvContext);
  391. if (_fSetAtPush)
  392. SetHook(FALSE, FALSE);
  393. PreventCapture(FALSE);
  394. _fInitialized = FALSE;
  395. if (_hwndCapture && GetCapture() == _hwndCapture)
  396. {
  397. TraceMsg(TF_MENUBAND, "CMBMsgFilter: Releasing capture");
  398. ReleaseCapture();
  399. }
  400. _hwndCapture = NULL;
  401. }
  402. #ifdef UNIX
  403. else if (1 == iItem)
  404. {
  405. CMenuBand * pmb = _GetTopPtr();
  406. if( pmb )
  407. {
  408. // Change item count only if we delete successfully.
  409. if( pmb->RemoveTopLevelFocus() )
  410. iItem = FDSA_GetItemCount(&_fdsa);
  411. }
  412. }
  413. #endif
  414. nRet = iItem;
  415. }
  416. return nRet;
  417. }
  418. LRESULT CMBMsgFilter::_HandleMouseMsgs(MSG * pmsg, BOOL bRemove)
  419. {
  420. LRESULT lRet = 0;
  421. CMenuBand * pmb;
  422. HWND hwnd = GetCapture();
  423. // Do we still have the capture?
  424. if (hwnd != _hwndCapture)
  425. {
  426. // No; is it b/c a CTrackPopupBar has it?
  427. #if 0 // Nuke this trace because I was getting annoyed.
  428. //def DEBUG
  429. pmb = _GetTopPtr();
  430. if (!EVAL(pmb) || !pmb->IsInSubMenu())
  431. {
  432. // No
  433. TraceMsg(TF_WARNING, "CMBMsgFilter: someone else has the capture (%#lx)", hwnd);
  434. }
  435. #endif
  436. if (NULL == hwnd)
  437. {
  438. // There are times that we must retake the capture because
  439. // TrackPopupMenuEx has taken it, or some context menu
  440. // might have taken it, so take it back.
  441. RetakeCapture();
  442. TraceMsg(TF_WARNING, "CMBMsgFilter: taking the capture back");
  443. }
  444. }
  445. else
  446. {
  447. // Yes; decide what to do with it
  448. POINT pt;
  449. HWND hwndPt;
  450. MSG msgT;
  451. pt.x = GET_X_LPARAM(pmsg->lParam);
  452. pt.y = GET_Y_LPARAM(pmsg->lParam);
  453. ClientToScreen(pmsg->hwnd, &pt);
  454. if (WM_MOUSEMOVE == pmsg->message)
  455. {
  456. // The mouse cursor can send repeated WM_MOUSEMOVE messages
  457. // with the same coordinates. When the user tries to navigate
  458. // thru the menus with the keyboard, and the mouse cursor
  459. // happens to be over a menu item, these spurious mouse
  460. // messages cause us to think the menu has been invoked under
  461. // the mouse cursor.
  462. //
  463. // To avoid this unpleasant rudeness, we eat any gratuitous
  464. // WM_MOUSEMOVE messages.
  465. if (_ptLastMove.x == pt.x && _ptLastMove.y == pt.y)
  466. {
  467. pmsg->message = WM_NULL;
  468. goto Bail;
  469. }
  470. // Since this is not a duplicate point, we need to keep it around.
  471. // We will use this stored point for the above comparison
  472. // msadek; W2k bug# 426005
  473. // On a mirrored system, we got a system bug as mouse coordinates has an off-by-one
  474. // This makes comparing the value with what we got from GetCursorPos() always fail.
  475. // Do not use AcquireMouseLocation().
  476. if(!IS_WINDOW_RTL_MIRRORED(pmsg->hwnd))
  477. {
  478. AcquireMouseLocation();
  479. }
  480. else
  481. {
  482. _ptLastMove.x = pt.x;
  483. _ptLastMove.y = pt.y;
  484. }
  485. if (_hcurArrow == NULL)
  486. _hcurArrow = LoadCursor(NULL, IDC_ARROW);
  487. if (GetCursor() != _hcurArrow)
  488. SetCursor(_hcurArrow);
  489. }
  490. pmb = _HitTest(pt, &hwndPt);
  491. if (pmb)
  492. {
  493. // Forward mouse message onto appropriate menuband. Note
  494. // the appropriate menuband's GetMsgFilterCB (below) will call
  495. // ScreenToClient to convert the coords correctly.
  496. // Use a stack variable b/c we don't want to confuse USER32
  497. // by changing the coords of the real message.
  498. msgT = *pmsg;
  499. msgT.lParam = MAKELPARAM(pt.x, pt.y);
  500. lRet = pmb->GetMsgFilterCB(&msgT, bRemove);
  501. // Remember the changed message (if there was one)
  502. pmsg->message = msgT.message;
  503. }
  504. // Debug note: to debug menubands on ntsd, set the prototype
  505. // flag accordingly. This will keep menubands from going
  506. // away the moment the focus changes to the NTSD window.
  507. else if ((WM_LBUTTONDOWN == pmsg->message || WM_RBUTTONDOWN == pmsg->message) &&
  508. !(g_dwPrototype & PF_USINGNTSD))
  509. {
  510. // Mouse down happened outside the menu. Bail.
  511. pmb = _GetTopPtr();
  512. if (EVAL(pmb))
  513. {
  514. msgT.hwnd = pmsg->hwnd;
  515. msgT.message = g_nMBFullCancel;
  516. msgT.wParam = 0;
  517. msgT.lParam = 0;
  518. TraceMsg(TF_MENUBAND, "CMBMsgFilter (pmb=%#08lx): hittest outside, bailing", SAFECAST(pmb, IMenuPopup *));
  519. pmb->GetMsgFilterCB(&msgT, bRemove);
  520. }
  521. #if 0
  522. // Now send the message to the originally intended window
  523. SendMessage(pmsg->hwnd, pmsg->message, pmsg->wParam, pmsg->lParam);
  524. #endif
  525. }
  526. else
  527. {
  528. pmb = _GetTopPtr();
  529. if (pmb)
  530. {
  531. IUnknown_QueryServiceExec(SAFECAST(pmb, IOleCommandTarget*), SID_SMenuBandBottom,
  532. &CGID_MenuBand, MBANDCID_SELECTITEM, MBSI_NONE, NULL, NULL);
  533. }
  534. }
  535. }
  536. Bail:
  537. return lRet;
  538. }
  539. /*----------------------------------------------------------
  540. Purpose: Message hook used to track keyboard and mouse messages
  541. while the menuband is "active".
  542. The menuband can't steal the focus away -- we use this
  543. hook to catch messages.
  544. */
  545. LRESULT CMBMsgFilter::GetMsgHook(int nCode, WPARAM wParam, LPARAM lParam)
  546. {
  547. LRESULT lRet = 0;
  548. MSG * pmsg = (MSG *)lParam;
  549. BOOL bRemove = (PM_REMOVE == wParam);
  550. CMBMsgFilter* pmf = GetMessageFilter();
  551. // The global message filter may be in a state when we are not processing messages,
  552. // but the menubands are still displayed. A situation where this will occur is when
  553. // a dialog box is displayed because of an interaction with the menus.
  554. // Are we engaged? (Are we allowed to process messages?)
  555. if (pmf->_fEngaged)
  556. {
  557. if (WM_SYSCHAR == pmsg->message)
  558. {
  559. // _fDontIgnoreSysChar is set when the Menubands ONLY want to know about
  560. // WM_SYSCHAR and nothing else.
  561. if (pmf->_fDontIgnoreSysChar)
  562. {
  563. CMenuBand * pmb = pmf->GetTopMostPtr();
  564. if (pmb)
  565. lRet = pmb->GetMsgFilterCB(pmsg, bRemove);
  566. }
  567. }
  568. else if (pmf->_fInitialized) // Only filter if we are initalized (have items on the stack)
  569. {
  570. switch (nCode)
  571. {
  572. case HC_ACTION:
  573. #ifdef DEBUG
  574. if (g_dwDumpFlags & DF_GETMSGHOOK)
  575. DumpMsg(TEXT("GetMsg"), pmsg);
  576. #endif
  577. // A lesson about GetMsgHook: it gets the same message
  578. // multiple times for as long as someone calls PeekMessage
  579. // with the PM_NOREMOVE flag. So we want to take action
  580. // only when PM_REMOVE is set (so we don't handle more than
  581. // once). If we modify any messages to redirect them (on a
  582. // regular basis), we must modify all the time so we don't
  583. // confuse the app.
  584. // Messages get redirected to different bands in the stack
  585. // in this way:
  586. //
  587. // 1) Keyboard messages go to the currently open submenu
  588. // (topmost on the stack).
  589. //
  590. // 2) The PopupOpen message goes to the hwnd that belongs
  591. // to the menu band (via IsWindowOwner).
  592. //
  593. switch (pmsg->message)
  594. {
  595. case WM_SYSKEYDOWN:
  596. case WM_KEYDOWN:
  597. case WM_CHAR:
  598. case WM_KEYUP:
  599. case WM_CLOSE: // only this message filter gets WM_CLOSE
  600. {
  601. // There is a situation that can occur when the last selected
  602. // menu pane is NOT the bottom most pane.
  603. // We need to see if that last selected guy is tracking a context
  604. // menu so that we forward the messages correctly.
  605. CMenuBand * pmb = pmf->_GetBottomMostSelected();
  606. if (pmb)
  607. {
  608. // Is it tracking a context menu?
  609. if (S_OK == IUnknown_Exec(SAFECAST(pmb, IMenuBand*), &CGID_MenuBand,
  610. MBANDCID_ISTRACKING, 0, NULL, NULL))
  611. {
  612. // Yes, forward for proper handling.
  613. lRet = pmb->GetMsgFilterCB(pmsg, bRemove);
  614. }
  615. else
  616. {
  617. // No; Then do the default processing. This can happen if there is no
  618. // context menu, but there is a selected parent and not a selected child.
  619. goto TopHandler;
  620. }
  621. }
  622. else
  623. {
  624. TopHandler:
  625. pmb = pmf->_GetTopPtr();
  626. if (pmb)
  627. lRet = pmb->GetMsgFilterCB(pmsg, bRemove);
  628. }
  629. }
  630. break;
  631. case WM_NULL:
  632. // Handle this here (we do nothing) to avoid mistaking this for
  633. // g_nMBPopupOpen below, in case g_nMBPopupOpen is 0 if
  634. // RegisterWindowMessage fails.
  635. break;
  636. default:
  637. if (bRemove && IsInRange(pmsg->message, WM_MOUSEFIRST, WM_MOUSELAST))
  638. {
  639. lRet = pmf->_HandleMouseMsgs(pmsg, bRemove);
  640. }
  641. else if (pmsg->message == g_nMBPopupOpen)
  642. {
  643. CMenuBand * pmb = pmf->_GetWindowOwnerPtr(pmsg->hwnd);
  644. if (pmb)
  645. lRet = pmb->GetMsgFilterCB(pmsg, bRemove);
  646. }
  647. else if (pmsg->message == g_nMBExecute)
  648. {
  649. CMenuBand * pmb = pmf->_GetWindowOwnerPtr(pmsg->hwnd);
  650. if (pmb)
  651. {
  652. VARIANT var;
  653. var.vt = VT_UINT_PTR;
  654. var.ullVal = (UINT_PTR)pmsg->hwnd;
  655. pmb->Exec(&CGID_MenuBand, MBANDCID_EXECUTE, (DWORD)pmsg->wParam, &var, NULL);
  656. }
  657. }
  658. break;
  659. }
  660. break;
  661. default:
  662. if (0 > nCode)
  663. return CallNextHookEx(pmf->_hhookMsg, nCode, wParam, lParam);
  664. break;
  665. }
  666. }
  667. }
  668. // Pass it on to the next hook in the chain
  669. if (0 == lRet)
  670. return CallNextHookEx(pmf->_hhookMsg, nCode, wParam, lParam);
  671. return 0; // Always return 0
  672. }
  673. CMBMsgFilter* GetMessageFilter()
  674. {
  675. CMBMsgFilter* pmf = NULL;
  676. if (g_tlsMessageFilter == -1)
  677. {
  678. DWORD tls = TlsAlloc();
  679. if (tls != -1)
  680. {
  681. InterlockedExchange((LONG*)&g_tlsMessageFilter, tls);
  682. if (tls != g_tlsMessageFilter)
  683. {
  684. TlsFree(tls);
  685. }
  686. }
  687. }
  688. if (g_tlsMessageFilter != -1)
  689. {
  690. pmf = (CMBMsgFilter*)TlsGetValue(g_tlsMessageFilter);
  691. if (pmf == NULL)
  692. {
  693. pmf = new CMBMsgFilter;
  694. if (pmf)
  695. {
  696. pmf->_fAllocated = TRUE;
  697. TlsSetValue(g_tlsMessageFilter, pmf);
  698. }
  699. }
  700. }
  701. if (pmf == NULL)
  702. pmf = &g_msgfilter;
  703. return pmf;
  704. }
  705. //=================================================================
  706. // Implementation of CMenuBand
  707. //=================================================================
  708. // Struct used by EXEC with a MBANDCID_GETFONTS to return fonts
  709. typedef struct tagMBANDFONTS
  710. {
  711. HFONT hFontMenu; // [out] TopLevelMenuBand's menu font
  712. HFONT hFontArrow; // [out] TopLevelMenuBand's font for drawing the cascade arrow
  713. int cyArrow; // [out] Height of TopLevelMenuBand's cascade arrow
  714. int cxArrow; // [out] Width of TopLevelMenuBand's cascade arrow
  715. int cxMargin; // [out] Margin b/t text and arrow
  716. } MBANDFONTS;
  717. #define THISCLASS CMenuBand
  718. #define SUPERCLASS CToolBand
  719. #ifdef DEBUG
  720. int g_nMenuLevel = 0;
  721. #define DBG_THIS _nMenuLevel, SAFECAST(this, IMenuPopup *)
  722. #else
  723. #define DBG_THIS 0, 0
  724. #endif
  725. CMenuBand::CMenuBand() :
  726. SUPERCLASS()
  727. {
  728. GetMessageFilter()->AddRef();
  729. _fCanFocus = TRUE;
  730. _fAppActive = TRUE;
  731. _nItemNew = -1;
  732. _nItemCur = -1;
  733. _nItemTimer = -1;
  734. _uIconSize = ISFBVIEWMODE_SMALLICONS;
  735. _uIdAncestor = ANCESTORDEFAULT;
  736. _nItemSubMenu = -1;
  737. }
  738. // The purpose of this method is to finish initializing Menubands,
  739. // since it can be initialized in many ways.
  740. void CMenuBand::_Initialize(DWORD dwFlags)
  741. {
  742. _fVertical = !BOOLIFY(dwFlags & SMINIT_HORIZONTAL);
  743. _fTopLevel = BOOLIFY(dwFlags & SMINIT_TOPLEVEL);
  744. _dwFlags = dwFlags;
  745. // We cannot have a horizontal menu if it is not the toplevel menu
  746. ASSERT(!_fVertical && _fTopLevel || _fVertical);
  747. if (_fTopLevel)
  748. {
  749. if (!g_nMBPopupOpen)
  750. {
  751. g_nMBPopupOpen = RegisterWindowMessage(TEXT("CMBPopupOpen"));
  752. g_nMBFullCancel = RegisterWindowMessage(TEXT("CMBFullCancel"));
  753. g_nMBDragCancel = RegisterWindowMessage(TEXT("CMBDragCancel"));
  754. g_nMBAutomation = RegisterWindowMessage(TEXT("CMBAutomation"));
  755. g_nMBExecute = RegisterWindowMessage(TEXT("CMBExecute"));
  756. g_nMBOpenChevronMenu = RegisterWindowMessage(TEXT("CMBOpenChevronMenu"));
  757. g_hCursorArrow = LoadCursor(NULL, IDC_ARROW);
  758. TraceMsg(TF_MENUBAND, "CMBPopupOpen message = %#lx", g_nMBPopupOpen);
  759. TraceMsg(TF_MENUBAND, "CMBFullCancel message = %#lx", g_nMBFullCancel);
  760. }
  761. if (!_pmbState)
  762. _pmbState = new CMenuBandState;
  763. }
  764. DEBUG_CODE( _nMenuLevel = -1; )
  765. }
  766. CMenuBand::~CMenuBand()
  767. {
  768. CMBMsgFilter* pmf = GetMessageFilter();
  769. // the message filter does not have a ref'd pointer to us!!!
  770. if (pmf->GetTopMostPtr() == this)
  771. pmf->SetTopMost(NULL);
  772. _CallCB(SMC_DESTROY);
  773. ATOMICRELEASE(_psmcb);
  774. // Cleanup
  775. CloseDW(0);
  776. if (_pmtbMenu)
  777. delete _pmtbMenu;
  778. if (_pmtbShellFolder)
  779. delete _pmtbShellFolder;
  780. ASSERT(_punkSite == NULL);
  781. ATOMICRELEASE(_pmpTrackPopup);
  782. ATOMICRELEASE(_pmbm);
  783. if (_fTopLevel)
  784. {
  785. if (_pmbState)
  786. delete _pmbState;
  787. }
  788. GetMessageFilter()->Release();
  789. }
  790. /*----------------------------------------------------------
  791. Purpose: Create-instance function for class factory
  792. */
  793. HRESULT CMenuBand_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
  794. {
  795. // aggregation checking is handled in class factory
  796. HRESULT hres = E_OUTOFMEMORY;
  797. CMenuBand* pObj = new CMenuBand();
  798. if (pObj)
  799. {
  800. *ppunk = SAFECAST(pObj, IShellMenu*);
  801. hres = S_OK;
  802. }
  803. return hres;
  804. }
  805. /*----------------------------------------------------------
  806. Purpose: Internal create-instance function
  807. */
  808. CMenuBand * CMenuBand_Create(IShellFolder* psf, LPCITEMIDLIST pidl,
  809. BOOL bHorizontal)
  810. {
  811. CMenuBand * pmb = NULL;
  812. if (psf || pidl)
  813. {
  814. DWORD dwFlags = bHorizontal ? (SMINIT_HORIZONTAL | SMINIT_TOPLEVEL) : 0;
  815. pmb = new CMenuBand();
  816. if (pmb)
  817. {
  818. pmb->_Initialize(dwFlags);
  819. pmb->SetShellFolder(psf, pidl, NULL, 0);
  820. }
  821. }
  822. return pmb;
  823. }
  824. #ifdef UNIX
  825. BOOL CMenuBand::RemoveTopLevelFocus()
  826. {
  827. if( _fTopLevel )
  828. {
  829. _CancelMode( MPOS_CANCELLEVEL );
  830. return TRUE;
  831. }
  832. return FALSE;
  833. }
  834. #endif
  835. void CMenuBand::_UpdateButtons()
  836. {
  837. if (_pmtbMenu)
  838. _pmtbMenu->v_UpdateButtons(FALSE);
  839. if (_pmtbShellFolder)
  840. _pmtbShellFolder->v_UpdateButtons(FALSE);
  841. _fForceButtonUpdate = FALSE;
  842. }
  843. HRESULT CMenuBand::ForwardChangeNotify(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
  844. {
  845. // Given a change notify from the ShellFolder child, we will forward that notify to each of our
  846. // sub menus, but only if they have a shell folder child.
  847. HRESULT hres = E_FAIL;
  848. BOOL fDone = FALSE;
  849. CMenuToolbarBase* pmtb = _pmtbBottom; // Start With the bottom toolbar. This is
  850. // is an optimization because typically
  851. // menus that have both a Shell Folder portion
  852. // and a static portion have the majority
  853. // of the change activity in the bottom portion.
  854. // This can be NULL on a shutdown, when we're deregistering change notifies
  855. if (pmtb)
  856. {
  857. HWND hwnd = pmtb->_hwndMB;
  858. for (int iButton = 0; !fDone; iButton++)
  859. {
  860. IShellChangeNotify* ptscn;
  861. int idCmd = GetButtonCmd(hwnd, iButton);
  862. #ifdef DEBUG
  863. TCHAR szSubmenuName[MAX_PATH];
  864. SendMessage(hwnd, TB_GETBUTTONTEXT, idCmd, (LPARAM)szSubmenuName);
  865. TraceMsg(TF_MENUBAND, "CMenuBand: Forwarding Change notify to %s", szSubmenuName);
  866. #endif
  867. // If it's not a seperator, see if there is a sub menu with a shell folder child.
  868. if (idCmd != -1 &&
  869. SUCCEEDED(pmtb->v_GetSubMenu(idCmd, &SID_MenuShellFolder, IID_IShellChangeNotify, (void**)&ptscn)))
  870. {
  871. IShellMenu* psm;
  872. // Don't forward this notify if the sub menu has specifically registered for change notify (By not passing
  873. // DontRegisterChangeNotify.
  874. if (SUCCEEDED(ptscn->QueryInterface(IID_IShellMenu, (void**)&psm)))
  875. {
  876. UINT uIdParent = 0;
  877. DWORD dwFlags = 0;
  878. // Get the flags
  879. psm->GetShellFolder(&dwFlags, NULL, IID_NULL, NULL);
  880. psm->GetMenuInfo(NULL, &uIdParent, NULL, NULL);
  881. // If this menupane is an "Optimized" pane, (meaning that we don't register for change notify
  882. // and forward from a top level menu down) then we want to forward. We also
  883. // forward if this is a child of Menu Folder. If it is a child,
  884. // then it also does not register for change notify, but does not explicitly set it in it's flags
  885. // (review: Should we set it in it's flags?)
  886. // If it is not an optimized pane, then don't forward.
  887. if ((dwFlags & SMSET_DONTREGISTERCHANGENOTIFY) ||
  888. uIdParent == MNFOLDER_IS_PARENT)
  889. {
  890. // There is!, then pass to the child the change.
  891. hres = ptscn->OnChange(lEvent, pidl1, pidl2);
  892. // Update Dir on a Recursive change notify forces us to update everyone... Good thing
  893. // this does not happen alot and is caused by user interaction most of the time.
  894. }
  895. psm->Release();
  896. }
  897. ptscn->Release();
  898. }
  899. // Did we go through all of the buttons on this toolbar?
  900. if (iButton >= ToolBar_ButtonCount(hwnd) - 1)
  901. {
  902. // Yes, then we need to switch to the next toolbar.
  903. if (_pmtbTop != _pmtbBottom && pmtb != _pmtbTop)
  904. {
  905. pmtb = _pmtbTop;
  906. hwnd = pmtb->_hwndMB;
  907. iButton = -1; // -1 because at the end of the loop the for loop will increment.
  908. }
  909. else
  910. {
  911. // No; Then we must be done.
  912. fDone = TRUE;
  913. }
  914. }
  915. }
  916. }
  917. else
  918. hres = S_OK; // Return success because we're shutting down.
  919. return hres;
  920. }
  921. // Resize the parent menubar
  922. VOID CMenuBand::ResizeMenuBar()
  923. {
  924. // If we're not shown, then we do not need to do any kind of resize.
  925. // NOTE: Horizontal menubands are always shown. Don't do any of the
  926. // vertical stuff if we're horizontal.
  927. if (!_fShow)
  928. return;
  929. // If we're horizontal, don't do any Vertical sizing stuff.
  930. if (!_fVertical)
  931. {
  932. // BandInfoChanged is only for Horizontal Menubands.
  933. _BandInfoChanged();
  934. return;
  935. }
  936. // We need to update the buttons before a resize so that the band is the right size.
  937. _UpdateButtons();
  938. // Have the menubar think about changing its height
  939. IUnknown_QueryServiceExec(_punkSite, SID_SMenuPopup, &CGID_MENUDESKBAR,
  940. MBCID_RESIZE, 0, NULL, NULL);
  941. }
  942. STDMETHODIMP CMenuBand::QueryInterface(REFIID riid, void **ppvObj)
  943. {
  944. HRESULT hres;
  945. static const QITAB qit[] = {
  946. QITABENT(CMenuBand, IMenuPopup),
  947. QITABENT(CMenuBand, IMenuBand),
  948. QITABENT(CMenuBand, IShellMenu),
  949. QITABENT(CMenuBand, IShellMenu2),
  950. QITABENT(CMenuBand, IWinEventHandler),
  951. QITABENT(CMenuBand, IShellMenuAcc),
  952. { 0 },
  953. };
  954. hres = QISearch(this, (LPCQITAB)qit, riid, ppvObj);
  955. if (FAILED(hres))
  956. hres = SUPERCLASS::QueryInterface(riid, ppvObj);
  957. // BUGBUG (lamadio) 8.5.98: Nuke this. We should not expose the this pointer,
  958. // this is a bastardization of COM.
  959. if (FAILED(hres) && IsEqualGUID(riid, CLSID_MenuBand))
  960. {
  961. AddRef();
  962. *ppvObj = (LPVOID)this;
  963. hres = S_OK;
  964. }
  965. return hres;
  966. }
  967. /*----------------------------------------------------------
  968. Purpose: IServiceProvider::QueryService method
  969. */
  970. STDMETHODIMP CMenuBand::QueryService(REFGUID guidService,
  971. REFIID riid, void **ppvObj)
  972. {
  973. HRESULT hres = E_FAIL;
  974. *ppvObj = NULL; // assume error
  975. if (IsEqualIID(guidService, SID_SMenuPopup) ||
  976. IsEqualIID(guidService, SID_SMenuBandChild) ||
  977. IsEqualIID(guidService, SID_SMenuBandParent) ||
  978. (_fTopLevel && IsEqualIID(guidService, SID_SMenuBandTop)))
  979. {
  980. if (IsEqualIID(riid, IID_IAccessible) || IsEqualIID(riid, IID_IDispatch))
  981. {
  982. hres = E_OUTOFMEMORY;
  983. CAccessible* pacc = new CAccessible(SAFECAST(this, IMenuBand*));
  984. if (pacc)
  985. {
  986. hres = pacc->InitAcc();
  987. if (SUCCEEDED(hres))
  988. {
  989. hres = pacc->QueryInterface(riid, ppvObj);
  990. }
  991. pacc->Release();
  992. }
  993. }
  994. else
  995. hres = QueryInterface(riid, ppvObj);
  996. }
  997. else if (IsEqualIID(guidService, SID_SMenuBandBottom) ||
  998. IsEqualIID(guidService, SID_SMenuBandBottomSelected))
  999. {
  1000. // SID_SMenuBandBottom queries down
  1001. BOOL fLookingForSelected = IsEqualIID(SID_SMenuBandBottomSelected, guidService);
  1002. // Are we the leaf node?
  1003. if (!_fInSubMenu)
  1004. {
  1005. if ( fLookingForSelected &&
  1006. (_pmtbTracked == NULL ||
  1007. ToolBar_GetHotItem(_pmtbTracked->_hwndMB) == -1))
  1008. {
  1009. hres = E_FAIL;
  1010. }
  1011. else
  1012. {
  1013. hres = QueryInterface(riid, ppvObj); // Yes; QI ourselves
  1014. }
  1015. }
  1016. else
  1017. {
  1018. // No; QS down...
  1019. IMenuPopup* pmp = _pmpSubMenu;
  1020. if (_pmpTrackPopup)
  1021. pmp = _pmpTrackPopup;
  1022. ASSERT(pmp);
  1023. hres = IUnknown_QueryService(pmp, guidService, riid, ppvObj);
  1024. if (FAILED(hres) && fLookingForSelected && _pmtbTracked != NULL)
  1025. {
  1026. hres = QueryInterface(riid, ppvObj); // Yes; QI ourselves
  1027. }
  1028. }
  1029. }
  1030. else if (IsEqualIID(guidService, SID_MenuShellFolder))
  1031. {
  1032. // This is a method of some other menu in the scheme to get to specifically the MenuShellfolder,
  1033. // This is for the COM Identity property.
  1034. if (_pmtbShellFolder)
  1035. hres = _pmtbShellFolder->QueryInterface(riid, ppvObj);
  1036. }
  1037. else
  1038. hres = SUPERCLASS::QueryService(guidService, riid, ppvObj);
  1039. return hres;
  1040. }
  1041. /*----------------------------------------------------------
  1042. Purpose: IWinEventHandler::IsWindowOwner method
  1043. */
  1044. STDMETHODIMP CMenuBand::IsWindowOwner(HWND hwnd)
  1045. {
  1046. if (( _pmtbShellFolder && (_pmtbShellFolder->IsWindowOwner(hwnd) == S_OK) ) ||
  1047. (_pmtbMenu && (_pmtbMenu->IsWindowOwner(hwnd) == S_OK)))
  1048. return S_OK;
  1049. return S_FALSE;
  1050. }
  1051. #define MB_EICH_FLAGS (EICH_SSAVETASKBAR | EICH_SWINDOWMETRICS | EICH_SPOLICY | EICH_SSHELLMENU | EICH_KWINPOLICY)
  1052. /*----------------------------------------------------------
  1053. Purpose: IWinEventHandler::OnWinEvent method
  1054. Processes messages passed on from the bandsite.
  1055. */
  1056. STDMETHODIMP CMenuBand::OnWinEvent(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plres)
  1057. {
  1058. HRESULT hres = NOERROR;
  1059. EnterModeless();
  1060. // Could our metrics be changing? (We keep track of this only for the
  1061. // toplevel menu)
  1062. BOOL fProcessSettingChange = FALSE;
  1063. switch (uMsg)
  1064. {
  1065. case WM_SETTINGCHANGE:
  1066. fProcessSettingChange = !lParam || (SHIsExplorerIniChange(wParam, lParam) & MB_EICH_FLAGS);
  1067. break;
  1068. case WM_SYSCOLORCHANGE:
  1069. case WM_DISPLAYCHANGE:
  1070. fProcessSettingChange = TRUE;
  1071. break;
  1072. }
  1073. if (_fTopLevel &&
  1074. fProcessSettingChange &&
  1075. _pmbState && !_pmbState->IsProcessingChangeNotify())
  1076. {
  1077. // There is a race condition that can occur during a refresh
  1078. // that's really nasty. It causes another one to get pumped in the
  1079. // middle of processing this one, Yuck!
  1080. _pmbState->PushChangeNotify();
  1081. // There is a race condiction that can occur when the menuband is created,
  1082. // but not yet initialized. This has been hit by the IEAK group....
  1083. if (_pmtbTop)
  1084. {
  1085. // Yes; create a new metrics object and tell the submenus
  1086. // about it.
  1087. CMenuBandMetrics* pmbm = new CMenuBandMetrics(_pmtbTop->_hwndMB);
  1088. if (pmbm)
  1089. {
  1090. ATOMICRELEASE(_pmbm);
  1091. _pmbm = pmbm;
  1092. if (_pmtbMenu)
  1093. _pmtbMenu->SetMenuBandMetrics(_pmbm);
  1094. if (_pmtbShellFolder)
  1095. _pmtbShellFolder->SetMenuBandMetrics(_pmbm);
  1096. _CallCB(SMC_REFRESH, wParam, lParam);
  1097. // We need to force a button update at some point so that the new sizes are calculated
  1098. // Setting this flag will cause the buttons to be updatted before the next time it
  1099. // is shown. If, however, the menu is currently displayed, then the ResizeMenuBar will
  1100. // recalculate immediatly.
  1101. _fForceButtonUpdate = TRUE;
  1102. RECT rcOld;
  1103. RECT rcNew;
  1104. // Resize the MenuBar
  1105. GetClientRect(_hwndParent, &rcOld);
  1106. ResizeMenuBar();
  1107. GetClientRect(_hwndParent, &rcNew);
  1108. // If the rect sizes haven't changed, then we need to re-layout the
  1109. // band because the button widths may have changed.
  1110. if (EqualRect(&rcOld, &rcNew) && _fVertical)
  1111. _pmtbTop->NegotiateSize();
  1112. }
  1113. }
  1114. if (_pmtbMenu)
  1115. hres = _pmtbMenu->OnWinEvent(hwnd, uMsg, wParam, lParam, plres);
  1116. if (_pmtbShellFolder)
  1117. hres = _pmtbShellFolder->OnWinEvent(hwnd, uMsg, wParam, lParam, plres);
  1118. _pmbState->PopChangeNotify();
  1119. }
  1120. else
  1121. {
  1122. if (_pmtbMenu && (_pmtbMenu->IsWindowOwner(hwnd) == S_OK) )
  1123. hres = _pmtbMenu->OnWinEvent(hwnd, uMsg, wParam, lParam, plres);
  1124. if (_pmtbShellFolder && (_pmtbShellFolder->IsWindowOwner(hwnd) == S_OK) )
  1125. hres = _pmtbShellFolder->OnWinEvent(hwnd, uMsg, wParam, lParam, plres);
  1126. }
  1127. ExitModeless();
  1128. return hres;
  1129. }
  1130. /*----------------------------------------------------------
  1131. Purpose: IOleWindow::GetWindow method
  1132. */
  1133. STDMETHODIMP CMenuBand::GetWindow(HWND * phwnd)
  1134. {
  1135. // We assert that only a real menu can be hosted in a standard
  1136. // bandsite (we're talking about the horizontal menubar).
  1137. // So we only return the window associated with the static
  1138. // menu.
  1139. if (_pmtbMenu)
  1140. {
  1141. *phwnd = _pmtbMenu->_hwndMB;
  1142. return NOERROR;
  1143. }
  1144. else
  1145. {
  1146. *phwnd = NULL;
  1147. return E_FAIL;
  1148. }
  1149. }
  1150. /*----------------------------------------------------------
  1151. Purpose: IOleWindow::ContextSensitiveHelp method
  1152. */
  1153. STDMETHODIMP CMenuBand::ContextSensitiveHelp(BOOL bEnterMode)
  1154. {
  1155. return SUPERCLASS::ContextSensitiveHelp(bEnterMode);
  1156. }
  1157. /*----------------------------------------------------------
  1158. Purpose: Handle WM_CHAR for accelerators
  1159. This is handled for any vertical menu. Since we have
  1160. two toolbars (potentially), this function determines
  1161. which toolbar gets the message depending on the
  1162. accelerator.
  1163. */
  1164. HRESULT CMenuBand::_HandleAccelerators(MSG * pmsg)
  1165. {
  1166. TCHAR ch = (TCHAR)pmsg->wParam;
  1167. HWND hwndTop = _pmtbTop->_hwndMB;
  1168. HWND hwndBottom = _pmtbBottom->_hwndMB;
  1169. // Here's how this works: the menu can have one or two toolbars.
  1170. //
  1171. // One toolbar: we simply forward the message onto the toolbar
  1172. // and let it handle any potential accelerators.
  1173. //
  1174. // Two toolbars: get the count of accelerators that match the
  1175. // given char for each toolbar. If only one toolbar has at
  1176. // least one match, forward the message onto that toolbar.
  1177. // Otherwise, forward the message onto the currently tracked
  1178. // toolbar and let it negotiate which accelerator button to
  1179. // choose (we might get a TBN_WRAPHOTITEM).
  1180. //
  1181. // If no match occurs, we beep. Beep beep.
  1182. //
  1183. if (!_pmtbTracked)
  1184. SetTracked(_pmtbTop);
  1185. ASSERT(_pmtbTracked);
  1186. if (_pmtbTop != _pmtbBottom)
  1187. {
  1188. int iNumBottomAccel;
  1189. int iNumTopAccel;
  1190. // Tell the dup handler not to handle this one....
  1191. _fProcessingDup = TRUE;
  1192. ToolBar_HasAccelerator(hwndTop, ch, &iNumTopAccel);
  1193. ToolBar_HasAccelerator(hwndBottom, ch, &iNumBottomAccel);
  1194. BOOL bBottom = (0 < iNumBottomAccel);
  1195. BOOL bTop = (0 < iNumTopAccel);
  1196. // Does one or the other (but not both) have an accelerator?
  1197. if (bBottom ^ bTop)
  1198. {
  1199. // Yes; do the work here for that specific toolbar
  1200. HWND hwnd = bBottom ? hwndBottom : hwndTop;
  1201. int cAccel = bBottom ? iNumBottomAccel : iNumTopAccel;
  1202. int idCmd;
  1203. pmsg->message = WM_NULL; // no need to forward the message
  1204. // This should never really fail since we just checked
  1205. EVAL( ToolBar_MapAccelerator(hwnd, ch, &idCmd) );
  1206. DWORD dwFlags = HICF_ACCELERATOR | HICF_RESELECT;
  1207. if (cAccel == 1)
  1208. dwFlags |= HICF_TOGGLEDROPDOWN;
  1209. int iPos = ToolBar_CommandToIndex(hwnd, idCmd);
  1210. ToolBar_SetHotItem2(hwnd, iPos, dwFlags);
  1211. }
  1212. // No; were there no accelerators?
  1213. else if ( !bTop )
  1214. {
  1215. // Yes
  1216. if (_fVertical)
  1217. {
  1218. MessageBeep(MB_OK);
  1219. }
  1220. else
  1221. {
  1222. _CancelMode(MPOS_FULLCANCEL);
  1223. }
  1224. }
  1225. // Else allow the message to go to the top toolbar
  1226. _fProcessingDup = FALSE;
  1227. }
  1228. return NOERROR;
  1229. }
  1230. /*----------------------------------------------------------
  1231. Purpose: Callback for the get message filter. We handle the
  1232. keyboard messages here (rather than IInputObject::
  1233. TranslateAcceleratorIO) so that we can redirect the
  1234. message *and* have the message pump still call
  1235. TranslateMessage to generate WM_CHAR and WM_SYSCHAR
  1236. messages.
  1237. */
  1238. LRESULT CMenuBand::GetMsgFilterCB(MSG * pmsg, BOOL bRemove)
  1239. {
  1240. // (See the note in CMBMsgFilter::GetMsgHook about bRemove.)
  1241. if (bRemove && !_fVertical && (pmsg->message == g_nMBPopupOpen) && _pmtbTracked)
  1242. {
  1243. // Menu is being popped open, send a WM_MENUSELECT equivalent.
  1244. _pmtbTracked->v_SendMenuNotification((UINT)pmsg->wParam, FALSE);
  1245. }
  1246. if (_fTopLevel && // Only do this for the top level
  1247. _dwFlags & SMINIT_USEMESSAGEFILTER && // They want to use the message filter
  1248. // instead of IsMenuMessage
  1249. bRemove && // Only do this if we're removing it.
  1250. WM_SYSCHAR == pmsg->message) // We only care about WM_SYSCHAR
  1251. {
  1252. // We intercept Alt-key combos (when pressed together) here,
  1253. // to prevent USER from going into a false menu loop check.
  1254. // There are compatibility problems if we let that happen.
  1255. //
  1256. // Sent by USER32 when the user hits an Alt-char combination.
  1257. // We need to translate this into popping down the correct
  1258. // menu. Normally we intercept this in the message pump
  1259. //
  1260. if (_OnSysChar(pmsg, TRUE) == S_OK)
  1261. {
  1262. pmsg->message = WM_NULL;
  1263. }
  1264. }
  1265. // If a user menu is up, then we do not want to intercept those messages. Intercepting
  1266. // messages intended for the poped up user menu causes havoc with keyboard accessibility.
  1267. // We also don't want to process messages if we're displaying a sub menu (It should be
  1268. // handling them).
  1269. BOOL fTracking = FALSE;
  1270. if (_pmtbMenu)
  1271. fTracking = _pmtbMenu->v_TrackingSubContextMenu();
  1272. if (_pmtbShellFolder && !fTracking)
  1273. fTracking = _pmtbShellFolder->v_TrackingSubContextMenu();
  1274. if (!_fInSubMenu && !fTracking)
  1275. {
  1276. // We don't process these messages when we're in a (modal) submenu
  1277. switch (pmsg->message)
  1278. {
  1279. case WM_SYSKEYDOWN:
  1280. case WM_KEYDOWN:
  1281. // Don't process IME message. Restore original VK value.
  1282. if (g_fRunOnFE && VK_PROCESSKEY == pmsg->wParam)
  1283. pmsg->wParam = ImmGetVirtualKey(pmsg->hwnd);
  1284. if (bRemove &&
  1285. (VK_ESCAPE == pmsg->wParam || VK_MENU == pmsg->wParam))
  1286. {
  1287. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Received Esc in msg filter", DBG_THIS);
  1288. DWORD dwSelect = (VK_ESCAPE == pmsg->wParam) ? MPOS_CANCELLEVEL : MPOS_FULLCANCEL;
  1289. _CancelMode(dwSelect);
  1290. pmsg->message = WM_NULL;
  1291. return 1;
  1292. }
  1293. // Fall thru
  1294. case WM_CHAR:
  1295. // Hitting the spacebar should invoke the system menu
  1296. if (!_fVertical &&
  1297. WM_CHAR == pmsg->message && TEXT(' ') == (TCHAR)pmsg->wParam)
  1298. {
  1299. // We need to leave this modal loop before bringing
  1300. // up the system menu (otherwise the user would need to
  1301. // hit Alt twice to get out.) Post the message.
  1302. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Leaving menu mode for system menu", DBG_THIS);
  1303. UIActivateIO(FALSE, NULL);
  1304. // Say the Alt-key is down to catch DefWindowProc's attention
  1305. pmsg->lParam |= 0x20000000;
  1306. pmsg->message = WM_SYSCHAR;
  1307. // Use the parent of the toolbar, because toolbar does not
  1308. // forward WM_SYSCHAR onto DefWindowProc.
  1309. pmsg->hwnd = GetParent(_pmtbTop->_hwndMB);
  1310. return 1;
  1311. }
  1312. else if (_fVertical && WM_CHAR == pmsg->message &&
  1313. pmsg->wParam != VK_RETURN)
  1314. {
  1315. #ifdef UNICODE
  1316. // Need to do this before we ask the toolbars..
  1317. // [msadek], On win9x we get the message thru a chain from explorer /iexplore (ANSI app.).
  1318. // and pass it to comctl32 (Unicode) so it will fail to match the hot key.
  1319. // the system sends the message with ANSI char and we treated it as Unicode.
  1320. // It looks like noone is affected with this bug (US, FE) since they have hot keys always in Latin.
  1321. // Bidi platforms are affected since they do have hot keys in native language.
  1322. if(!g_fRunningOnNT)
  1323. {
  1324. pmsg->wParam = AnsiWparamToUnicode(pmsg->wParam);
  1325. }
  1326. #endif // UNICODE
  1327. // We do not want to pass VK_RETURN to _HandleAccelerators
  1328. // because it will try to do a character match. When it fails
  1329. // it will beep. Then we pass the VK_RETURN to the tracked toolbar
  1330. // and it executes the command.
  1331. // Handle accelerators here
  1332. _HandleAccelerators(pmsg);
  1333. }
  1334. // Fall thru
  1335. case WM_KEYUP:
  1336. // Collection point for most key messages...
  1337. if (NULL == _pmtbTracked)
  1338. {
  1339. // Normally we default to the top toolbar, unless that toolbar
  1340. // cannot receive the selection (As is the case on the top level
  1341. // start menu where the fast items are (Empty).
  1342. // Can the top toolbar be cycled into?
  1343. if (!_pmtbTop->DontShowEmpty())
  1344. {
  1345. // Yes;
  1346. SetTracked(_pmtbTop); // default to the top toolbar
  1347. }
  1348. else
  1349. {
  1350. // No; Set the tracked to the bottom, and hope that he can....
  1351. SetTracked(_pmtbBottom);
  1352. }
  1353. }
  1354. // F10 has special meaning for menus.
  1355. // - F10 alone, should toggle the selection of the first item
  1356. // in a horizontal menu
  1357. // - Shift-F10 should display a context menu.
  1358. if (VK_F10 == pmsg->wParam)
  1359. {
  1360. // Is this the Shift-F10 Case?
  1361. if (GetKeyState(VK_SHIFT) < 0)
  1362. {
  1363. // Yes. We need to force this message into a context menu
  1364. // message.
  1365. pmsg->message = WM_CONTEXTMENU;
  1366. pmsg->lParam = -1;
  1367. pmsg->wParam = (WPARAM)_pmtbTracked->_hwndMB;
  1368. return 0;
  1369. }
  1370. else if (!_fVertical) //No; Then we need to toggle in the horizontal case
  1371. {
  1372. if (_pmtbMenu)
  1373. {
  1374. // Set the hot item to the first one.
  1375. int iHot = 0;
  1376. if (ToolBar_GetHotItem(_pmtbMenu->_hwndMB) != -1)
  1377. iHot = -1; // We're toggling the selection off.
  1378. ToolBar_SetHotItem(_pmtbMenu->_hwndMB, iHot);
  1379. }
  1380. return 0;
  1381. }
  1382. }
  1383. // Redirect to the toolbar
  1384. if (_pmtbTracked)
  1385. pmsg->hwnd = _pmtbTracked->_hwndMB;
  1386. return 0;
  1387. case WM_NULL:
  1388. // Handle this here (we do nothing) to avoid mistaking this for
  1389. // g_nMBPopupOpen below, in case g_nMBPopupOpen is 0 if
  1390. // RegisterWindowMessage fails.
  1391. return 0;
  1392. default:
  1393. // We used to handle g_nMBPopupOpen here. But we can't because calling TrackPopupMenu
  1394. // (via CTrackPopupBar::Popup) w/in a GetMessageFilter is very bad.
  1395. break;
  1396. }
  1397. }
  1398. if (bRemove)
  1399. {
  1400. // These messages must be processed even when no submenu is open
  1401. switch (pmsg->message)
  1402. {
  1403. case WM_CLOSE:
  1404. // Being deactivated. Bail out of menus.
  1405. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): sending MPOS_FULLCANCEL", DBG_THIS);
  1406. _CancelMode(MPOS_FULLCANCEL);
  1407. break;
  1408. default:
  1409. if (IsInRange(pmsg->message, WM_MOUSEFIRST, WM_MOUSELAST))
  1410. {
  1411. // If we move the mouse, collapse the tip. Careful not to blow away a balloon tip...
  1412. if (_pmbState)
  1413. _pmbState->HideTooltip(FALSE);
  1414. if (_pmtbShellFolder)
  1415. _pmtbShellFolder->v_ForwardMouseMessage(pmsg->message, pmsg->wParam, pmsg->lParam);
  1416. if (_pmtbMenu)
  1417. _pmtbMenu->v_ForwardMouseMessage(pmsg->message, pmsg->wParam, pmsg->lParam);
  1418. // Don't let the message be dispatched now that we've
  1419. // forwarded it.
  1420. pmsg->message = WM_NULL;
  1421. }
  1422. else if (pmsg->message == g_nMBFullCancel)
  1423. {
  1424. // Popup
  1425. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Received private full cancel message", DBG_THIS);
  1426. _SubMenuOnSelect(MPOS_CANCELLEVEL);
  1427. _CancelMode(MPOS_FULLCANCEL);
  1428. return 1;
  1429. }
  1430. break;
  1431. }
  1432. }
  1433. return 0;
  1434. }
  1435. /*----------------------------------------------------------
  1436. Purpose: Handle WM_SYSCHAR
  1437. This is handled for the toplevel menu only.
  1438. */
  1439. HRESULT CMenuBand::_OnSysChar(MSG * pmsg, BOOL bFirstDibs)
  1440. {
  1441. TCHAR ch = (TCHAR)pmsg->wParam;
  1442. // HACKHACK (scotth): I'm only doing all this checking because I don't
  1443. // understand why the doc-obj case sometimes (and sometimes doesn't)
  1444. // intercept this in its message filter.
  1445. if (!bFirstDibs && _fSysCharHandled)
  1446. {
  1447. _fSysCharHandled = FALSE;
  1448. return S_FALSE;
  1449. }
  1450. if (TEXT(' ') == (TCHAR)pmsg->wParam)
  1451. {
  1452. _fAltSpace = TRUE; // In the words of Spock..."Remember"
  1453. // start menu alt+space
  1454. TraceMsg(DM_MISC, "cmb._osc: alt+space _fTopLevel(1)");
  1455. UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_UIINPUT, UIBL_INPMENU);
  1456. }
  1457. else if (!_fInSubMenu)
  1458. {
  1459. int idBtn;
  1460. ASSERT(_fTopLevel);
  1461. // There is a brief instant when we're merging a menu and pumping messages
  1462. // This results in a null _pmtbMenu.
  1463. if (_pmtbMenu)
  1464. {
  1465. // Only a toplevel menubar follows this codepath. This means only
  1466. // the static menu toolbar will exist (and not the shellfolder toolbar).
  1467. _pmtbTracked = _pmtbMenu;
  1468. HWND hwnd = _pmtbTracked->_hwndMB;
  1469. #ifdef UNICODE
  1470. // [msadek], On win9x we get the message thru a chain from explorer /iexplore (ANSI app.).
  1471. // and pass it to comctl32 (Unicode) so it will fail to match the hot key.
  1472. // the system sends the message with ANSI char and we treated it as Unicode.
  1473. // It looks like noone is affected with this bug (US, FE) since they have hot keys always in Latin.
  1474. // Bidi platforms are affected since they do have hot keys in native language.
  1475. if(!g_fRunningOnNT)
  1476. {
  1477. ch = (TCHAR) AnsiWparamToUnicode(pmsg->wParam);
  1478. }
  1479. #endif // UNICODE
  1480. if (ToolBar_MapAccelerator(hwnd, ch, &idBtn))
  1481. {
  1482. // Post a message since we're already in a menu loop
  1483. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): WM_SYSCHAR: Posting CMBPopup message", DBG_THIS);
  1484. UIActivateIO(TRUE, NULL);
  1485. _pmtbTracked->PostPopup(idBtn, TRUE, TRUE);
  1486. // browser menu alt+key, start menu alt+key
  1487. TraceMsg(DM_MISC, "cmb._osc: alt+key _fTopLevel(1)");
  1488. UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_UIINPUT, UIBL_INPMENU);
  1489. return S_OK;
  1490. }
  1491. }
  1492. }
  1493. // Set or reset
  1494. _fSysCharHandled = bFirstDibs ? TRUE : FALSE;
  1495. return S_FALSE;
  1496. }
  1497. HRESULT CMenuBand::_ProcessMenuPaneMessages(MSG* pmsg)
  1498. {
  1499. if (pmsg->message == g_nMBPopupOpen)
  1500. {
  1501. // Popup the submenu. Since the top-level menuband receives this first, the
  1502. // command must be piped down the chain to the bottom-most menuband.
  1503. IOleCommandTarget * poct;
  1504. QueryService(SID_SMenuBandBottom, IID_IOleCommandTarget, (LPVOID *)&poct);
  1505. if (poct)
  1506. {
  1507. BOOL bSetItem = LOWORD(pmsg->lParam);
  1508. BOOL bInitialSelect = HIWORD(pmsg->lParam);
  1509. VARIANTARG vargIn;
  1510. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Received private popup menu message", DBG_THIS);
  1511. DWORD dwOpt = 0;
  1512. vargIn.vt = VT_I4;
  1513. vargIn.lVal = (LONG)pmsg->wParam;
  1514. if (bSetItem)
  1515. dwOpt |= MBPUI_SETITEM;
  1516. if (bInitialSelect)
  1517. dwOpt |= MBPUI_INITIALSELECT;
  1518. poct->Exec(&CGID_MenuBand, MBANDCID_POPUPITEM, dwOpt, &vargIn, NULL);
  1519. poct->Release();
  1520. return S_OK;
  1521. }
  1522. }
  1523. else if (pmsg->message == g_nMBDragCancel)
  1524. {
  1525. // If we got a drag cancel, make sure that the bottom most
  1526. // menu does not have the drag enter.
  1527. IUnknown_QueryServiceExec(SAFECAST(this, IOleCommandTarget*),
  1528. SID_SMenuBandBottom, &CGID_MenuBand, MBANDCID_DRAGCANCEL, 0, NULL, NULL);
  1529. return S_OK;
  1530. }
  1531. else if (pmsg->message == g_nMBOpenChevronMenu)
  1532. {
  1533. VARIANTARG v;
  1534. v.vt = VT_I4;
  1535. v.lVal = (LONG)pmsg->wParam;
  1536. IUnknown_Exec(_punkSite, &CGID_DeskBand, DBID_PUSHCHEVRON, _dwBandID, &v, NULL);
  1537. }
  1538. else if (pmsg->message == g_nMBFullCancel)
  1539. {
  1540. _SubMenuOnSelect(MPOS_CANCELLEVEL);
  1541. _CancelMode(MPOS_FULLCANCEL);
  1542. return S_OK;
  1543. }
  1544. return S_FALSE;
  1545. }
  1546. /*----------------------------------------------------------
  1547. Purpose: IMenuBand::IsMenuMessage method
  1548. The thread's message pump calls this function to see if any
  1549. messages need to be redirected to the menu band.
  1550. This returns S_OK if the message is handled. The
  1551. message pump should not pass it onto TranslateMessage
  1552. or DispatchMessage if it does.
  1553. */
  1554. STDMETHODIMP CMenuBand::IsMenuMessage(MSG * pmsg)
  1555. {
  1556. HRESULT hres = S_FALSE;
  1557. ASSERT(IS_VALID_WRITE_PTR(pmsg, MSG));
  1558. #ifdef DEBUG
  1559. if (g_dwDumpFlags & DF_TRANSACCELIO)
  1560. DumpMsg(TEXT("CMB::IsMM"), pmsg);
  1561. #endif
  1562. if (!_fShow)
  1563. goto Return;
  1564. switch (pmsg->message)
  1565. {
  1566. case WM_SYSKEYDOWN:
  1567. // blow this off if it's a repeated keystroke
  1568. if (!(pmsg->lParam & 0x40000000))
  1569. {
  1570. SendMessage(_hwndParent, WM_CHANGEUISTATE ,MAKEWPARAM(UIS_CLEAR, UISF_HIDEACCEL), 0);
  1571. // Are we pressing the Alt key to activate the menu?
  1572. if (!_fMenuMode && pmsg->wParam == VK_MENU && _pmbState)
  1573. {
  1574. // Yes; The the menu was activated because of a keyboard,
  1575. // Set the global state to show the keyboard cues.
  1576. _pmbState->SetKeyboardCue(TRUE);
  1577. // Since this only happens on the top level menu,
  1578. // We only have to tell the "Top" menu to update it's state.
  1579. _pmtbTop->SetKeyboardCue();
  1580. }
  1581. }
  1582. break;
  1583. case WM_SYSKEYUP:
  1584. // If we're in menu mode, ignore this message.
  1585. //
  1586. if (_fMenuMode)
  1587. hres = S_OK;
  1588. break;
  1589. case WM_SYSCHAR:
  1590. // We intercept Alt-key combos (when pressed together) here,
  1591. // to prevent USER from going into a false menu loop check.
  1592. // There are compatibility problems if we let that happen.
  1593. //
  1594. // Sent by USER32 when the user hits an Alt-char combination.
  1595. // We need to translate this into popping down the correct
  1596. // menu. Normally we intercept this in the message pump
  1597. //
  1598. // Outlook Express needs a message hook in order to filter this
  1599. // message for perf we do not use that method.
  1600. // Athena fix 222185 (lamadio) We also don't want to do this if we are not active!
  1601. // otherwise when WAB is on top of OE, we'll steal it's messages
  1602. // BUGBUG: i'm removing GetTopMostPtr check below ... breaks menu accelerators for IE5 (224040)
  1603. // (lamadio): If the Message filter is "engaged", then we can process accelerators.
  1604. // Engaged does not mean that the filter is running.
  1605. if (GetMessageFilter()->IsEngaged())
  1606. {
  1607. hres = (_OnSysChar(pmsg, TRUE) == S_OK) ? S_OK : S_FALSE;
  1608. }
  1609. break;
  1610. case WM_KEYDOWN:
  1611. case WM_CHAR:
  1612. case WM_KEYUP:
  1613. if (_fMenuMode)
  1614. {
  1615. // All keystrokes should be handled or eaten by menubands
  1616. // if we're engaged. We must do this, otherwise hosted
  1617. // components like mshtml or word will try to handle the
  1618. // keystroke in CBaseBrowser.
  1619. // Also, don't bother forwarding tabs
  1620. if (VK_TAB != pmsg->wParam)
  1621. {
  1622. // Since we're answer S_OK, dispatch it ourselves.
  1623. TranslateMessage(pmsg);
  1624. DispatchMessage(pmsg);
  1625. }
  1626. hres = S_OK;
  1627. }
  1628. break;
  1629. case WM_CONTEXTMENU:
  1630. // HACKHACK (lamadio): Since the start button has the keyboard focus,
  1631. // the start button will handle this. We need to forward this off to the
  1632. // currently tracked item at the bottom of the chain
  1633. LRESULT lres;
  1634. IWinEventHandler* pweh;
  1635. if (_fMenuMode &&
  1636. SUCCEEDED(QueryService(SID_SMenuBandBottomSelected, IID_IWinEventHandler, (LPVOID *)&pweh)))
  1637. {
  1638. // BUGBUG (lamadio): This will only work because only one of the two possible toolbars
  1639. // handles this
  1640. pweh->OnWinEvent(HWND_BROADCAST, pmsg->message,
  1641. pmsg->wParam, pmsg->lParam, &lres);
  1642. pweh->Release();
  1643. hres = S_OK;
  1644. }
  1645. break;
  1646. default:
  1647. // We only want to process the pane messages in IsMenuMessage when there is no
  1648. // top level HWND. This is for the Deskbar menus. Outlook Express needs the
  1649. // TranslateMenuMessage entry point
  1650. if (_pmbState->GetSubclassedHWND() == NULL)
  1651. hres = _ProcessMenuPaneMessages(pmsg);
  1652. break;
  1653. }
  1654. Return:
  1655. if (!_fMenuMode && hres != S_OK)
  1656. hres = E_FAIL;
  1657. return hres;
  1658. }
  1659. BOOL HasWindowTopmostOwner(HWND hwnd)
  1660. {
  1661. HWND hwndOwner = hwnd;
  1662. while (hwndOwner = GetWindowOwner(hwndOwner))
  1663. {
  1664. if (GetWindowLong(hwndOwner, GWL_EXSTYLE) & WS_EX_TOPMOST)
  1665. return TRUE;
  1666. }
  1667. return FALSE;
  1668. }
  1669. /*----------------------------------------------------------
  1670. Purpose: IMenuBand::TranslateMenuMessage method
  1671. The main app's window proc calls this so the menuband
  1672. catches messages that are dispatched from a different
  1673. message pump (than the thread's main pump).
  1674. Translates messages specially for menubands. Some messages
  1675. are processed while the menuband is active. Others are only
  1676. processed when it is not. Messages that are not b/t
  1677. WM_KEYFIRST and WM_KEYLAST are handled here (the browser
  1678. does not send these messages to IInputObject::
  1679. TranslateAcceleratorIO).
  1680. Returns: S_OK if message is processed
  1681. */
  1682. STDMETHODIMP CMenuBand::TranslateMenuMessage(MSG * pmsg, LRESULT * plRet)
  1683. {
  1684. ASSERT(IS_VALID_WRITE_PTR(pmsg, MSG));
  1685. #ifdef DEBUG
  1686. if (g_dwDumpFlags & DF_TRANSACCELIO)
  1687. DumpMsg(TEXT("CMB::TMM"), pmsg);
  1688. #endif
  1689. switch (pmsg->message)
  1690. {
  1691. case WM_SYSCHAR:
  1692. // In certain doc-obj situations, the OLE message filter (??)
  1693. // grabs this before the main thread's message pump gets a
  1694. // whack at it. So we handle it here too, in case we're in
  1695. // this scenario.
  1696. //
  1697. // See the comments in IsMenuMessage regarding this message.
  1698. return _OnSysChar(pmsg, FALSE);
  1699. case WM_INITMENUPOPUP:
  1700. // Normally the LOWORD(lParam) is the index of the menu that
  1701. // is being popped up. TrackPopupMenu (which CMenuISF uses)
  1702. // always sends this message with an index of 0. This breaks
  1703. // clients (like DefView) who check this value. We need to
  1704. // massage this value if we find we're the source of the
  1705. // WM_INITMENUPOPUP.
  1706. //
  1707. // (This is not in TranslateAcceleratorIO b/c TrackPopupMenu's
  1708. // message pump does not call it. The wndproc must forward
  1709. // the message to this function for us to get it.)
  1710. if (_fInSubMenu && _pmtbTracked)
  1711. {
  1712. // Massage lParam to use the right index
  1713. int iPos = ToolBar_CommandToIndex(_pmtbTracked->_hwndMB, _nItemCur);
  1714. pmsg->lParam = MAKELPARAM(iPos, HIWORD(pmsg->lParam));
  1715. // Return S_FALSE so this message will still be handled
  1716. }
  1717. break;
  1718. case WM_UPDATEUISTATE:
  1719. if (_pmbState)
  1720. {
  1721. // we don't care about UISF_HIDEFOCUS
  1722. if (UISF_HIDEACCEL == HIWORD(pmsg->wParam))
  1723. _pmbState->SetKeyboardCue(UIS_CLEAR == LOWORD(pmsg->wParam) ? TRUE : FALSE);
  1724. }
  1725. break;
  1726. case WM_ACTIVATE:
  1727. {
  1728. CMBMsgFilter* pmf = GetMessageFilter();
  1729. // Debug note: to debug menubands on ntsd, set the prototype
  1730. // flag accordingly. This will keep menubands from going
  1731. // away the moment the focus changes.
  1732. // Becomming inactive?
  1733. if (WA_INACTIVE == LOWORD(pmsg->wParam))
  1734. {
  1735. // Yes; Free up the global object
  1736. // Athena fix (lamadio) 08.02.1998: Athena uses menubands. Since they
  1737. // have a band per window in one thread, we needed a mechanism to switch
  1738. // between them. So we used the Msgfilter to forward messages. Since there
  1739. // are multiple windows, we need to set correct one.
  1740. // But, On a deactivate, we need to NULL it out incase a window,
  1741. // running in the same thread, has normal USER menu. We don't want to steal
  1742. // their messages.
  1743. if (pmf->GetTopMostPtr() == this)
  1744. pmf->SetTopMost(NULL);
  1745. pmf->DisEngage(_pmbState->GetContext());
  1746. HWND hwndLostTo = (HWND)(pmsg->lParam);
  1747. // We won't bail on the menus if we're loosing activation to a child.
  1748. if (!IsAncestor(hwndLostTo, _pmbState->GetWorkerWindow(NULL)))
  1749. {
  1750. if (_fMenuMode &&
  1751. !(g_dwPrototype & PF_USINGNTSD) &&
  1752. !_fDragEntered)
  1753. {
  1754. // Being deactivated. Bail out of menus.
  1755. // (Only the toplevel band gets this message.)
  1756. if (_fInSubMenu)
  1757. {
  1758. IMenuPopup* pmp = _pmpSubMenu;
  1759. if (_pmpTrackPopup)
  1760. pmp = _pmpTrackPopup;
  1761. ASSERT(pmp); // This should be valid. If not, someone screwed up.
  1762. pmp->OnSelect(MPOS_FULLCANCEL);
  1763. }
  1764. _CancelMode(MPOS_FULLCANCEL);
  1765. }
  1766. }
  1767. }
  1768. else if (WA_ACTIVE == LOWORD(pmsg->wParam) ||
  1769. WA_CLICKACTIVE == LOWORD(pmsg->wParam))
  1770. {
  1771. // If I have activation, the Worker Window needs to be bottom...
  1772. //
  1773. // NOTE: Don't do this if the worker window has a topmost owner
  1774. // (such as the tray). Setting a window to HWND_NOTOPMOST moves
  1775. // its owner windows to HWND_NOTOPMOST as well, which in this case
  1776. // was breaking the tray's "always on top" feature.
  1777. //
  1778. HWND hwndWorker = _pmbState->GetWorkerWindow(NULL);
  1779. if (hwndWorker && !HasWindowTopmostOwner(hwndWorker) && !_fDragEntered)
  1780. SetWindowPos(hwndWorker, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE);
  1781. // Set the context because when a menu heirarchy becomes active because the
  1782. // subclassed HWND becomes active, we need to reenable the message hook.
  1783. pmf->SetContext(this, TRUE);
  1784. // When we get reactivated, we need to position ourself above the start bar.
  1785. Exec(&CGID_MenuBand, MBANDCID_REPOSITION, TRUE, NULL, NULL);
  1786. // Becomming activated. We need to reengage the message hook so that
  1787. // we get the correct messages.
  1788. pmf->ReEngage(_pmbState->GetContext());
  1789. // Are we in menu mode?
  1790. if (_fMenuMode)
  1791. {
  1792. // Need to reengage some things.
  1793. // Take the capture back because we have lost it to context menus or dialogs.
  1794. pmf->RetakeCapture();
  1795. }
  1796. pmf->SetTopMost(this);
  1797. }
  1798. //
  1799. // Memphis and NT5 grey their horizontal menus when the windows is inactive.
  1800. //
  1801. if (!_fVertical && (g_bRunOnMemphis || g_bRunOnNT5) && _pmtbMenu)
  1802. {
  1803. // This needs to stay here because of the above check...
  1804. if (WA_INACTIVE == LOWORD(pmsg->wParam))
  1805. {
  1806. _fAppActive = FALSE;
  1807. }
  1808. else
  1809. {
  1810. _fAppActive = TRUE;
  1811. }
  1812. // Reduces flicker by using this instead of an InvalidateWindow/UpdateWindow Pair
  1813. RedrawWindow(_pmtbMenu->_hwndMB, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
  1814. }
  1815. }
  1816. break;
  1817. case WM_SYSCOMMAND:
  1818. if ( !_fMenuMode )
  1819. {
  1820. switch (pmsg->wParam & 0xFFF0)
  1821. {
  1822. case SC_KEYMENU:
  1823. // The user either hit the Alt key by itself or Alt-space.
  1824. // If it was Alt-space, let DefWindowProc handle it so the
  1825. // system menu comes up. Otherwise, we'll handle it to
  1826. // toggle the menuband.
  1827. // Was it Alt-space?
  1828. if (_fAltSpace)
  1829. {
  1830. // Yes; let it go
  1831. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Caught the Alt-space", DBG_THIS);
  1832. _fAltSpace = FALSE;
  1833. }
  1834. else if (_fShow)
  1835. {
  1836. // No; activate the menu
  1837. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Caught the WM_SYSCOMMAND, SC_KEYMENU", DBG_THIS);
  1838. UIActivateIO(TRUE, NULL);
  1839. // We sit in a modal loop here because typically
  1840. // WM_SYSCOMMAND doesn't return until the menu is finished.
  1841. //
  1842. while (_fMenuMode)
  1843. {
  1844. MSG msg;
  1845. if (GetMessage(&msg, NULL, 0, 0))
  1846. {
  1847. if ( S_OK != IsMenuMessage(&msg) )
  1848. {
  1849. TranslateMessage(&msg);
  1850. DispatchMessage(&msg);
  1851. }
  1852. }
  1853. }
  1854. *plRet = 0;
  1855. return S_OK; // Caller shouldn't handle this
  1856. }
  1857. break;
  1858. }
  1859. }
  1860. break;
  1861. default:
  1862. // We only want to process the pane messages in IsMenuMessage when there is no
  1863. // top level HWND. This is for the Deskbar menus. Outlook Express needs the
  1864. // TranslateMenuMessage entry point
  1865. if (_pmbState->GetSubclassedHWND() != NULL)
  1866. return _ProcessMenuPaneMessages(pmsg);
  1867. break;
  1868. }
  1869. return S_FALSE;
  1870. }
  1871. /*----------------------------------------------------------
  1872. Purpose: IObjectWithSite::SetSite method
  1873. Called by the menusite to host this band. Since the
  1874. menuband contains two toolbars, we set their parent
  1875. window to be the site's hwnd.
  1876. */
  1877. STDMETHODIMP CMenuBand::SetSite(IUnknown* punkSite)
  1878. {
  1879. // Do this first because SetParent needs to query to the top level browser for
  1880. // sftbar who queries to the top level browser to get the drag and drop window.
  1881. HRESULT hres = SUPERCLASS::SetSite(punkSite);
  1882. if (_psmcb && _fTopLevel && !(_dwFlags & SMINIT_NOSETSITE))
  1883. IUnknown_SetSite(_psmcb, punkSite);
  1884. IUnknown_GetWindow(punkSite, &_hwndParent);
  1885. // Need this for Closing an expanded vertical menu. Start Menu knows to do this when it's top level,
  1886. // but the Favorites needs to know when it's parent is the horizontal menu.
  1887. VARIANT var;
  1888. if (SUCCEEDED(IUnknown_QueryServiceExec(punkSite, SID_SMenuBandParent, &CGID_MenuBand,
  1889. MBANDCID_ISVERTICAL, 0, NULL, &var)) &&
  1890. var.boolVal == VARIANT_FALSE)
  1891. {
  1892. _fParentIsHorizontal = TRUE;
  1893. }
  1894. // Tell the toolbars who their new parent is
  1895. if (_pmtbMenu)
  1896. _pmtbMenu->SetParent(_hwndParent);
  1897. if (_pmtbShellFolder)
  1898. _pmtbShellFolder->SetParent(_hwndParent);
  1899. return hres;
  1900. }
  1901. /*----------------------------------------------------------
  1902. Purpose: IShellMenu::Initialize method
  1903. */
  1904. STDMETHODIMP CMenuBand::Initialize(IShellMenuCallback* psmcb, UINT uId, UINT uIdAncestor, DWORD dwFlags)
  1905. {
  1906. DEBUG_CODE( _fInitialized = TRUE; );
  1907. // Initalized can be called with NULL values to only set some of them.
  1908. // Default to Vertical
  1909. if (!(dwFlags & SMINIT_HORIZONTAL) && !(dwFlags & SMINIT_VERTICAL))
  1910. dwFlags |= SMINIT_VERTICAL;
  1911. _Initialize(dwFlags);
  1912. if (uIdAncestor != ANCESTORDEFAULT)
  1913. _uIdAncestor = uIdAncestor;
  1914. if (_uId != -1)
  1915. _uId = uId;
  1916. if (psmcb)
  1917. {
  1918. if (!SHIsSameObject(psmcb, _psmcb))
  1919. {
  1920. if (_punkSite && _fTopLevel && !(dwFlags & SMINIT_NOSETSITE))
  1921. IUnknown_SetSite(_psmcb, NULL);
  1922. ATOMICRELEASE(_psmcb);
  1923. _psmcb = psmcb;
  1924. _psmcb->AddRef();
  1925. // We do not set the site in case this callback is shared between 2 bands (Menubar/Chevron menu)
  1926. if (_punkSite && _fTopLevel && !(dwFlags & SMINIT_NOSETSITE))
  1927. IUnknown_SetSite(_psmcb, _punkSite);
  1928. // Only call this if we're setting a new one. Pass the address of the user associated
  1929. // data section. This is so that the callback can associate data with this pane only
  1930. _CallCB(SMC_CREATE, 0, (LPARAM)&_pvUserData);
  1931. }
  1932. }
  1933. return NOERROR;
  1934. }
  1935. /*----------------------------------------------------------
  1936. Purpose: IShellMenu::GetMenuInfo method
  1937. */
  1938. STDMETHODIMP CMenuBand::GetMenuInfo(IShellMenuCallback** ppsmc, UINT* puId,
  1939. UINT* puIdAncestor, DWORD* pdwFlags)
  1940. {
  1941. if (ppsmc)
  1942. {
  1943. *ppsmc = _psmcb;
  1944. if (_psmcb)
  1945. ((IShellMenuCallback*)*ppsmc)->AddRef();
  1946. }
  1947. if (puId)
  1948. *puId = _uId;
  1949. if (puIdAncestor)
  1950. *puIdAncestor = _uIdAncestor;
  1951. if (pdwFlags)
  1952. *pdwFlags = _dwFlags;
  1953. return NOERROR;
  1954. }
  1955. void CMenuBand::_AddToolbar(CMenuToolbarBase* pmtb, DWORD dwFlags)
  1956. {
  1957. pmtb->SetSite(SAFECAST(this, IMenuBand*));
  1958. if (_hwndParent)
  1959. pmtb->CreateToolbar(_hwndParent);
  1960. // Treat this like a two-element stack, where this function
  1961. // behaves like a "push". The one additional trick is we
  1962. // could be pushing onto the top or the bottom of the "stack".
  1963. if (dwFlags & SMSET_BOTTOM)
  1964. {
  1965. if (_pmtbBottom)
  1966. {
  1967. // I don't need to release, because _pmtbTop and _pmtbBottom are aliases for
  1968. // _pmtbShellFolder and _pmtbMenu
  1969. _pmtbTop = _pmtbBottom;
  1970. _pmtbTop->SetToTop(TRUE);
  1971. }
  1972. _pmtbBottom = pmtb;
  1973. _pmtbBottom->SetToTop(FALSE);
  1974. }
  1975. else // Default to Top...
  1976. {
  1977. if (_pmtbTop)
  1978. {
  1979. _pmtbBottom = _pmtbTop;
  1980. _pmtbBottom->SetToTop(FALSE);
  1981. }
  1982. _pmtbTop = pmtb;
  1983. _pmtbTop->SetToTop(TRUE);
  1984. }
  1985. // _pmtbBottom should never be the only toolbar that exists in the menuband.
  1986. if (!_pmtbTop)
  1987. _pmtbTop = _pmtbBottom;
  1988. // The menuband determines there is a single toolbar by comparing
  1989. // the bottom with the top. So make the bottom the same if necessary.
  1990. if (!_pmtbBottom)
  1991. _pmtbBottom = _pmtbTop;
  1992. }
  1993. /*----------------------------------------------------------
  1994. Purpose: IShellMenu::GetShellFolder method
  1995. */
  1996. STDMETHODIMP CMenuBand::GetShellFolder(DWORD* pdwFlags, LPITEMIDLIST* ppidl,
  1997. REFIID riid, void** ppvObj)
  1998. {
  1999. HRESULT hres = E_FAIL;
  2000. if (_pmtbShellFolder)
  2001. {
  2002. *pdwFlags = _pmtbShellFolder->GetFlags();
  2003. hres = S_OK;
  2004. if (ppvObj)
  2005. {
  2006. // HACK HACK. this should QI for a mnfolder specific interface to do this.
  2007. hres = _pmtbShellFolder->GetShellFolder(ppidl, riid, ppvObj);
  2008. }
  2009. }
  2010. return hres;
  2011. }
  2012. /*----------------------------------------------------------
  2013. Purpose: IShellMenu::SetShellFolder method
  2014. */
  2015. STDMETHODIMP CMenuBand::SetShellFolder(IShellFolder* psf, LPCITEMIDLIST pidlFolder, HKEY hKey, DWORD dwFlags)
  2016. {
  2017. ASSERT(_fInitialized);
  2018. HRESULT hres = E_OUTOFMEMORY;
  2019. // If we're processing a change notify, we cannot do anything that will modify state.
  2020. // NOTE: if we don't have a state, we can't possibly processing a change notify
  2021. if (_pmbState && _pmbState->IsProcessingChangeNotify())
  2022. return E_PENDING;
  2023. // Only one shellfolder menu can exist per menuband. Additionally,
  2024. // a shellfolder menu can exist either at the top of the menu, or
  2025. // at the bottom (when it coexists with a static menu).
  2026. // Is there already a shellfolder menu?
  2027. if (_pmtbShellFolder)
  2028. {
  2029. IShellFolderBand* psfb;
  2030. EVAL(SUCCEEDED(_pmtbShellFolder->QueryInterface(IID_IShellFolderBand, (void**)&psfb)));
  2031. hres = psfb->InitializeSFB(psf, pidlFolder);
  2032. psfb->Release();
  2033. }
  2034. else
  2035. {
  2036. _pmtbShellFolder = new CMenuSFToolbar(this, psf, pidlFolder, hKey, dwFlags);
  2037. if (_pmtbShellFolder)
  2038. {
  2039. _AddToolbar(_pmtbShellFolder, dwFlags);
  2040. hres = NOERROR;
  2041. }
  2042. }
  2043. return hres;
  2044. }
  2045. /*----------------------------------------------------------
  2046. Purpose: IMenuBand::GetMenu method
  2047. */
  2048. STDMETHODIMP CMenuBand::GetMenu(HMENU* phmenu, HWND* phwnd, DWORD* pdwFlags)
  2049. {
  2050. HRESULT hres = E_FAIL;
  2051. // HACK HACK. this should QI for a menustatic specific interface to do this.
  2052. if (_pmtbMenu)
  2053. hres = _pmtbMenu->GetMenu(phmenu, phwnd, pdwFlags);
  2054. return hres;
  2055. }
  2056. /*----------------------------------------------------------
  2057. Purpose: IMenuBand::SetMenu method
  2058. */
  2059. STDMETHODIMP CMenuBand::SetMenu(HMENU hmenu, HWND hwnd, DWORD dwFlags)
  2060. {
  2061. // Passing a NULL hmenu is valid. It means destroy our menu object.
  2062. ASSERT(_fInitialized);
  2063. HRESULT hres = E_FAIL;
  2064. // Only one static menu can exist per menuband. Additionally,
  2065. // a static menu can exist either at the top of the menu, or
  2066. // at the bottom (when it coexists with a shellfolder menu).
  2067. // Is there already a static menu?
  2068. if (_pmtbMenu)
  2069. {
  2070. // Since we're merging in a new menu, make sure to update the cache...
  2071. _hmenu = hmenu;
  2072. // Yes
  2073. // HACK HACK. this should QI for a menustatic specific interface to do this.
  2074. return _pmtbMenu->SetMenu(hmenu, hwnd, dwFlags);
  2075. }
  2076. else
  2077. {
  2078. // BUGBUG (lamadio): This is to work around a problem in the interface definintion: We have
  2079. // no method of setting the Subclassed HWND outside of a SetMenu. So I'm just piggybacking
  2080. // off of this. A better fix would be to introduce IMenuBand2::SetSubclass(HWND). IMenuBand
  2081. // actually implements the "Subclassing", so extending this interface would be worthwhile.
  2082. _hwndMenuOwner = hwnd;
  2083. if (_fTopLevel)
  2084. {
  2085. _pmbState->SetSubclassedHWND(hwnd);
  2086. }
  2087. if (hmenu)
  2088. {
  2089. _hmenu = hmenu;
  2090. _pmtbMenu = new CMenuStaticToolbar(this, hmenu, hwnd, _uId, dwFlags);
  2091. if (_pmtbMenu)
  2092. {
  2093. _AddToolbar(_pmtbMenu, dwFlags);
  2094. hres = S_OK;
  2095. }
  2096. else
  2097. hres = E_OUTOFMEMORY;
  2098. }
  2099. }
  2100. return hres;
  2101. }
  2102. /*----------------------------------------------------------
  2103. Purpose: IShellMenu::SetMenuToolbar method
  2104. */
  2105. STDMETHODIMP CMenuBand::SetMenuToolbar(IUnknown* punk, DWORD dwFlags)
  2106. {
  2107. CMenuToolbarBase* pmtb;
  2108. if (punk && SUCCEEDED(punk->QueryInterface(CLSID_MenuToolbarBase, (void**)&pmtb)))
  2109. {
  2110. ASSERT(_pmtbShellFolder == NULL);
  2111. _pmtbShellFolder = pmtb;
  2112. _AddToolbar(pmtb, dwFlags);
  2113. return S_OK;
  2114. }
  2115. else
  2116. {
  2117. return E_INVALIDARG;
  2118. }
  2119. }
  2120. /*----------------------------------------------------------
  2121. Purpose: IShellMenu::InvalidateItem method
  2122. */
  2123. STDMETHODIMP CMenuBand::InvalidateItem(LPSMDATA psmd, DWORD dwFlags)
  2124. {
  2125. HRESULT hres = S_FALSE;
  2126. // If psmd is NULL, we need to just dump the toolbars and do a full reset.
  2127. if (psmd == NULL)
  2128. {
  2129. // If we're processing a change notify, we cannot do anything that will modify state.
  2130. if (_pmbState && _pmbState->IsProcessingChangeNotify())
  2131. return E_PENDING;
  2132. if (_pmbState)
  2133. _pmbState->PushChangeNotify();
  2134. // Tell the callback we're refreshing so that it can
  2135. // reset any cached state
  2136. _CallCB(SMC_REFRESH);
  2137. _fExpanded = FALSE;
  2138. // We don't need to refill if the caller only wanted to
  2139. // refresh the sub menus.
  2140. // Refresh the Shell Folder first because
  2141. // It may have no items after it's done, and the
  2142. // menuband may rely on this to add a seperator
  2143. if (_pmtbShellFolder)
  2144. _pmtbShellFolder->v_Refresh();
  2145. // Refresh the Static menu
  2146. if (_pmtbMenu)
  2147. _pmtbMenu->v_Refresh();
  2148. if (_pmpSubMenu)
  2149. {
  2150. _fInSubMenu = FALSE;
  2151. IUnknown_SetSite(_pmpSubMenu, NULL);
  2152. ATOMICRELEASE(_pmpSubMenu);
  2153. }
  2154. if (_pmbState)
  2155. _pmbState->PopChangeNotify();
  2156. }
  2157. else
  2158. {
  2159. if (_pmtbTop)
  2160. hres = _pmtbTop->v_InvalidateItem(psmd, dwFlags);
  2161. // We refresh everything at this level if the psmd is null
  2162. if (_pmtbBottom && hres != S_OK)
  2163. hres = _pmtbBottom->v_InvalidateItem(psmd, dwFlags);
  2164. }
  2165. return hres;
  2166. }
  2167. /*----------------------------------------------------------
  2168. Purpose: IShellMenu::GetState method
  2169. */
  2170. STDMETHODIMP CMenuBand::GetState(LPSMDATA psmd)
  2171. {
  2172. if (_pmtbTracked)
  2173. return _pmtbTracked->v_GetState(-1, psmd);
  2174. // todo: might want to put stuff from _CallCB (below) in here
  2175. return E_FAIL;
  2176. }
  2177. HRESULT CMenuBand::_CallCB(DWORD dwMsg, WPARAM wParam, LPARAM lParam)
  2178. {
  2179. if (!_psmcb)
  2180. return S_FALSE;
  2181. // We don't need to check callback mask here because these are not maskable events.
  2182. SMDATA smd = {0};
  2183. smd.punk = SAFECAST(this, IShellMenu*);
  2184. smd.uIdParent = _uId;
  2185. smd.uIdAncestor = _uIdAncestor;
  2186. smd.hwnd = _hwnd;
  2187. smd.hmenu = _hmenu;
  2188. smd.pvUserData = _pvUserData;
  2189. if (_pmtbShellFolder)
  2190. _pmtbShellFolder->GetShellFolder(&smd.pidlFolder, IID_IShellFolder, (void**)&smd.psf);
  2191. HRESULT hres = _psmcb->CallbackSM(&smd, dwMsg, wParam, lParam);
  2192. ILFree(smd.pidlFolder);
  2193. if (smd.psf)
  2194. smd.psf->Release();
  2195. return hres;
  2196. }
  2197. /*----------------------------------------------------------
  2198. Purpose: IInputObject::TranslateAcceleratorIO
  2199. This is called by the base browser only when the menuband
  2200. "has the focus", and only for messages b/t WM_KEYFIRST
  2201. and WM_KEYLAST. This isn't very useful for menubands.
  2202. See the explanations in GetMsgFilterCB, IsMenuMessage
  2203. and TranslateMenuMessage.
  2204. In addition, menubands cannot ever have the activation,
  2205. so this method should never be called.
  2206. Returns S_OK if handled.
  2207. */
  2208. STDMETHODIMP CMenuBand::TranslateAcceleratorIO(LPMSG pmsg)
  2209. {
  2210. AssertMsg(0, TEXT("Menuband has the activation but it shouldn't!"));
  2211. return S_FALSE;
  2212. }
  2213. /*----------------------------------------------------------
  2214. Purpose: IInputObject::HasFocusIO
  2215. */
  2216. STDMETHODIMP CMenuBand::HasFocusIO()
  2217. {
  2218. // We consider a menuband has the focus even if it has submenus
  2219. // that are currently cascaded out. All menubands in the chain
  2220. // have the focus.
  2221. return _fMenuMode ? S_OK : S_FALSE;
  2222. }
  2223. /*----------------------------------------------------------
  2224. Purpose: IMenuPopup::SetSubMenu method
  2225. The child menubar calls us with its IMenuPopup pointer.
  2226. */
  2227. STDMETHODIMP CMenuBand::SetSubMenu(IMenuPopup * pmp, BOOL fSet)
  2228. {
  2229. ASSERT(IS_VALID_CODE_PTR(pmp, IMenuPopup));
  2230. if (fSet)
  2231. {
  2232. _fInSubMenu = TRUE;
  2233. }
  2234. else
  2235. {
  2236. if (_pmtbTracked)
  2237. {
  2238. _pmtbTracked->PopupClose();
  2239. }
  2240. _fInSubMenu = FALSE;
  2241. _nItemSubMenu = -1;
  2242. }
  2243. return S_OK;
  2244. }
  2245. HRESULT CMenuBand::_SiteSetSubMenu(IMenuPopup * pmp, BOOL bSet)
  2246. {
  2247. HRESULT hres;
  2248. IMenuPopup * pmpSite;
  2249. hres = IUnknown_QueryService(_punkSite, SID_SMenuPopup, IID_IMenuPopup,
  2250. (LPVOID *)&pmpSite);
  2251. if (SUCCEEDED(hres))
  2252. {
  2253. hres = pmpSite->SetSubMenu(pmp, bSet);
  2254. pmpSite->Release();
  2255. }
  2256. return hres;
  2257. }
  2258. /*----------------------------------------------------------
  2259. Purpose: Tell the GetMsg filter that this menuband is ready to
  2260. listen to messages.
  2261. */
  2262. HRESULT CMenuBand::_EnterMenuMode(void)
  2263. {
  2264. ASSERT(!_fMenuMode); // Must not push onto stack more than once
  2265. if (g_dwProfileCAP & 0x00002000)
  2266. StartCAP();
  2267. DEBUG_CODE( _nMenuLevel = g_nMenuLevel++; )
  2268. _fMenuMode = TRUE;
  2269. _fInSubMenu = FALSE;
  2270. _nItemMove = -1;
  2271. _fCascadeAnimate = TRUE;
  2272. _hwndFocusPrev = NULL;
  2273. if (_fTopLevel)
  2274. {
  2275. // BUGBUG (lamadio): this piece should be moved to the shbrowse callback
  2276. // REVIEW (scotth): some embedded controls (like the surround
  2277. // video ctl on the carpoint website) have another thread that
  2278. // eats all the messages when the control has the focus.
  2279. // This prevents us from getting any messages once we're in
  2280. // menu mode. I don't understand why USER menus work yet.
  2281. // One way to work around this bug is to detect this case and
  2282. // set the focus to our main window for the duration.
  2283. if (GetWindowThreadProcessId(GetFocus(), NULL) != GetCurrentThreadId())
  2284. {
  2285. IShellBrowser* psb;
  2286. if (SUCCEEDED(QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void**)&psb)))
  2287. {
  2288. HWND hwndT;
  2289. psb->GetWindow(&hwndT);
  2290. _hwndFocusPrev = SetFocus(hwndT);
  2291. psb->Release();
  2292. }
  2293. }
  2294. _hCursorOld = GetCursor();
  2295. SetCursor(g_hCursorArrow);
  2296. HideCaret(NULL);
  2297. }
  2298. _SiteSetSubMenu(this, TRUE);
  2299. if (_pmtbTop)
  2300. {
  2301. HWND hwnd = _pmtbTop->_hwndMB;
  2302. if (!_fVertical && -1 == _nItemNew)
  2303. {
  2304. // The Alt key always highlights the first menu item initially
  2305. SetTracked(_pmtbTop);
  2306. ToolBar_SetHotItem(hwnd, 0);
  2307. NotifyWinEvent(EVENT_OBJECT_FOCUS, _pmtbTop->_hwndMB, OBJID_CLIENT,
  2308. GetIndexFromChild(TRUE, 0));
  2309. }
  2310. _pmtbTop->Activate(TRUE);
  2311. // The toolbar usually tracks mouse events. However, as the mouse
  2312. // moves over submenus, we still want the parent menubar to
  2313. // behave as if it has retained the focus (that is, keep the
  2314. // last selected item highlighted). This also prevents the toolbar
  2315. // from handling WM_MOUSELEAVE messages unnecessarily.
  2316. ToolBar_SetAnchorHighlight(hwnd, TRUE);
  2317. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Entering menu mode", DBG_THIS);
  2318. NotifyWinEvent(_fVertical? EVENT_SYSTEM_MENUPOPUPSTART: EVENT_SYSTEM_MENUSTART,
  2319. hwnd, OBJID_CLIENT, CHILDID_SELF);
  2320. }
  2321. if (_pmtbBottom)
  2322. {
  2323. _pmtbBottom->Activate(TRUE);
  2324. ToolBar_SetAnchorHighlight(_pmtbBottom->_hwndMB, TRUE); // Turn off anchoring
  2325. }
  2326. GetMessageFilter()->Push(_pmbState->GetContext(), this, _punkSite);
  2327. return S_OK;
  2328. }
  2329. void CMenuBand::_ExitMenuMode(void)
  2330. {
  2331. _fMenuMode = FALSE;
  2332. _nItemCur = -1;
  2333. _fPopupNewMenu = FALSE;
  2334. _fInitialSelect = FALSE;
  2335. CMBMsgFilter* pmf = GetMessageFilter();
  2336. if (_pmtbTop)
  2337. {
  2338. HWND hwnd = _pmtbTop->_hwndMB;
  2339. ToolBar_SetAnchorHighlight(hwnd, FALSE); // Turn off anchoring
  2340. if (!_fVertical)
  2341. {
  2342. // Use the first item, since we're assuming every menu must have
  2343. // at least one item
  2344. _pmtbTop->v_SendMenuNotification(0, TRUE);
  2345. // The user may have clicked outside the menu, which would have
  2346. // cancelled it. But since we set the ANCHORHIGHLIGHT attribute,
  2347. // the toolbar won't receive a message to cause it to
  2348. // remove the highlight. So do it explicitly now.
  2349. SetTracked(NULL);
  2350. UpdateWindow(hwnd);
  2351. }
  2352. _pmtbTop->Activate(FALSE);
  2353. NotifyWinEvent(_fVertical? EVENT_SYSTEM_MENUPOPUPEND: EVENT_SYSTEM_MENUEND,
  2354. hwnd, OBJID_CLIENT, CHILDID_SELF);
  2355. }
  2356. if (_pmtbBottom)
  2357. {
  2358. _pmtbBottom->Activate(FALSE);
  2359. ToolBar_SetAnchorHighlight(_pmtbBottom->_hwndMB, FALSE); // Turn off anchoring
  2360. }
  2361. pmf->Pop(_pmbState->GetContext());
  2362. _SiteSetSubMenu(this, FALSE);
  2363. if (_fTopLevel)
  2364. {
  2365. SetCursor(_hCursorOld);
  2366. ShowCaret(NULL);
  2367. pmf->SetContext(this, FALSE);
  2368. // We do this here, because ShowDW(FALSE) does not get called on the
  2369. // top level menu band. This resets the state, so that the accelerators
  2370. // are not shown.
  2371. if (_pmbState)
  2372. _pmbState->SetKeyboardCue(FALSE);
  2373. // Tell the menus to update their state to the current global cue state.
  2374. if (_pmtbTop)
  2375. _pmtbTop->SetKeyboardCue();
  2376. if (_pmtbTop != _pmtbBottom && _pmtbBottom)
  2377. _pmtbBottom->SetKeyboardCue();
  2378. }
  2379. if (_hwndFocusPrev)
  2380. SetFocus(_hwndFocusPrev);
  2381. if (_fTopLevel)
  2382. {
  2383. //
  2384. // The top-level menu has gone away. Win32 focus and ui-activation don't
  2385. // actually change when this happens, so the browser and focused dude have
  2386. // no idea that something happened and won't generate any AA event. So, we
  2387. // do it here for them. Note that if there was a selection inside the focused
  2388. // dude, we'll lose it. This is the best we can do for now, as we don't
  2389. // currently have a way to tell the focused/ui-active guy (who knows about the
  2390. // current selection) to reannounce focus.
  2391. //
  2392. HWND hwndFocus = GetFocus();
  2393. NotifyWinEvent(EVENT_OBJECT_FOCUS, hwndFocus, OBJID_CLIENT, CHILDID_SELF);
  2394. }
  2395. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Exited menu mode", DBG_THIS);
  2396. DEBUG_CODE( g_nMenuLevel--; )
  2397. DEBUG_CODE( _nMenuLevel = -1; )
  2398. if (g_dwProfileCAP & 0x00002000)
  2399. StopCAP();
  2400. }
  2401. /*----------------------------------------------------------
  2402. Purpose: IInputObject::UIActivateIO
  2403. Menubands CANNOT take the activation. Normally
  2404. a band would return S_OK and call the site's
  2405. OnFocusChangeIS method, so that its TranslateAcceleratorIO
  2406. method would receive keyboard messages.
  2407. However, menus are different. The window/toolbar that
  2408. currently has the activation must retain that activation
  2409. when the menu pops down. Because of this, menubands use
  2410. a GetMessage filter to intercept messages.
  2411. */
  2412. STDMETHODIMP CMenuBand::UIActivateIO(BOOL fActivate, LPMSG lpMsg)
  2413. {
  2414. HRESULT hres;
  2415. ASSERT(NULL == lpMsg || IS_VALID_WRITE_PTR(lpMsg, MSG));
  2416. if (lpMsg != NULL) {
  2417. // don't allow TAB to band (or any other 'non-explicit' activation).
  2418. // (if we just cared about TAB we'd check IsVK_TABCycler).
  2419. // all kinds of badness would result if we did.
  2420. // the band can't take focus (see above), so it can't obey the
  2421. // UIAct/OnFocChg rules (e.g. can't call OnFocusChangeIS), so
  2422. // our basic activation-tracking assumptions would be broken.
  2423. return S_FALSE;
  2424. }
  2425. if (fActivate)
  2426. {
  2427. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): UIActivateIO(%d)", DBG_THIS, fActivate);
  2428. if (!_fMenuMode)
  2429. {
  2430. _EnterMenuMode();
  2431. // BUGBUG (lamadio) : Should go in the Favorites callback.
  2432. // The toplevel menuband does not set the real activation.
  2433. // But the children do, so activation can be communicated
  2434. // with the parent menuband.
  2435. if (_fVertical)
  2436. {
  2437. IUnknown_OnFocusChangeIS(_punkSite, SAFECAST(this, IInputObject*), TRUE);
  2438. }
  2439. else
  2440. {
  2441. IUnknown_Exec(_punkSite, &CGID_Theater, THID_TOOLBARACTIVATED, 0, NULL, NULL);
  2442. }
  2443. }
  2444. if (_fPopupNewMenu)
  2445. {
  2446. _nItemCur = _nItemNew;
  2447. ASSERT(-1 != _nItemCur);
  2448. ASSERT(_pmtbTracked);
  2449. _fPopupNewMenu = FALSE;
  2450. _nItemNew = -1;
  2451. // Popup a menu
  2452. hres = _pmtbTracked->PopupOpen(_nItemCur);
  2453. if (FAILED(hres))
  2454. {
  2455. // Don't fail the activation
  2456. }
  2457. else if (S_FALSE == hres)
  2458. {
  2459. // The submenu was modal and is finished now
  2460. _ExitMenuMode();
  2461. }
  2462. }
  2463. }
  2464. else if (_fMenuMode)
  2465. {
  2466. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): UIActivateIO(%d)", DBG_THIS, fActivate);
  2467. ASSERT( !_fInSubMenu );
  2468. if (!_fTopLevel)
  2469. IUnknown_OnFocusChangeIS(_punkSite, SAFECAST(this, IInputObject*), FALSE);
  2470. _ExitMenuMode();
  2471. }
  2472. return S_FALSE;
  2473. }
  2474. /*----------------------------------------------------------
  2475. Purpose: IDeskBand::GetBandInfo method
  2476. */
  2477. HRESULT CMenuBand::GetBandInfo(DWORD dwBandID, DWORD fViewMode,
  2478. DESKBANDINFO* pdbi)
  2479. {
  2480. HRESULT hres = NOERROR;
  2481. _dwBandID = dwBandID; // critical for perf! (BandInfoChanged)
  2482. pdbi->dwMask &= ~DBIM_TITLE; // no title (ever, for now)
  2483. // We expect that _pmtbBottom should never be the only toolbar
  2484. // that exists in the menuband.
  2485. ASSERT(NULL == _pmtbBottom || _pmtbTop);
  2486. pdbi->dwModeFlags = DBIMF_USECHEVRON;
  2487. if (_pmtbTop)
  2488. {
  2489. // If the buttons need to be updated in the toolbars, the we should
  2490. // do this before we start asking them about their sizes....
  2491. if (_fForceButtonUpdate)
  2492. {
  2493. _UpdateButtons();
  2494. }
  2495. if (_fVertical)
  2496. {
  2497. pdbi->ptMaxSize.y = 0;
  2498. pdbi->ptMaxSize.x = 0;
  2499. SIZE size = {0};
  2500. if (_pmtbMenu)
  2501. {
  2502. // size param zero here => it's just an out param
  2503. _pmtbMenu->GetSize(&size);
  2504. // HACKHACK (lamadio): On downlevel, LARGE metrics mode causes
  2505. // Start menu to push the programs menu item off screen.
  2506. if (size.cy > (3 * GetSystemMetrics(SM_CYSCREEN) / 4))
  2507. {
  2508. Exec(&CGID_MenuBand, MBANDCID_SETICONSIZE, ISFBVIEWMODE_SMALLICONS, NULL, NULL);
  2509. size.cx = 0;
  2510. size.cy = 0;
  2511. _pmtbMenu->GetSize(&size);
  2512. }
  2513. pdbi->ptMaxSize.y = size.cy;
  2514. pdbi->ptMaxSize.x = size.cx;
  2515. }
  2516. if (_pmtbShellFolder)
  2517. {
  2518. // size param should be non-zero here => it's an in/out param
  2519. _pmtbShellFolder->GetSize(&size);
  2520. pdbi->ptMaxSize.y += size.cy + ((_pmtbMenu && !_fExpanded)? 1 : 0); // Minor sizing problem
  2521. pdbi->ptMaxSize.x = max(size.cx, pdbi->ptMaxSize.x);
  2522. }
  2523. pdbi->ptMinSize = pdbi->ptMaxSize;
  2524. }
  2525. else
  2526. {
  2527. HWND hwnd = _pmtbTop->_hwndMB;
  2528. ShowDW(TRUE);
  2529. SIZE rgSize;
  2530. if ( SendMessage( hwnd, TB_GETMAXSIZE, 0, (LPARAM) &rgSize ))
  2531. {
  2532. pdbi->ptActual.y = rgSize.cy;
  2533. SendMessage(hwnd, TB_GETIDEALSIZE, FALSE, (LPARAM)&pdbi->ptActual);
  2534. }
  2535. // make our min size identical to the size of the first button
  2536. // (we're assuming that the toolbar has at least one button)
  2537. RECT rc;
  2538. SendMessage(hwnd, TB_GETITEMRECT, 0, (WPARAM)&rc);
  2539. pdbi->ptMinSize.x = RECTWIDTH(rc);
  2540. pdbi->ptMinSize.y = RECTHEIGHT(rc);
  2541. }
  2542. }
  2543. return hres;
  2544. }
  2545. /*----------------------------------------------------------
  2546. Purpose: IOleService::Exec method
  2547. */
  2548. STDMETHODIMP CMenuBand::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
  2549. DWORD nCmdExecOpt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut)
  2550. {
  2551. // Don't do anything if we're closing.
  2552. if (_fClosing)
  2553. return E_FAIL;
  2554. if (pguidCmdGroup == NULL)
  2555. {
  2556. /*NOTHING*/
  2557. }
  2558. else if (IsEqualGUID(CGID_MENUDESKBAR, *pguidCmdGroup))
  2559. {
  2560. switch (nCmdID)
  2561. {
  2562. case MBCID_GETSIDE:
  2563. if (pvarargOut)
  2564. {
  2565. BOOL fOurChoice = FALSE;
  2566. pvarargOut->vt = VT_I4;
  2567. if (!_fTopLevel)
  2568. {
  2569. // if we are not the top level menu, we
  2570. // must continue with the direction our parent was in
  2571. IMenuPopup* pmpParent;
  2572. IUnknown_QueryService(_punkSite, SID_SMenuPopup, IID_IMenuPopup, (LPVOID*)&pmpParent);
  2573. if (pmpParent)
  2574. {
  2575. if (FAILED(IUnknown_Exec(pmpParent, pguidCmdGroup, nCmdID, nCmdExecOpt, pvarargIn, pvarargOut)))
  2576. fOurChoice = TRUE;
  2577. pmpParent->Release();
  2578. }
  2579. } else
  2580. fOurChoice = TRUE;
  2581. if (!fOurChoice) {
  2582. // only use the parent's side hint if it is in the same orientation (ie, horizontal menubar to vertical popup
  2583. // means we need to make a new choice)
  2584. BOOL fParentVertical = (pvarargOut->lVal == MENUBAR_RIGHT || pvarargOut->lVal == MENUBAR_LEFT);
  2585. if (BOOLIFY(_fVertical) != BOOLIFY(fParentVertical))
  2586. fOurChoice = TRUE;
  2587. }
  2588. if (fOurChoice)
  2589. {
  2590. if (_fVertical)
  2591. {
  2592. HWND hWndMenuBand;
  2593. //
  2594. // The MenuBand is Mirrored , then start the first Menu Window
  2595. // as Mirrored. [samera]
  2596. //
  2597. if ((SUCCEEDED(GetWindow(&hWndMenuBand))) &&
  2598. (IS_WINDOW_RTL_MIRRORED(hWndMenuBand)) )
  2599. pvarargOut->lVal = MENUBAR_LEFT;
  2600. else
  2601. pvarargOut->lVal = MENUBAR_RIGHT;
  2602. }
  2603. else
  2604. pvarargOut->lVal = MENUBAR_BOTTOM;
  2605. }
  2606. }
  2607. return S_OK;
  2608. }
  2609. }
  2610. else if (IsEqualGUID(CGID_MenuBand, *pguidCmdGroup))
  2611. {
  2612. switch (nCmdID)
  2613. {
  2614. case MBANDCID_GETFONTS:
  2615. // BUGBUG (lamadio): can I remove this?
  2616. if (pvarargOut)
  2617. {
  2618. if (EVAL(_pmbm))
  2619. {
  2620. // BUGBUG (lamadio): this is not marshal-safe.
  2621. pvarargOut->vt = VT_UNKNOWN;
  2622. _pmbm->QueryInterface(IID_IUnknown, (void**)&pvarargOut->punkVal);
  2623. return S_OK;
  2624. }
  2625. else
  2626. return E_FAIL;
  2627. }
  2628. else
  2629. return E_INVALIDARG;
  2630. break;
  2631. case MBANDCID_SETFONTS:
  2632. if (pvarargIn && VT_UNKNOWN == pvarargIn->vt && pvarargIn->punkVal)
  2633. {
  2634. // BUGBUG (lamadio): this is not marshal-safe.
  2635. ATOMICRELEASE(_pmbm);
  2636. pvarargIn->punkVal->QueryInterface(CLSID_MenuBandMetrics, (void**)&_pmbm);
  2637. _fForceButtonUpdate = TRUE;
  2638. // Force Update of Toolbars:
  2639. if (_pmtbMenu)
  2640. _pmtbMenu->SetMenuBandMetrics(_pmbm);
  2641. if (_pmtbShellFolder)
  2642. _pmtbShellFolder->SetMenuBandMetrics(_pmbm);
  2643. }
  2644. else
  2645. return E_INVALIDARG;
  2646. break;
  2647. case MBANDCID_RECAPTURE:
  2648. GetMessageFilter()->RetakeCapture();
  2649. break;
  2650. case MBANDCID_NOTAREALSITE:
  2651. _fParentIsNotASite = BOOLIFY(nCmdExecOpt);
  2652. break;
  2653. case MBANDCID_ITEMDROPPED:
  2654. {
  2655. _fDragEntered = FALSE;
  2656. HWND hwndWorker = _pmbState->GetWorkerWindow(NULL);
  2657. if (hwndWorker && !HasWindowTopmostOwner(hwndWorker))
  2658. SetWindowPos(hwndWorker, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE);
  2659. }
  2660. break;
  2661. case MBANDCID_DRAGENTER:
  2662. _fDragEntered = TRUE;
  2663. break;
  2664. case MBANDCID_DRAGLEAVE:
  2665. _fDragEntered = FALSE;
  2666. break;
  2667. case MBANDCID_SELECTITEM:
  2668. {
  2669. int iPos = nCmdExecOpt;
  2670. // If they are passing vararg in, then this is an ID, not a position
  2671. if (pvarargIn && pvarargIn->vt == VT_I4)
  2672. {
  2673. _nItemNew = pvarargIn->lVal;
  2674. _fPopupNewItemOnShow = TRUE;
  2675. }
  2676. // This can be called outside of a created band.
  2677. if (_pmtbTop)
  2678. {
  2679. if (iPos == MBSI_NONE)
  2680. {
  2681. SetTracked(NULL);
  2682. }
  2683. else
  2684. {
  2685. CMenuToolbarBase* pmtb = (iPos == MBSI_LASTITEM) ? _pmtbBottom : _pmtbTop;
  2686. ASSERT(pmtb);
  2687. SetTracked(pmtb);
  2688. _pmtbTracked->SetHotItem(1, iPos, -1, HICF_OTHER);
  2689. // If the new hot item is in the obscured part of the menu, then the
  2690. // above call will have reentered & nulled out _pmtbTracked (since we
  2691. // drop down the chevron menu if the new hot item is obscured). So we
  2692. // need to revalidate _pmtbTracked.
  2693. if (!_pmtbTracked)
  2694. break;
  2695. NotifyWinEvent(EVENT_OBJECT_FOCUS, _pmtbTracked->_hwndMB, OBJID_CLIENT,
  2696. GetIndexFromChild(TRUE, iPos));
  2697. }
  2698. }
  2699. }
  2700. break;
  2701. case MBANDCID_KEYBOARD:
  2702. // If we've been executed because of a keyboard, then set the global
  2703. // state to reflect that. This is sent by MenuBar when it's ::Popup
  2704. // member is called with the flag MPPF_KEYBOARD. This is for start menu.
  2705. if (_pmbState)
  2706. _pmbState->SetKeyboardCue(TRUE);
  2707. break;
  2708. case MBANDCID_POPUPITEM:
  2709. if (pvarargIn && VT_I4 == pvarargIn->vt)
  2710. {
  2711. // we don't want to popup a sub menu if we're tracking a context menu...
  2712. if ( !((_pmtbBottom && _pmtbBottom->v_TrackingSubContextMenu()) ||
  2713. (_pmtbTop && _pmtbTop->v_TrackingSubContextMenu())))
  2714. {
  2715. // No tracked item? Well default to the top (For the chevron menu)
  2716. if (!_pmtbTracked)
  2717. {
  2718. SetTracked(_pmtbTop);
  2719. }
  2720. // We don't want to display the sub menu if we're not shown.
  2721. // We do this because we could have been dismissed before the message
  2722. // was routed.
  2723. if (_fShow && _pmtbTracked)
  2724. {
  2725. int iItem;
  2726. int iPos;
  2727. if (nCmdExecOpt & MBPUI_ITEMBYPOS)
  2728. {
  2729. iPos = pvarargIn->lVal;
  2730. iItem = GetButtonCmd(_pmtbTracked->_hwndMB, pvarargIn->lVal);
  2731. }
  2732. else
  2733. {
  2734. iPos = ToolBar_CommandToIndex(_pmtbTracked->_hwndMB, pvarargIn->lVal);
  2735. iItem = pvarargIn->lVal;
  2736. }
  2737. if (nCmdExecOpt & MBPUI_SETITEM)
  2738. {
  2739. // Set the hot item explicitly since this can be
  2740. // invoked by the keyboard and the mouse could be
  2741. // anywhere.
  2742. _pmtbTracked->SetHotItem(1, iPos, -1, HICF_OTHER);
  2743. // If the new hot item is in the obscured part of the menu, then the
  2744. // above call will have reentered & nulled out _pmtbTracked (since we
  2745. // drop down the chevron menu if the new hot item is obscured). So we
  2746. // need to revalidate _pmtbTracked.
  2747. if (!_pmtbTracked)
  2748. break;
  2749. NotifyWinEvent(EVENT_OBJECT_FOCUS, _pmtbTracked->_hwndMB, OBJID_CLIENT,
  2750. GetIndexFromChild(TRUE, iPos) );
  2751. }
  2752. _pmtbTracked->PopupHelper(iItem, nCmdExecOpt & MBPUI_INITIALSELECT);
  2753. }
  2754. }
  2755. }
  2756. break;
  2757. case MBANDCID_ISVERTICAL:
  2758. if (pvarargOut)
  2759. {
  2760. pvarargOut->vt = VT_BOOL;
  2761. pvarargOut->boolVal = (_fVertical)? VARIANT_TRUE: VARIANT_FALSE;
  2762. }
  2763. break;
  2764. case MBANDCID_SETICONSIZE:
  2765. ASSERT(nCmdExecOpt == ISFBVIEWMODE_SMALLICONS ||
  2766. nCmdExecOpt == ISFBVIEWMODE_LARGEICONS);
  2767. _uIconSize = nCmdExecOpt;
  2768. if (_pmtbTop)
  2769. _pmtbTop->v_UpdateIconSize(nCmdExecOpt, TRUE);
  2770. if (_pmtbBottom)
  2771. _pmtbBottom->v_UpdateIconSize(nCmdExecOpt, TRUE);
  2772. break;
  2773. case MBANDCID_SETSTATEOBJECT:
  2774. if (pvarargIn && VT_INT_PTR == pvarargIn->vt)
  2775. {
  2776. _pmbState = (CMenuBandState*)pvarargIn->byref;
  2777. }
  2778. break;
  2779. case MBANDCID_ISINSUBMENU:
  2780. if (_fInSubMenu || (_pmtbTracked && _pmtbTracked->v_TrackingSubContextMenu()))
  2781. return S_OK;
  2782. else
  2783. return S_FALSE;
  2784. break;
  2785. case MBANDCID_ISTRACKING:
  2786. if (_pmtbTracked && _pmtbTracked->v_TrackingSubContextMenu())
  2787. return S_OK;
  2788. else
  2789. return S_FALSE;
  2790. break;
  2791. case MBANDCID_REPOSITION:
  2792. // Don't reposition unless we're shown (Avoids artifacts onscreen of a bad positioning)
  2793. if (_fShow)
  2794. {
  2795. // Don't forget to reposition US!!!
  2796. IMenuPopup* pmdb;
  2797. DWORD dwFlags = MPPF_REPOSITION | MPPF_NOANIMATE;
  2798. // If we should force a reposition. This is so that we get
  2799. // the trickle down reposition so things overlap correctly
  2800. if (nCmdExecOpt)
  2801. dwFlags |= MPPF_FORCEZORDER;
  2802. if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SMenuPopup, IID_IMenuPopup, (void**)&pmdb)))
  2803. {
  2804. pmdb->Popup(NULL, NULL, dwFlags);
  2805. pmdb->Release();
  2806. }
  2807. // Reposition the Tracked sub menu based on the current popped up item
  2808. // since this pane has now moved
  2809. // If they have a sub menu, tell them to reposition as well.
  2810. if (_fInSubMenu && _pmtbTracked)
  2811. {
  2812. IUnknown_QueryServiceExec(_pmpSubMenu, SID_SMenuBandChild,
  2813. &CGID_MenuBand, MBANDCID_REPOSITION, nCmdExecOpt, NULL, NULL);
  2814. }
  2815. _pmbState->PutTipOnTop();
  2816. }
  2817. break;
  2818. case MBANDCID_REFRESH:
  2819. InvalidateItem(NULL, SMINV_REFRESH);
  2820. break;
  2821. case MBANDCID_EXPAND:
  2822. if (_pmtbShellFolder)
  2823. _pmtbShellFolder->Expand(TRUE);
  2824. if (_pmtbMenu)
  2825. _pmtbMenu->Expand(TRUE);
  2826. break;
  2827. case MBANDCID_DRAGCANCEL:
  2828. // If one of the Sub bands in the menu heirarchy has the drag
  2829. // (Either because of Drag enter or because of the drop) then
  2830. // we do not want to cancel.
  2831. if (!_pmbState->HasDrag())
  2832. _CancelMode(MPOS_FULLCANCEL);
  2833. break;
  2834. case MBANDCID_EXECUTE:
  2835. ASSERT(pvarargIn != NULL);
  2836. if (_pmtbTop && _pmtbTop->IsWindowOwner((HWND)pvarargIn->ullVal) == S_OK)
  2837. _pmtbTop->v_ExecItem((int)nCmdExecOpt);
  2838. else if (_pmtbBottom && _pmtbBottom->IsWindowOwner((HWND)pvarargIn->ullVal) == S_OK)
  2839. _pmtbBottom->v_ExecItem((int)nCmdExecOpt);
  2840. _SiteOnSelect(MPOS_EXECUTE);
  2841. break;
  2842. }
  2843. // Don't bother passing CGID_MenuBand commands to CToolBand
  2844. return S_OK;
  2845. }
  2846. return SUPERCLASS::Exec(pguidCmdGroup, nCmdID, nCmdExecOpt, pvarargIn, pvarargOut);
  2847. }
  2848. /*----------------------------------------------------------
  2849. Purpose: IDockingWindow::CloseDW method.
  2850. */
  2851. STDMETHODIMP CMenuBand::CloseDW(DWORD dw)
  2852. {
  2853. // We don't want to destroy the band if it's cached.
  2854. // That means it's the caller's respocibility to Unset this bit and call CloseDW explicitly
  2855. if (_dwFlags & SMINIT_CACHED)
  2856. return S_OK;
  2857. // Since we're blowing away all of the menus,
  2858. // Top and bottom are invalid
  2859. _pmtbTracked = _pmtbTop = _pmtbBottom = NULL;
  2860. if (_pmtbMenu)
  2861. {
  2862. _pmtbMenu->v_Close();
  2863. }
  2864. if (_pmtbShellFolder)
  2865. {
  2866. _pmtbShellFolder->v_Close();
  2867. }
  2868. if (_pmpSubMenu)
  2869. {
  2870. _fInSubMenu = FALSE;
  2871. IUnknown_SetSite(_pmpSubMenu, NULL);
  2872. ATOMICRELEASE(_pmpSubMenu);
  2873. }
  2874. // We don't want our base class to blow this window away. It belongs to someone else.
  2875. _hwnd = NULL;
  2876. _fClosing = TRUE;
  2877. return SUPERCLASS::CloseDW(dw);
  2878. }
  2879. /*----------------------------------------------------------
  2880. Purpose: IDockingWindow::ShowDW method
  2881. Notes:
  2882. for the start menu (non-browser) case, we bracket* the top-level popup
  2883. operation w/ a LockSetForegroundWindow so that another app can't steal
  2884. the foreground and collapse our menu. (nt5:172813: don't do it for
  2885. the browser case since a) we don't want to and b) ShowDW(FALSE) isn't
  2886. called until exit the browser so we'd be permanently locked!)
  2887. */
  2888. STDMETHODIMP CMenuBand::ShowDW(BOOL fShow)
  2889. {
  2890. CMBMsgFilter* pmf = GetMessageFilter();
  2891. // Prevent rentrancy when we're already shown.
  2892. ASSERT((int)_fShow == BOOLIFY(_fShow));
  2893. if ((int)_fShow == BOOLIFY(fShow))
  2894. return NOERROR;
  2895. HRESULT hres = SUPERCLASS::ShowDW(fShow);
  2896. if (!fShow)
  2897. {
  2898. _fShow = FALSE;
  2899. if (_fTopLevel)
  2900. {
  2901. if (_fVertical)
  2902. {
  2903. // (_fTopLevel && _fVertical) => start menu
  2904. MyLockSetForegroundWindow(FALSE);
  2905. }
  2906. else if (_dwFlags & SMINIT_USEMESSAGEFILTER)
  2907. {
  2908. pmf->SetHook(FALSE, TRUE);
  2909. pmf->SetTopMost(this);
  2910. }
  2911. }
  2912. if ((_fTopLevel || _fParentIsHorizontal) && _pmbState)
  2913. {
  2914. // Reset to not have the drag when we collapse.
  2915. _pmbState->HasDrag(FALSE);
  2916. _pmbState->SetExpand(FALSE);
  2917. _pmbState->SetUEMState(0);
  2918. }
  2919. _CallCB(SMC_EXITMENU);
  2920. }
  2921. else
  2922. {
  2923. _CallCB(SMC_INITMENU);
  2924. _fClosing = FALSE;
  2925. _fShow = TRUE;
  2926. _GetFontMetrics();
  2927. if (_fTopLevel)
  2928. {
  2929. // We set the context here so that the ReEngage causes the message filter
  2930. // to start taking messages on a TopLevel::Show. This prevents a problem
  2931. // where tracking doesn't work when switching between Favorites and Start Menu
  2932. _pmbState->SetContext(this);
  2933. pmf->SetContext(this, TRUE);
  2934. pmf->ReEngage(_pmbState->GetContext());
  2935. if (_hwndMenuOwner && _fVertical)
  2936. SetForegroundWindow(_hwndMenuOwner);
  2937. if (_fVertical)
  2938. {
  2939. // (_fTopLevel && _fVertical) => start menu
  2940. MyLockSetForegroundWindow(TRUE);
  2941. }
  2942. else if (_dwFlags & SMINIT_USEMESSAGEFILTER)
  2943. {
  2944. pmf->SetHook(TRUE, TRUE);
  2945. pmf->SetTopMost(this);
  2946. }
  2947. _pmbState->CreateFader(_hwndParent);
  2948. }
  2949. }
  2950. if (_pmtbShellFolder)
  2951. _pmtbShellFolder->v_Show(_fShow, _fForceButtonUpdate);
  2952. // Menu needs to be last so that it can update the seperator.
  2953. if (_pmtbMenu)
  2954. _pmtbMenu->v_Show(_fShow, _fForceButtonUpdate);
  2955. if (_fPopupNewItemOnShow)
  2956. {
  2957. HWND hwnd = _pmbState ? _pmbState->GetSubclassedHWND() : NULL;
  2958. if (hwnd || _pmtbMenu)
  2959. {
  2960. PostMessage(hwnd ? hwnd : _pmtbMenu->_hwndMB, g_nMBPopupOpen,
  2961. _nItemNew, MAKELPARAM(TRUE, TRUE));
  2962. }
  2963. _fPopupNewItemOnShow = FALSE;
  2964. }
  2965. _fForceButtonUpdate = FALSE;
  2966. return hres;
  2967. }
  2968. void CMenuBand::_GetFontMetrics()
  2969. {
  2970. if (_pmbm)
  2971. return;
  2972. if (_fTopLevel)
  2973. {
  2974. ASSERT(_pmtbTop);
  2975. // We need only 1 HWND
  2976. _pmbm = new CMenuBandMetrics(_pmtbTop->_hwndMB);
  2977. }
  2978. else
  2979. {
  2980. AssertMsg(0, TEXT("When this menuband was created, someone forgot to set the metrics"));
  2981. IOleCommandTarget *poct;
  2982. HRESULT hres = IUnknown_QueryService(_punkSite, SID_SMenuBandTop, IID_IOleCommandTarget, (LPVOID *)&poct);
  2983. if (SUCCEEDED(hres))
  2984. {
  2985. VARIANTARG vargOut;
  2986. // Ask the toplevel menuband for their font info
  2987. if (SUCCEEDED(poct->Exec(&CGID_MenuBand, MBANDCID_GETFONTS, 0, NULL, &vargOut)))
  2988. {
  2989. if (vargOut.vt == VT_UNKNOWN && vargOut.punkVal)
  2990. {
  2991. vargOut.punkVal->QueryInterface(CLSID_MenuBandMetrics, (void**)&_pmbm);
  2992. vargOut.punkVal->Release();
  2993. }
  2994. }
  2995. poct->Release();
  2996. }
  2997. }
  2998. }
  2999. /*----------------------------------------------------------
  3000. Purpose: IMenuPopup::OnSelect method
  3001. This allows the child menubar to tell us when and how
  3002. to bail out of the menu.
  3003. */
  3004. STDMETHODIMP CMenuBand::OnSelect(DWORD dwType)
  3005. {
  3006. int iIndex;
  3007. switch (dwType)
  3008. {
  3009. case MPOS_CHILDTRACKING:
  3010. // this means that our child did get tracked over it, so we should abort any timeout to destroy it
  3011. if (_pmtbTracked)
  3012. {
  3013. HWND hwnd = _pmtbTracked->_hwndMB;
  3014. if (_nItemTimer)
  3015. {
  3016. _pmtbTracked->KillPopupTimer();
  3017. // Use the command id of the SubMenu that we actually have cascaded out.
  3018. iIndex = ToolBar_CommandToIndex(hwnd, _nItemSubMenu);
  3019. ToolBar_SetHotItem(hwnd, iIndex);
  3020. }
  3021. KillTimer(hwnd, MBTIMER_DRAGOVER);
  3022. _SiteOnSelect(dwType);
  3023. }
  3024. break;
  3025. case MPOS_SELECTLEFT:
  3026. if (!_fVertical)
  3027. _OnSelectArrow(-1);
  3028. else
  3029. {
  3030. // Cancel the child submenu. Hitting left arrow is like
  3031. // hitting escape.
  3032. _SubMenuOnSelect(MPOS_CANCELLEVEL);
  3033. }
  3034. break;
  3035. case MPOS_SELECTRIGHT:
  3036. if (!_fVertical)
  3037. _OnSelectArrow(1);
  3038. else
  3039. {
  3040. // The right arrow gets propagated up to the top, so
  3041. // a fully cascaded menu will be cancelled and the
  3042. // top level menuband will move to the next menu to the
  3043. // right.
  3044. _SiteOnSelect(dwType);
  3045. }
  3046. break;
  3047. case MPOS_CANCELLEVEL:
  3048. // Forward onto submenu
  3049. _SubMenuOnSelect(dwType);
  3050. break;
  3051. case MPOS_FULLCANCEL:
  3052. case MPOS_EXECUTE:
  3053. DEBUG_CODE( TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): CMenuToolbarBase received %s",
  3054. DBG_THIS, MPOS_FULLCANCEL == dwType ? TEXT("MPOS_FULLCANCEL") : TEXT("MPOS_EXECUTE")); )
  3055. _CancelMode(dwType);
  3056. break;
  3057. }
  3058. return S_OK;
  3059. }
  3060. void CMenuBand::SetTrackMenuPopup(IUnknown* punk)
  3061. {
  3062. ATOMICRELEASE(_pmpTrackPopup);
  3063. if (punk)
  3064. {
  3065. punk->QueryInterface(IID_IMenuPopup, (void**)&_pmpTrackPopup);
  3066. }
  3067. }
  3068. /*----------------------------------------------------------
  3069. Purpose: Set the currently tracked toolbar. Only one
  3070. of the toolbars can have the "activation" at one time.
  3071. */
  3072. BOOL CMenuBand::SetTracked(CMenuToolbarBase* pmtb)
  3073. {
  3074. if (pmtb == _pmtbTracked)
  3075. return FALSE;
  3076. if (_pmtbTracked)
  3077. {
  3078. // Tell the existing toolbar we're leaving him
  3079. SendMessage(_pmtbTracked->_hwndMB, TB_SETHOTITEM2, -1, HICF_LEAVING);
  3080. }
  3081. _pmtbTracked = pmtb;
  3082. if (_pmtbTracked)
  3083. {
  3084. // This is for accessibility.
  3085. HWND hwnd = _pmtbTracked->_hwndMB;
  3086. int iHotItem = ToolBar_GetHotItem(hwnd);
  3087. if (iHotItem >= 0)
  3088. {
  3089. // Toolbar Items are 0 based, Accessibility apps require 1 based
  3090. NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT,
  3091. GetIndexFromChild(_pmtbTracked->GetFlags() & SMSET_TOP, iHotItem));
  3092. }
  3093. }
  3094. return TRUE;
  3095. }
  3096. void CMenuBand::_OnSelectArrow(int iDir)
  3097. {
  3098. _fKeyboardSelected = TRUE;
  3099. int iIndex;
  3100. if (!_pmtbTracked)
  3101. {
  3102. if (iDir < 0)
  3103. {
  3104. SetTracked(_pmtbBottom);
  3105. iIndex = ToolBar_ButtonCount(_pmtbTracked->_hwndMB) - 1;
  3106. }
  3107. else
  3108. {
  3109. SetTracked(_pmtbTop);
  3110. iIndex = 0;
  3111. }
  3112. // This can happen when going to the chevron.
  3113. if (_pmtbTracked)
  3114. _pmtbTracked->SetHotItem(iDir, iIndex, -1, HICF_ARROWKEYS);
  3115. }
  3116. else
  3117. {
  3118. HWND hwnd = _pmtbTracked->_hwndMB;
  3119. iIndex = ToolBar_GetHotItem(hwnd);
  3120. int iCount = ToolBar_ButtonCount(hwnd);
  3121. // Set the hot item explicitly since this is invoked by the
  3122. // keyboard and the mouse could be anywhere.
  3123. // cycle iIndex by iDir (add extra iCount to avoid negative number problems
  3124. iIndex = (iIndex + iCount + iDir) % iCount;
  3125. ToolBar_SetHotItem(hwnd, iIndex);
  3126. }
  3127. if (_pmtbTracked)
  3128. {
  3129. NotifyWinEvent(EVENT_OBJECT_FOCUS, _pmtbTracked->_hwndMB, OBJID_CLIENT,
  3130. GetIndexFromChild(_pmtbTracked->GetFlags() & SMSET_TOP, iIndex));
  3131. }
  3132. _fKeyboardSelected = FALSE;
  3133. }
  3134. void CMenuBand::_CancelMode(DWORD dwType)
  3135. {
  3136. // Tell the hosting site to cancel this level
  3137. if (_fParentIsNotASite)
  3138. UIActivateIO(FALSE, NULL);
  3139. else
  3140. _SiteOnSelect(dwType);
  3141. }
  3142. HRESULT CMenuBand::OnPosRectChangeDB (LPRECT prc)
  3143. {
  3144. // We want the HMENU portion to ALWAYS have the maximum allowed.
  3145. RECT rcMenu = {0};
  3146. SIZE sizeMenu = {0};
  3147. SIZE sizeSF = {0};
  3148. SIZE sizeMax;
  3149. if (_pmtbMenu)
  3150. _pmtbMenu->GetSize(&sizeMenu);
  3151. if (_pmtbShellFolder)
  3152. _pmtbShellFolder->GetSize(&sizeSF);
  3153. if (sizeSF.cx > sizeMenu.cx)
  3154. sizeMax = sizeSF;
  3155. else
  3156. sizeMax = sizeMenu;
  3157. if (_pmtbMenu)
  3158. {
  3159. if (_pmtbMenu->GetFlags() & SMSET_TOP)
  3160. {
  3161. rcMenu.bottom = sizeMenu.cy;
  3162. rcMenu.right = prc->right;
  3163. }
  3164. else
  3165. {
  3166. rcMenu.bottom = prc->bottom;
  3167. rcMenu.right = prc->right;
  3168. rcMenu.top = prc->bottom - sizeMenu.cy;
  3169. rcMenu.left = 0;
  3170. }
  3171. _pmtbMenu->SetWindowPos(&sizeMax, &rcMenu, 0);
  3172. }
  3173. if (_pmtbShellFolder)
  3174. {
  3175. RECT rc = *prc;
  3176. if (_pmtbShellFolder->GetFlags() & SMSET_TOP)
  3177. {
  3178. rc.bottom = prc->bottom - RECTHEIGHT(rcMenu) + 1;
  3179. }
  3180. else
  3181. {
  3182. rc.top = prc->top + RECTHEIGHT(rcMenu);
  3183. }
  3184. _pmtbShellFolder->SetWindowPos(&sizeMax, &rc, 0);
  3185. }
  3186. return NOERROR;
  3187. }
  3188. HRESULT IUnknown_OnSelect(IUnknown* punk, DWORD dwType, REFGUID guid)
  3189. {
  3190. HRESULT hres;
  3191. IMenuPopup * pmp;
  3192. hres = IUnknown_QueryService(punk, guid, IID_IMenuPopup,
  3193. (LPVOID *)&pmp);
  3194. if (SUCCEEDED(hres))
  3195. {
  3196. pmp->OnSelect(dwType);
  3197. pmp->Release();
  3198. }
  3199. return hres;
  3200. }
  3201. HRESULT CMenuBand::_SiteOnSelect(DWORD dwType)
  3202. {
  3203. return IUnknown_OnSelect(_punkSite, dwType, SID_SMenuPopup);
  3204. }
  3205. HRESULT CMenuBand::_SubMenuOnSelect(DWORD dwType)
  3206. {
  3207. IMenuPopup* pmp = _pmpSubMenu;
  3208. if (_pmpTrackPopup)
  3209. pmp = _pmpTrackPopup;
  3210. return IUnknown_OnSelect(pmp, dwType, SID_SMenuPopup);
  3211. }
  3212. HRESULT CMenuBand::GetTop(CMenuToolbarBase** ppmtbTop)
  3213. {
  3214. *ppmtbTop = _pmtbTop;
  3215. if (*ppmtbTop)
  3216. {
  3217. (*ppmtbTop)->AddRef();
  3218. return NOERROR;
  3219. }
  3220. return E_FAIL;
  3221. }
  3222. HRESULT CMenuBand::GetBottom(CMenuToolbarBase** ppmtbBottom)
  3223. {
  3224. *ppmtbBottom = _pmtbBottom;
  3225. if (*ppmtbBottom)
  3226. {
  3227. (*ppmtbBottom)->AddRef();
  3228. return NOERROR;
  3229. }
  3230. return E_FAIL;
  3231. }
  3232. HRESULT CMenuBand::GetTracked(CMenuToolbarBase** ppmtbTracked)
  3233. {
  3234. *ppmtbTracked = _pmtbTracked;
  3235. if (*ppmtbTracked)
  3236. {
  3237. (*ppmtbTracked)->AddRef();
  3238. return NOERROR;
  3239. }
  3240. return E_FAIL;
  3241. }
  3242. HRESULT CMenuBand::GetParentSite(REFIID riid, void** ppvObj)
  3243. {
  3244. if (_punkSite)
  3245. return _punkSite->QueryInterface(riid, ppvObj);
  3246. return E_FAIL;
  3247. }
  3248. HRESULT CMenuBand::GetState(BOOL* pfVertical, BOOL* pfOpen)
  3249. {
  3250. *pfVertical = _fVertical;
  3251. *pfOpen = _fMenuMode;
  3252. return NOERROR;
  3253. }
  3254. HRESULT CMenuBand::DoDefaultAction(VARIANT* pvarChild)
  3255. {
  3256. if (pvarChild->lVal != CHILDID_SELF)
  3257. {
  3258. CMenuToolbarBase* pmtb = (pvarChild->lVal & TOOLBAR_MASK)? _pmtbTop : _pmtbBottom;
  3259. int idCmd = GetButtonCmd(pmtb->_hwndMB, (pvarChild->lVal & ~TOOLBAR_MASK) - 1);
  3260. SendMessage(pmtb->_hwndMB, TB_SETHOTITEM2, idCmd, HICF_OTHER | HICF_TOGGLEDROPDOWN);
  3261. }
  3262. else
  3263. {
  3264. _CancelMode(MPOS_CANCELLEVEL);
  3265. }
  3266. return NOERROR;
  3267. }
  3268. HRESULT CMenuBand::GetSubMenu(VARIANT* pvarChild, REFIID riid, void** ppvObj)
  3269. {
  3270. HRESULT hres = E_FAIL;
  3271. CMenuToolbarBase* pmtb = (pvarChild->lVal & TOOLBAR_MASK)? _pmtbTop : _pmtbBottom;
  3272. int idCmd = GetButtonCmd(pmtb->_hwndMB, (pvarChild->lVal & ~TOOLBAR_MASK) - 1);
  3273. *ppvObj = NULL;
  3274. if (idCmd != -1 && pmtb)
  3275. {
  3276. hres = pmtb->v_GetSubMenu(idCmd, &SID_SMenuBandChild, riid, ppvObj);
  3277. }
  3278. return hres;
  3279. }
  3280. HRESULT CMenuBand::IsEmpty()
  3281. {
  3282. BOOL fReturn = TRUE;
  3283. if (_pmtbShellFolder)
  3284. fReturn = _pmtbShellFolder->IsEmpty();
  3285. if (fReturn && _pmtbMenu)
  3286. fReturn = _pmtbMenu->IsEmpty();
  3287. return fReturn? S_OK : S_FALSE;
  3288. }
  3289. /*----------------------------------------------------------
  3290. Purpose: IShellMenu2::GetSubMenu method
  3291. */
  3292. HRESULT CMenuBand::GetSubMenu(UINT idCmd, REFIID riid, void** ppvObj)
  3293. {
  3294. return E_NOTIMPL;
  3295. }
  3296. HRESULT CMenuBand::SetToolbar(HWND hwnd, DWORD dwFlags)
  3297. {
  3298. HRESULT hr = E_OUTOFMEMORY;
  3299. CMenuToolbarBase *pmtb = ToolbarMenu_Create(hwnd);
  3300. if (pmtb)
  3301. {
  3302. hr = SetMenuToolbar(SAFECAST(pmtb, IWinEventHandler*), dwFlags);
  3303. // DONT release! The menus break com identity rules because of a foobar when they were
  3304. // initially designed.
  3305. }
  3306. return hr;
  3307. }
  3308. HRESULT CMenuBand::SetMinWidth(int cxMenu)
  3309. {
  3310. return E_NOTIMPL;
  3311. }
  3312. HRESULT CMenuBand::SetNoBorder(BOOL fNoBorder)
  3313. {
  3314. return E_NOTIMPL;
  3315. }
  3316. HRESULT CMenuBand::SetTheme(LPCWSTR pszTheme)
  3317. {
  3318. return E_NOTIMPL;
  3319. }
  3320. //----------------------------------------------------------------------------
  3321. // CMenuBandMetrics
  3322. //
  3323. //----------------------------------------------------------------------------
  3324. COLORREF GetDemotedColor()
  3325. {
  3326. WORD iHue;
  3327. WORD iLum;
  3328. WORD iSat;
  3329. COLORREF clr = (COLORREF)GetSysColor(COLOR_MENU);
  3330. HDC hdc = GetDC(NULL);
  3331. // Office CommandBars use this same algorithm for their "intellimenus"
  3332. // colors. We prefer to call them "expando menus"...
  3333. if (hdc)
  3334. {
  3335. int cColors = GetDeviceCaps(hdc, BITSPIXEL);
  3336. ReleaseDC(NULL, hdc);
  3337. switch (cColors)
  3338. {
  3339. case 4: // 16 Colors
  3340. case 8: // 256 Colors
  3341. // Default to using Button Face
  3342. break;
  3343. default: // 256+ colors
  3344. ColorRGBToHLS(clr, &iHue, &iLum, &iSat);
  3345. if (iLum > 220)
  3346. iLum -= 20;
  3347. else if (iLum <= 20)
  3348. iLum += 40;
  3349. else
  3350. iLum += 20;
  3351. clr = ColorHLSToRGB(iHue, iLum, iSat);
  3352. break;
  3353. }
  3354. }
  3355. return clr;
  3356. }
  3357. ULONG CMenuBandMetrics::AddRef()
  3358. {
  3359. return ++_cRef;
  3360. }
  3361. ULONG CMenuBandMetrics::Release()
  3362. {
  3363. ASSERT(_cRef > 0);
  3364. if (--_cRef > 0)
  3365. return _cRef;
  3366. delete this;
  3367. return 0;
  3368. }
  3369. HRESULT CMenuBandMetrics::QueryInterface(REFIID riid, LPVOID * ppvObj)
  3370. {
  3371. if (IsEqualIID(riid, IID_IUnknown))
  3372. {
  3373. *ppvObj = SAFECAST(this, IUnknown*);
  3374. }
  3375. else if (IsEqualIID(riid, CLSID_MenuBandMetrics))
  3376. {
  3377. *ppvObj = this;
  3378. }
  3379. else
  3380. {
  3381. *ppvObj = NULL;
  3382. return E_FAIL;
  3383. }
  3384. AddRef();
  3385. return S_OK;
  3386. }
  3387. CMenuBandMetrics::CMenuBandMetrics(HWND hwnd)
  3388. : _cRef(1)
  3389. {
  3390. _SetMenuFont();
  3391. _SetArrowFont(hwnd);
  3392. _SetChevronFont(hwnd);
  3393. #ifndef DRAWEDGE
  3394. _SetPaintMetrics(hwnd);
  3395. #endif
  3396. _SetTextBrush(hwnd);
  3397. _SetColors();
  3398. HIGHCONTRAST hc = {sizeof(HIGHCONTRAST)};
  3399. if (SystemParametersInfoA(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0))
  3400. {
  3401. _fHighContrastMode = (HCF_HIGHCONTRASTON & hc.dwFlags);
  3402. }
  3403. }
  3404. CMenuBandMetrics::~CMenuBandMetrics()
  3405. {
  3406. if (_hFontMenu)
  3407. DeleteObject(_hFontMenu);
  3408. if (_hFontArrow)
  3409. DeleteObject(_hFontArrow);
  3410. if (_hFontChevron)
  3411. DeleteObject(_hFontChevron);
  3412. if (_hbrText)
  3413. DeleteObject(_hbrText);
  3414. #ifndef DRAWEDGE
  3415. if (_hPenHighlight)
  3416. DeleteObject(_hPenHighlight);
  3417. if (_hPenShadow)
  3418. DeleteObject(_hPenShadow);
  3419. #endif
  3420. }
  3421. HFONT CMenuBandMetrics::_CalcFont(HWND hwnd, LPCTSTR pszFont, DWORD dwCharSet, TCHAR ch, int* pcx,
  3422. int* pcy, int* pcxMargin, int iOrientation, int iWeight)
  3423. {
  3424. ASSERT(hwnd);
  3425. HFONT hFontOld, hFontRet = NULL;
  3426. TEXTMETRIC tm;
  3427. RECT rect={0};
  3428. int cx = 0, cy = 0, cxM = 0;
  3429. HDC hdc = GetDC(hwnd);
  3430. if (hdc)
  3431. {
  3432. hFontOld = (HFONT)SelectObject(hdc, _hFontMenu);
  3433. GetTextMetrics(hdc, &tm);
  3434. // Set the font height (based on original USER code)
  3435. cy = ((tm.tmHeight + tm.tmExternalLeading + GetSystemMetrics(SM_CYBORDER)) & 0xFFFE) - 1;
  3436. // Use the menu font's avg character width as the margin.
  3437. cxM = tm.tmAveCharWidth; // Not exactly how USER does it, but close
  3438. // Shlwapi wraps the ansi/unicode behavior.
  3439. hFontRet = CreateFontWrap(cy, 0, iOrientation, 0, iWeight, 0, 0, 0, dwCharSet, 0, 0, 0, 0, pszFont);
  3440. if (EVAL(hFontRet))
  3441. {
  3442. // Calc width of arrow using this new font
  3443. SelectObject(hdc, hFontRet);
  3444. if (EVAL(DrawText(hdc, &ch, 1, &rect, DT_CALCRECT | DT_SINGLELINE | DT_LEFT | DT_VCENTER)))
  3445. cx = rect.right;
  3446. else
  3447. cx = tm.tmMaxCharWidth;
  3448. }
  3449. else
  3450. {
  3451. cx = tm.tmMaxCharWidth;
  3452. }
  3453. SelectObject(hdc, hFontOld);
  3454. ReleaseDC(hwnd, hdc);
  3455. }
  3456. *pcx = cx;
  3457. *pcy = cy;
  3458. *pcxMargin = cxM;
  3459. return hFontRet;
  3460. }
  3461. /*
  3462. Call after _SetMenuFont()
  3463. */
  3464. void CMenuBandMetrics::_SetChevronFont(HWND hwnd)
  3465. {
  3466. ASSERT(!_hFontChevron);
  3467. TCHAR szPath[MAX_PATH];
  3468. NONCLIENTMETRICSA ncm;
  3469. ncm.cbSize = sizeof(ncm);
  3470. // Should only fail with bad parameters...
  3471. EVAL(SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0));
  3472. // Obtain the font's metrics
  3473. SHAnsiToTChar(ncm.lfMenuFont.lfFaceName, szPath, ARRAYSIZE(szPath));
  3474. _hFontChevron = _CalcFont(hwnd, szPath, DEFAULT_CHARSET, CH_MENUARROW, &_cxChevron, &_cyChevron,
  3475. &_cxChevron, -900, FW_NORMAL);
  3476. }
  3477. /*
  3478. Call after _SetMenuFont()
  3479. */
  3480. void CMenuBandMetrics::_SetArrowFont(HWND hwnd)
  3481. {
  3482. ASSERT(!_hFontArrow);
  3483. ASSERT(_hFontMenu);
  3484. // Obtain the font's metrics
  3485. if (_hFontMenu)
  3486. {
  3487. _hFontArrow = _CalcFont(hwnd, szfnMarlett, SYMBOL_CHARSET, CH_MENUARROW, &_cxArrow, &_cyArrow,
  3488. &_cxMargin, 0, FW_NORMAL);
  3489. }
  3490. else
  3491. {
  3492. _cxArrow = _cyArrow = _cxMargin = 0;
  3493. }
  3494. }
  3495. void CMenuBandMetrics::_SetMenuFont()
  3496. {
  3497. NONCLIENTMETRICSA ncm;
  3498. ncm.cbSize = sizeof(ncm);
  3499. // Should only fail with bad parameters...
  3500. EVAL(SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0));
  3501. // Should only fail under low mem conditions...
  3502. EVAL(_hFontMenu = CreateFontIndirectA(&ncm.lfMenuFont));
  3503. }
  3504. void CMenuBandMetrics::_SetColors()
  3505. {
  3506. _clrBackground = GetSysColor(COLOR_MENU);
  3507. _clrMenuText = GetSysColor(COLOR_MENUTEXT);
  3508. _clrDemoted = GetDemotedColor();
  3509. }
  3510. #ifndef DRAWEDGE
  3511. // Office "IntelliMenu" style
  3512. void CMenuBandMetrics::_SetPaintMetrics(HWND hwnd)
  3513. {
  3514. DWORD dwSysHighlight = GetSysColor(COLOR_3DHIGHLIGHT);
  3515. DWORD dwSysShadow = GetSysColor(COLOR_3DSHADOW);
  3516. _hPenHighlight = CreatePen(PS_SOLID, 1, dwSysHighlight);
  3517. _hPenShadow = CreatePen(PS_SOLID, 1, dwSysShadow);
  3518. }
  3519. #endif
  3520. void CMenuBandMetrics::_SetTextBrush(HWND hwnd)
  3521. {
  3522. _hbrText = CreateSolidBrush(GetSysColor(COLOR_MENUTEXT));
  3523. }
  3524. CMenuBandState::CMenuBandState()
  3525. {
  3526. // We will default to NOT show the keyboard cues. This
  3527. // is overridden based on the User Settings.
  3528. _fKeyboardCue = FALSE;
  3529. }
  3530. CMenuBandState::~CMenuBandState()
  3531. {
  3532. ATOMICRELEASE(_ptFader);
  3533. ATOMICRELEASE(_pScheduler);
  3534. if (IsWindow(_hwndToolTip))
  3535. DestroyWindow(_hwndToolTip);
  3536. if (IsWindow(_hwndWorker)) // JANK : Fix for bug #101302
  3537. DestroyWindow(_hwndWorker);
  3538. }
  3539. int CMenuBandState::GetKeyboardCue()
  3540. {
  3541. return _fKeyboardCue;
  3542. }
  3543. void CMenuBandState::SetKeyboardCue(BOOL fKC)
  3544. {
  3545. _fKeyboardCue = fKC;
  3546. }
  3547. IShellTaskScheduler* CMenuBandState::GetScheduler()
  3548. {
  3549. if (!_pScheduler)
  3550. {
  3551. CoCreateInstance(CLSID_ShellTaskScheduler, NULL, CLSCTX_INPROC,
  3552. IID_IShellTaskScheduler, (void **) &_pScheduler);
  3553. }
  3554. if (_pScheduler)
  3555. _pScheduler->AddRef();
  3556. return _pScheduler;
  3557. }
  3558. BOOL CMenuBandState::FadeRect(PRECT prc, PFNFADESCREENRECT pfn, LPVOID pvParam)
  3559. {
  3560. BOOL fFade = FALSE;
  3561. SystemParametersInfo(SPI_GETSELECTIONFADE, 0, &fFade, 0);
  3562. if (g_bRunOnNT5 && _ptFader && fFade)
  3563. {
  3564. // Set the callback into the fader window. Do this each time, as the pane
  3565. // may have changed between fades
  3566. if (_ptFader->FadeRect(prc, pfn, pvParam))
  3567. {
  3568. IShellTaskScheduler* pScheduler = GetScheduler();
  3569. if (pScheduler)
  3570. {
  3571. fFade = pScheduler->AddTask(_ptFader, TASKID_Fader,
  3572. ITSAT_DEFAULT_LPARAM, ITSAT_DEFAULT_PRIORITY) == S_OK;
  3573. }
  3574. }
  3575. }
  3576. return fFade;
  3577. }
  3578. void CMenuBandState::CreateFader(HWND hwnd)
  3579. {
  3580. // We do this on first show, because in the Constuctor of CMenuBandState,
  3581. // the Window classes might not be registered yet (As is the case with start menu).
  3582. if (g_bRunOnNT5 && !_ptFader)
  3583. {
  3584. _ptFader = new CFadeTask();
  3585. }
  3586. }
  3587. void CMenuBandState::CenterOnButton(HWND hwndTB, BOOL fBalloon, int idCmd, LPTSTR pszTitle, LPTSTR pszTip)
  3588. {
  3589. // Balloon style holds presidence over info tips
  3590. if (_fTipShown && _fBalloonStyle)
  3591. return;
  3592. if (!_hwndToolTip)
  3593. {
  3594. _hwndToolTip = CreateWindow(TOOLTIPS_CLASS, NULL,
  3595. WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON,
  3596. CW_USEDEFAULT, CW_USEDEFAULT,
  3597. CW_USEDEFAULT, CW_USEDEFAULT,
  3598. NULL, NULL, g_hinst,
  3599. NULL);
  3600. if (_hwndToolTip)
  3601. {
  3602. // set the version so we can have non buggy mouse event forwarding
  3603. SendMessage(_hwndToolTip, CCM_SETVERSION, COMCTL32_VERSION, 0);
  3604. SendMessage(_hwndToolTip, TTM_SETMAXTIPWIDTH, 0, (LPARAM)300);
  3605. }
  3606. }
  3607. if (_hwndToolTip)
  3608. {
  3609. // Collapse the previous tip because we're going to be doing some stuff to it before displaying again.
  3610. SendMessage(_hwndToolTip, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)0);
  3611. // Balloon tips don't have a border, but regular tips do. Swap now...
  3612. SHSetWindowBits(_hwndToolTip, GWL_STYLE, TTS_BALLOON | WS_BORDER, (fBalloon) ? TTS_BALLOON : WS_BORDER);
  3613. if (pszTip && pszTip[0])
  3614. {
  3615. POINT ptCursor;
  3616. RECT rcItemScreen, rcItemTB;
  3617. TOOLINFO ti = {0};
  3618. ti.cbSize = SIZEOF(ti);
  3619. // This was pretty bad: I kept adding tools, but never deleteing them. Now we get rid of the current
  3620. // one then add the new one.
  3621. if (SendMessage(_hwndToolTip, TTM_ENUMTOOLS, 0, (LPARAM)&ti))
  3622. {
  3623. SendMessage(_hwndToolTip, TTM_DELTOOL, 0, (LPARAM)&ti); // Delete the current tool.
  3624. }
  3625. SendMessage(hwndTB, TB_GETRECT, idCmd, (LPARAM)&rcItemScreen);
  3626. rcItemTB = rcItemScreen;
  3627. MapWindowPoints(hwndTB, HWND_DESKTOP, (POINT*)&rcItemScreen, 2);
  3628. ti.cbSize = IsOS(OS_WHISTLERORGREATER)?SIZEOF(ti):TTTOOLINFOW_V2_SIZE;
  3629. ti.uFlags = TTF_IDISHWND | TTF_TRANSPARENT | (fBalloon? TTF_TRACK : 0);
  3630. // Check if the cursor is within the bounds of the hot item.
  3631. // If it is, then proceed as usual.
  3632. // If it isn't, then the hot item was activated via the keyboard, so the tooltip
  3633. // shouldn't be hung from the cursor. Stick it on the hot item instead.
  3634. // Set the vertical offset to use later.
  3635. // Notice the correction for the bottom: gsierra wanted it up a couple of pixels.
  3636. int nOffset;
  3637. nOffset = -3;
  3638. GetCursorPos(&ptCursor);
  3639. if (!PtInRect(&rcItemScreen, ptCursor))
  3640. {
  3641. ti.uFlags |= TTF_TRACK;
  3642. // The tooltip doesn't seem to pick up the hot item's rect right, so
  3643. // do it manually.
  3644. ti.uFlags ^= TTF_IDISHWND; // turn this off, so ti.rect is used.
  3645. ti.rect = rcItemTB;
  3646. // Force the tool tip to track along the bottom.
  3647. nOffset = 1;
  3648. }
  3649. SendMessage(_hwndToolTip, TTM_TRACKPOSITION, 0, MAKELONG((rcItemScreen.left + rcItemScreen.right)/2, rcItemScreen.bottom + nOffset));
  3650. ti.hwnd = hwndTB;
  3651. ti.uId = (UINT_PTR)hwndTB;
  3652. SendMessage(_hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
  3653. ti.lpszText = pszTip;
  3654. SendMessage(_hwndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
  3655. SendMessage(_hwndToolTip, TTM_SETTITLE, TTI_INFO, (LPARAM)pszTitle);
  3656. SetWindowPos(_hwndToolTip, HWND_TOPMOST,
  3657. 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
  3658. SendMessage(_hwndToolTip, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&ti);
  3659. _fTipShown = TRUE;
  3660. _fBalloonStyle = fBalloon;
  3661. }
  3662. }
  3663. }
  3664. void CMenuBandState::HideTooltip(BOOL fAllowBalloonCollapse)
  3665. {
  3666. if (_hwndToolTip && _fTipShown)
  3667. {
  3668. // Now we're going to latch the Balloon style. The rest of menuband blindly
  3669. // collapses the tooltip when selection changes. Here's where we say "Don't collapse
  3670. // the chevron balloon tip because of a selection change."
  3671. if ((_fBalloonStyle && fAllowBalloonCollapse) || !_fBalloonStyle)
  3672. {
  3673. SendMessage(_hwndToolTip, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)0);
  3674. _fTipShown = FALSE;
  3675. }
  3676. }
  3677. }
  3678. void CMenuBandState::PutTipOnTop()
  3679. {
  3680. // Force the tooltip to the topmost.
  3681. if (_hwndToolTip)
  3682. {
  3683. SetWindowPos(_hwndToolTip, HWND_TOPMOST,
  3684. 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
  3685. }
  3686. }
  3687. HWND CMenuBandState::GetWorkerWindow(HWND hwndParent)
  3688. {
  3689. if (!_hwndSubclassed)
  3690. return NULL;
  3691. if (!_hwndWorker)
  3692. {
  3693. // We need a worker window, so that dialogs show up on top of our menus.
  3694. // HiddenWndProc is included from sftbar.h
  3695. _hwndWorker = SHCreateWorkerWindow(HiddenWndProc, _hwndSubclassed,
  3696. WS_EX_TOOLWINDOW, WS_POPUP, 0, (void*)_hwndSubclassed);
  3697. }
  3698. //hwndParent is unused at this time. I plan on using it to prevent the parenting to the subclassed window.
  3699. return _hwndWorker;
  3700. }