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.

4522 lines
143 KiB

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