Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4548 lines
143 KiB

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