#include "shellprv.h" #define WANT_SHLWAPI_POSTSPLIT #include #include "common.h" #include "initguid.h" #include "menuband.h" #include "bands.h" #include "isfband.h" #include "dpastuff.h" // COrderList_* #include "resource.h" #include "oleacc.h" #include "apithk.h" #include "uemapp.h" #include "mnbase.h" #include "mnfolder.h" #include "mnstatic.h" #include "iaccess.h" #include "util.h" #include "tbmenu.h" // NOTE: Conflicts with one defined in winuserp.h #undef WINEVENT_VALID //It's tripping on this... #include "winable.h" #define DM_MISC 0 // miscellany #define PF_USINGNTSD 0x00000400 // set this if you're debugging on ntsd // This must be reset to -1 on any WM_WININICHANGE. We do it in // shbrows2.cpp, but if there are no browser windows open when the // metric changes, we end up running around with a stale value. Oh well. long g_lMenuPopupTimeout = -1; // {AD35F50A-0CC0-11d3-AE2D-00C04F8EEA99} static const CLSID CLSID_MenuBandMetrics = { 0xad35f50a, 0xcc0, 0x11d3, { 0xae, 0x2d, 0x0, 0xc0, 0x4f, 0x8e, 0xea, 0x99 } }; // Registered window messages for the menuband UINT g_nMBPopupOpen = 0; UINT g_nMBFullCancel = 0; UINT g_nMBDragCancel = 0; UINT g_nMBAutomation = 0; UINT g_nMBExecute = 0; UINT g_nMBOpenChevronMenu = 0; HCURSOR g_hCursorArrow = NULL; //UINT g_nMBIgnoreNextDeselect = 0; // Dealt with in menuisf.cpp BOOL IsAncestor(HWND hwndChild, HWND hwndAncestor) { HWND hwnd = hwndChild; while (hwnd != hwndAncestor && hwnd != NULL) { hwnd = GetParent(hwnd); } return hwndAncestor == hwnd; } //================================================================= // Implementation of menuband message filter //================================================================= extern "C" void DumpMsg(LPCTSTR pszLabel, MSG * pmsg); // Just one of these, b/c we only need one message filter CMBMsgFilter g_msgfilter = { 0 }; static DWORD g_tlsMessageFilter = -1; CMBMsgFilter* GetMessageFilter() { CMBMsgFilter* pmf = NULL; if (g_tlsMessageFilter == -1) { DWORD tls = TlsAlloc(); if (tls != -1) { InterlockedExchange((LONG*)&g_tlsMessageFilter, tls); if (tls != g_tlsMessageFilter) { TlsFree(tls); } } } if (g_tlsMessageFilter != -1) { pmf = (CMBMsgFilter*)TlsGetValue(g_tlsMessageFilter); if (pmf == NULL) { pmf = new CMBMsgFilter; if (pmf) { pmf->_fAllocated = TRUE; TlsSetValue(g_tlsMessageFilter, pmf); } } } if (pmf == NULL) pmf = &g_msgfilter; return pmf; } void FreeMessageFilter(CMBMsgFilter* that) { if (g_tlsMessageFilter != -1) { CMBMsgFilter* pmf = (CMBMsgFilter*)TlsGetValue(g_tlsMessageFilter); if (pmf == that) { TlsSetValue(g_tlsMessageFilter, NULL); } } } void CMBMsgFilter::AddRef() { _cRef++; } void CMBMsgFilter::Release() { _cRef--; if (_cRef <= 0 && _fAllocated) { FreeMessageFilter(this); delete this; } } void CMBMsgFilter::SetModal(BOOL fModal) { // There was an interesting problem: // Click on the Chevron menu. Right click Delete. // The menus were hosed // Why? // Well, I'll tell you: // We got a deactivate on the subclassed window. We have // 2 menus subclassing it: The Main menu, and the modal // chevron menu. Problem is, the main menu snagged the WM_ACTIVATE // and does a set context. This causes a Pop and releases the Message hook. // Since I still had a menu up, this caused havoc. // So I introduced a concept of a "Modal" menuband. // This says: "Ignore any request to change contexts until I'm done". When // that modal band is done, it sets the old context back in. // Seems like a hack, but we need a better underlying archtecture for // the message passing. _fModal = fModal; } void CMBMsgFilter::ReEngage(void* pvContext) { // We need to make sure that we don't dis/reengage when // switching contexts if (pvContext == _pvContext) _fEngaged = TRUE; } void CMBMsgFilter::DisEngage(void* pvContext) { if (pvContext == _pvContext) _fEngaged = FALSE; } CMenuBand * CMBMsgFilter::_GetTopPtr(void) { CMenuBand * pmb = NULL; int cItems = FDSA_GetItemCount(&_fdsa); if (0 < cItems) { MBELEM * pmbelem = FDSA_GetItemPtr(&_fdsa, cItems-1, MBELEM); pmb = pmbelem->pmb; } return pmb; } CMenuBand * CMBMsgFilter::_GetBottomMostSelected(void) { // Ick, I can't believe I just did this. Mix COM and C++ identities... Yuck. CMenuBand* pmb = NULL; if (_pmb) { IUnknown_QueryService(SAFECAST(_pmb, IMenuBand*), SID_SMenuBandBottomSelected, CLSID_MenuBand, (void**)&pmb); // Since we have the C++ identity, release the COM identity. if (pmb) pmb->Release(); } return pmb; } CMenuBand * CMBMsgFilter::_GetWindowOwnerPtr(HWND hwnd) { CMenuBand * pmb = NULL; int cItems = FDSA_GetItemCount(&_fdsa); if (0 < cItems) { // Go thru the list of bands on the stack and return the // one who owns the given window. int i; for (i = 0; i < cItems; i++) { MBELEM * pmbelem = FDSA_GetItemPtr(&_fdsa, i, MBELEM); if (pmbelem->pmb && S_OK == pmbelem->pmb->IsWindowOwner(hwnd)) { pmb = pmbelem->pmb; break; } } } return pmb; } /*---------------------------------------------------------- Purpose: Return menuband or NULL based upon hittest. pt must be in screen coords */ CMenuBand * CMBMsgFilter::_HitTest(POINT pt, HWND * phwnd) { CMenuBand * pmb = NULL; HWND hwnd = NULL; int cItems = FDSA_GetItemCount(&_fdsa); if (0 < cItems) { // Go thru the list of bands on the stack and return the // one who owns the given window. Work backwards since the // later bands are on top (z-order), if the menus ever overlap. int i = cItems - 1; while (0 <= i) { MBELEM * pmbelem = FDSA_GetItemPtr(&_fdsa, i, MBELEM); RECT rc; // Do this dynamically because the hwndBar hasn't been positioned // until after this mbelem has been pushed onto the msg filter stack. GetWindowRect(pmbelem->hwndBar, &rc); if (PtInRect(&rc, pt)) { pmb = pmbelem->pmb; hwnd = pmbelem->hwndTB; break; } i--; } } if (phwnd) *phwnd = hwnd; return pmb; } void CMBMsgFilter::RetakeCapture(void) { // The TrackPopupMenu submenus can steal the capture. Take // it back. Don't take it back if the we're in edit mode, // because the modal drag/drop loop has the capture at that // point. // We do not want to take capture unless we are engaged. // We need to do this because we are not handling mouse messages lower down // in the code. When we set the capture, the messages that we do not handle // trickle up to the top level menu, and can cause weird problems (Such // as signaling a "click out of bounds" or a context menu of the ITBar) if (_hwndCapture && !_fPreventCapture && _fEngaged) { TraceMsg(TF_MENUBAND, "CMBMsgFilter: Setting capture to %#lx", _hwndCapture); SetCapture(_hwndCapture); } } void CMBMsgFilter::SetHook(BOOL fSet, BOOL fDontIgnoreSysChar) { if (fDontIgnoreSysChar) _iSysCharStack += fSet? 1: -1; if (NULL == _hhookMsg && fSet) { TraceMsg(TF_MENUBAND, "CMBMsgFilter: Initialize"); _hhookMsg = SetWindowsHookEx(WH_GETMESSAGE, GetMsgHook, HINST_THISDLL, GetCurrentThreadId()); _fDontIgnoreSysChar = fDontIgnoreSysChar; } else if (!fSet && _iSysCharStack == 0) { TraceMsg(TF_MENUBAND, "CMBMsgFilter: Hook removed"); if (_hhookMsg) { UnhookWindowsHookEx(_hhookMsg); _hhookMsg = NULL; } } } // 1) Set Deskbars on Both Monitors and set to chevron // 2) On Monitor #2 open a chevron // 3) On Monitor #1 open a chevron then open the Start Menu // Result: Start Menu does not work. // The reason is, we set the _fModal of the global message filter. This prevents context switches. Why? // The modal flag was invented to solve context switching problems with the browser frame. So what causes this? // Well, when switching from #2 to #3, we have not switched contexts. But since we got a click out of bounds, we collapse // the previous menu. When switching from #3 to #4, neither have the context, so things get messy. void CMBMsgFilter::ForceModalCollapse() { if (_fModal) { _fModal = FALSE; SetContext(NULL, TRUE); } } void CMBMsgFilter::SetContext(void* pvContext, BOOL fSet) { TraceMsg(TF_MENUBAND, "CMBMsgFilter::SetContext from 0x%x to 0x%x", _pvContext, pvContext); // When changing a menuband context, we need to pop all of the items // in the stack. This is to prevent a race condition that can occur. // We do not want to pop all of the items off the stack if we're setting the same context. // We do a set context on Activation, Both when we switch from one Browser frame to another // but also when right clicking or causing the Rename dialog to be displayed. BOOL fPop = FALSE; if (_fModal) return; // Are we setting a new context? if (fSet) { // Is this different than the one we've got? if (pvContext != _pvContext) { // Yes, then we need to pop off all of the old items. fPop = TRUE; } _pvContext = pvContext; } else { // Then we are trying to unset the message hook. Make sure it still belongs to // this context if (pvContext == _pvContext) { // This context is trying to unset itself, and no other context owns it. // remove all the old items. fPop = TRUE; } } if (fPop) { CMenuBand* pcmb = _GetTopPtr(); if (pcmb) { PostMessage(pcmb->_pmbState->GetSubclassedHWND(), g_nMBFullCancel, 0, 0); // No release. if (FDSA_GetItemCount(&_fdsa) != 0) { CMBMsgFilter* pmf = GetMessageFilter(); while (pmf->Pop(pvContext)) ; } } } } /*---------------------------------------------------------- Purpose: Push another menuband onto the message filter's stack */ void CMBMsgFilter::Push(void* pvContext, CMenuBand * pmb, IUnknown * punkSite) { ASSERT(IS_VALID_CODE_PTR(pmb, CMenuBand)); TraceMsg(TF_MENUBAND, "CMBMsgFilter::Push called from context 0x%x", pvContext); if (pmb && pvContext == _pvContext) { BOOL bRet = TRUE; HWND hwndBand; pmb->GetWindow(&hwndBand); // If the bar isn't available use the band window HWND hwndBar = hwndBand; IOleWindow * pow; IUnknown_QueryService(punkSite, SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &pow)); if (pow) { pow->GetWindow(&hwndBar); pow->Release(); } if (NULL == _hhookMsg) { // We want to ignore the WM_SYSCHAR message in the message filter because // we are using the IsMenuMessage call instead of the global message hook. SetHook(TRUE, FALSE); TraceMsg(TF_MENUBAND, "CMBMsgFilter::push Setting hook from context 0x%x", pvContext); _fSetAtPush = TRUE; } if (!_fInitialized) { ASSERT(NULL == _hwndCapture); _hwndCapture = hwndBar; _fInitialized = TRUE; bRet = FDSA_Initialize(sizeof(MBELEM), CMBELEM_GROW, &_fdsa, _rgmbelem, CMBELEM_INIT); // We need to initialize this for the top level guy so that we have the correct positioning // from the start of this new set of bands. This is used to eliminate spurious WM_MOUSEMOVE // messages which cause problems. See _HandleMouseMessages for more information AcquireMouseLocation(); } if (EVAL(bRet)) { MBELEM mbelem = {0}; TraceMsg(TF_MENUBAND, "CMBMsgFilter: Push (pmp:%#08lx) onto stack", SAFECAST(pmb, IMenuPopup *)); pmb->AddRef(); mbelem.pmb = pmb; mbelem.hwndTB = hwndBand; mbelem.hwndBar = hwndBar; FDSA_AppendItem(&_fdsa, &mbelem); CMenuBand* pmbTop = _GetTopPtr(); if ((pmbTop && (pmbTop->GetFlags() & SMINIT_LEGACYMENU)) || NULL == GetCapture()) RetakeCapture(); } else { UnhookWindowsHookEx(_hhookMsg); _hhookMsg = NULL; _hwndCapture = NULL; } } } /*---------------------------------------------------------- Purpose: Pop a menuband off the message filter stack Returns the number of bands left on the stack */ int CMBMsgFilter::Pop(void* pvContext) { int nRet = 0; TraceMsg(TF_MENUBAND, "CMBMsgFilter::pop called from context 0x%x", pvContext); // This can be called from a context switch or when we're exiting menu mode, // so we'll switch off the fact that we clear _hhookMsg when we pop the top twice. if (pvContext == _pvContext && _hhookMsg) { int iItem = FDSA_GetItemCount(&_fdsa) - 1; MBELEM * pmbelem; ASSERT(0 <= iItem); pmbelem = FDSA_GetItemPtr(&_fdsa, iItem, MBELEM); if (EVAL(pmbelem->pmb)) { TraceMsg(TF_MENUBAND, "CMBMsgFilter: Pop (pmb=%#08lx) off stack", SAFECAST(pmbelem->pmb, IMenuPopup *)); pmbelem->pmb->Release(); pmbelem->pmb = NULL; } FDSA_DeleteItem(&_fdsa, iItem); if (0 == iItem) { TraceMsg(TF_MENUBAND, "CMBMsgFilter::pop removing hook from context 0x%x", pvContext); if (_fSetAtPush) SetHook(FALSE, FALSE); PreventCapture(FALSE); _fInitialized = FALSE; if (_hwndCapture && GetCapture() == _hwndCapture) { TraceMsg(TF_MENUBAND, "CMBMsgFilter: Releasing capture"); ReleaseCapture(); } _hwndCapture = NULL; } nRet = iItem; } return nRet; } LRESULT CMBMsgFilter::_HandleMouseMsgs(MSG * pmsg, BOOL bRemove) { LRESULT lRet = 0; CMenuBand * pmb; HWND hwnd = GetCapture(); // Do we still have the capture? if (hwnd != _hwndCapture) { // No; is it b/c a CTrackPopupBar has it? if (NULL == hwnd) { // There are times that we must retake the capture because // TrackPopupMenuEx has taken it, or some context menu // might have taken it, so take it back. RetakeCapture(); TraceMsg(TF_WARNING, "CMBMsgFilter: taking the capture back"); } } else { // Yes; decide what to do with it POINT pt; HWND hwndPt; MSG msgT; pt.x = GET_X_LPARAM(pmsg->lParam); pt.y = GET_Y_LPARAM(pmsg->lParam); ClientToScreen(pmsg->hwnd, &pt); if (WM_MOUSEMOVE == pmsg->message) { // The mouse cursor can send repeated WM_MOUSEMOVE messages // with the same coordinates. When the user tries to navigate // thru the menus with the keyboard, and the mouse cursor // happens to be over a menu item, these spurious mouse // messages cause us to think the menu has been invoked under // the mouse cursor. // // To avoid this unpleasant rudeness, we eat any gratuitous // WM_MOUSEMOVE messages. if (_ptLastMove.x == pt.x && _ptLastMove.y == pt.y) { pmsg->message = WM_NULL; goto Bail; } // Since this is not a duplicate point, we need to keep it around. // We will use this stored point for the above comparison // msadek; W2k bug# 426005 // On a mirrored system, we got a system bug as mouse coordinates has an off-by-one // This makes comparing the value with what we got from GetCursorPos() always fail. // Do not use AcquireMouseLocation(). if(!IS_WINDOW_RTL_MIRRORED(pmsg->hwnd)) { AcquireMouseLocation(); } else { _ptLastMove.x = pt.x; _ptLastMove.y = pt.y; } if (_hcurArrow == NULL) _hcurArrow = LoadCursor(NULL, IDC_ARROW); if (GetCursor() != _hcurArrow) SetCursor(_hcurArrow); } // Use a stack variable b/c we don't want to confuse USER32 // by changing the coords of the real message. msgT = *pmsg; msgT.lParam = MAKELPARAM(pt.x, pt.y); pmb = _HitTest(pt, &hwndPt); if (_TopFilterMouseMessage(&msgT, bRemove, pmb) == S_OK) { // Remember the changed message (if there was one) pmsg->message = msgT.message; } else if (pmb) { // Forward mouse message onto appropriate menuband. Note // the appropriate menuband's GetMsgFilterCB (below) will call // ScreenToClient to convert the coords correctly. lRet = pmb->GetMsgFilterCB(&msgT, bRemove); // Remember the changed message (if there was one) pmsg->message = msgT.message; } // Debug note: to debug menubands on ntsd, set the prototype // flag accordingly. This will keep menubands from going // away the moment the focus changes to the NTSD window. else if ((WM_LBUTTONDOWN == pmsg->message || WM_RBUTTONDOWN == pmsg->message) && !(g_dwPrototype & PF_USINGNTSD)) { // Mouse down happened outside the menu. Bail. pmb = _GetTopPtr(); if (EVAL(pmb)) { msgT.hwnd = pmsg->hwnd; msgT.message = g_nMBFullCancel; msgT.wParam = 0; msgT.lParam = 0; TraceMsg(TF_MENUBAND, "CMBMsgFilter (pmb=%#08lx): hittest outside, bailing", SAFECAST(pmb, IMenuPopup *)); pmb->GetMsgFilterCB(&msgT, bRemove); } } else { pmb = _GetTopPtr(); if (pmb) { IUnknown_QueryServiceExec(SAFECAST(pmb, IOleCommandTarget*), SID_SMenuBandBottom, &CGID_MenuBand, MBANDCID_SELECTITEM, MBSI_NONE, NULL, NULL); } } } Bail: return lRet; } HRESULT CMBMsgFilter::_TopFilterMouseMessage(MSG *pmsg, BOOL bRemove, CMenuBand *pmbTarget) { CMenuBand *pmb = _GetTopPtr(); if (pmb && pmb->_psmcb) { // This is a high-frequency message so we handle the callback // ourselves. (_CallCB will allocate memory.) SMDATA smd = {0}; if (pmbTarget) { smd.punk = SAFECAST(pmbTarget, IShellMenu*); smd.uIdParent = pmbTarget->_uId; smd.uIdAncestor = pmbTarget->_uIdAncestor; smd.hwnd = pmbTarget->_hwnd; smd.hmenu = pmbTarget->_hmenu; smd.pvUserData = pmbTarget->_pvUserData; } return pmb->_psmcb->CallbackSM(&smd, SMC_MOUSEFILTER, bRemove, (LPARAM)pmsg); } return S_FALSE; // not handled } /*---------------------------------------------------------- Purpose: Message hook used to track keyboard and mouse messages while the menuband is "active". The menuband can't steal the focus away -- we use this hook to catch messages. */ LRESULT CMBMsgFilter::GetMsgHook(int nCode, WPARAM wParam, LPARAM lParam) { LRESULT lRet = 0; MSG * pmsg = (MSG *)lParam; BOOL bRemove = (PM_REMOVE == wParam); CMBMsgFilter* pmf = GetMessageFilter(); // The global message filter may be in a state when we are not processing messages, // but the menubands are still displayed. A situation where this will occur is when // a dialog box is displayed because of an interaction with the menus. // Are we engaged? (Are we allowed to process messages?) if (pmf->_fEngaged) { if (WM_SYSCHAR == pmsg->message) { // _fDontIgnoreSysChar is set when the Menubands ONLY want to know about // WM_SYSCHAR and nothing else. if (pmf->_fDontIgnoreSysChar) { CMenuBand * pmb = pmf->GetTopMostPtr(); if (pmb) lRet = pmb->GetMsgFilterCB(pmsg, bRemove); } } else if (pmf->_fInitialized) // Only filter if we are initalized (have items on the stack) { switch (nCode) { case HC_ACTION: #ifdef DEBUG if (g_dwDumpFlags & DF_GETMSGHOOK) DumpMsg(TEXT("GetMsg"), pmsg); #endif // A lesson about GetMsgHook: it gets the same message // multiple times for as long as someone calls PeekMessage // with the PM_NOREMOVE flag. So we want to take action // only when PM_REMOVE is set (so we don't handle more than // once). If we modify any messages to redirect them (on a // regular basis), we must modify all the time so we don't // confuse the app. // Messages get redirected to different bands in the stack // in this way: // // 1) Keyboard messages go to the currently open submenu // (topmost on the stack). // // 2) The PopupOpen message goes to the hwnd that belongs // to the menu band (via IsWindowOwner). // switch (pmsg->message) { case WM_SYSKEYDOWN: case WM_KEYDOWN: case WM_CHAR: case WM_KEYUP: case WM_CLOSE: // only this message filter gets WM_CLOSE { // There is a situation that can occur when the last selected // menu pane is NOT the bottom most pane. // We need to see if that last selected guy is tracking a context // menu so that we forward the messages correctly. CMenuBand * pmb = pmf->_GetBottomMostSelected(); if (pmb) { // Is it tracking a context menu? if (S_OK == IUnknown_Exec(SAFECAST(pmb, IMenuBand*), &CGID_MenuBand, MBANDCID_ISTRACKING, 0, NULL, NULL)) { // Yes, forward for proper handling. lRet = pmb->GetMsgFilterCB(pmsg, bRemove); } else { // No; Then do the default processing. This can happen if there is no // context menu, but there is a selected parent and not a selected child. goto TopHandler; } } else { TopHandler: pmb = pmf->_GetTopPtr(); if (pmb) lRet = pmb->GetMsgFilterCB(pmsg, bRemove); } } break; case WM_NULL: // Handle this here (we do nothing) to avoid mistaking this for // g_nMBPopupOpen below, in case g_nMBPopupOpen is 0 if // RegisterWindowMessage fails. break; default: if (bRemove && IsInRange(pmsg->message, WM_MOUSEFIRST, WM_MOUSELAST)) { lRet = pmf->_HandleMouseMsgs(pmsg, bRemove); } else if (pmsg->message == g_nMBPopupOpen) { CMenuBand * pmb = pmf->_GetWindowOwnerPtr(pmsg->hwnd); if (pmb) lRet = pmb->GetMsgFilterCB(pmsg, bRemove); } else if (pmsg->message == g_nMBExecute) { CMenuBand * pmb = pmf->_GetWindowOwnerPtr(pmsg->hwnd); if (pmb) { VARIANT var; var.vt = VT_UINT_PTR; var.ullVal = (UINT_PTR)pmsg->hwnd; pmb->Exec(&CGID_MenuBand, MBANDCID_EXECUTE, (DWORD)pmsg->wParam, &var, NULL); } } break; } break; default: if (0 > nCode) return CallNextHookEx(pmf->_hhookMsg, nCode, wParam, lParam); break; } } } // Pass it on to the next hook in the chain if (0 == lRet) return CallNextHookEx(pmf->_hhookMsg, nCode, wParam, lParam); return 0; // Always return 0 } //================================================================= // Implementation of CMenuBand //================================================================= // Struct used by EXEC with a MBANDCID_GETFONTS to return fonts typedef struct tagMBANDFONTS { HFONT hFontMenu; // [out] TopLevelMenuBand's menu font HFONT hFontArrow; // [out] TopLevelMenuBand's font for drawing the cascade arrow int cyArrow; // [out] Height of TopLevelMenuBand's cascade arrow int cxArrow; // [out] Width of TopLevelMenuBand's cascade arrow int cxMargin; // [out] Margin b/t text and arrow } MBANDFONTS; #define THISCLASS CMenuBand #define SUPERCLASS CToolBand #ifdef DEBUG int g_nMenuLevel = 0; #define DBG_THIS _nMenuLevel, SAFECAST(this, IMenuPopup *) #else #define DBG_THIS 0, 0 #endif CMenuBand::CMenuBand() : SUPERCLASS() { GetMessageFilter()->AddRef(); _fCanFocus = TRUE; _fAppActive = TRUE; _nItemNew = -1; _nItemCur = -1; _nItemTimer = -1; _uIconSize = ISFBVIEWMODE_SMALLICONS; _uIdAncestor = ANCESTORDEFAULT; _nItemSubMenu = -1; } // The purpose of this method is to finish initializing Menubands, // since it can be initialized in many ways. HRESULT CMenuBand::_Initialize(DWORD dwFlags) { _fVertical = !BOOLIFY(dwFlags & SMINIT_HORIZONTAL); _fTopLevel = BOOLIFY(dwFlags & SMINIT_TOPLEVEL); _dwFlags = dwFlags; // We cannot have a horizontal menu if it is not the toplevel menu ASSERT(!_fVertical && _fTopLevel || _fVertical); HRESULT hr = S_OK; if (_fTopLevel) { if (!g_nMBPopupOpen) { g_nMBPopupOpen = RegisterWindowMessage(TEXT("CMBPopupOpen")); g_nMBFullCancel = RegisterWindowMessage(TEXT("CMBFullCancel")); g_nMBDragCancel = RegisterWindowMessage(TEXT("CMBDragCancel")); g_nMBAutomation = RegisterWindowMessage(TEXT("CMBAutomation")); g_nMBExecute = RegisterWindowMessage(TEXT("CMBExecute")); g_nMBOpenChevronMenu = RegisterWindowMessage(TEXT("CMBOpenChevronMenu")); g_hCursorArrow = LoadCursor(NULL, IDC_ARROW); TraceMsg(TF_MENUBAND, "CMBPopupOpen message = %#lx", g_nMBPopupOpen); TraceMsg(TF_MENUBAND, "CMBFullCancel message = %#lx", g_nMBFullCancel); } if (!_pmbState) _pmbState = new CMenuBandState; if (!_pmbm) _pmbm = new CMenuBandMetrics(); if (!_pmbState || !_pmbm) { hr = E_OUTOFMEMORY; } } DEBUG_CODE( _nMenuLevel = -1; ) return hr; } CMenuBand::~CMenuBand() { CMBMsgFilter* pmf = GetMessageFilter(); // the message filter does not have a ref'd pointer to us!!! if (pmf->GetTopMostPtr() == this) pmf->SetTopMost(NULL); _CallCB(SMC_DESTROY); ATOMICRELEASE(_psmcb); // Cleanup CloseDW(0); if (_pmtbMenu) delete _pmtbMenu; if (_pmtbShellFolder) delete _pmtbShellFolder; Str_SetPtr(&_pszTheme, NULL); ASSERT(_punkSite == NULL); ATOMICRELEASE(_pmpTrackPopup); ATOMICRELEASE(_pmbm); if (_fTopLevel) { if (_pmbState) delete _pmbState; } GetMessageFilter()->Release(); } /*---------------------------------------------------------- Purpose: Create-instance function for class factory */ HRESULT CMenuBand_CreateInstance(IUnknown* pUnkOuter, REFIID riid, void **ppv) { // aggregation checking is handled in class factory HRESULT hr = E_OUTOFMEMORY; CMenuBand *pObj = new CMenuBand(); if (pObj) { hr = pObj->QueryInterface(riid, ppv); pObj->Release(); } return hr; } void CMenuBand::_UpdateButtons() { if (_pmtbMenu) _pmtbMenu->v_UpdateButtons(FALSE); if (_pmtbShellFolder) _pmtbShellFolder->v_UpdateButtons(FALSE); _fForceButtonUpdate = FALSE; } HRESULT CMenuBand::ForwardChangeNotify(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { // Given a change notify from the ShellFolder child, we will forward that notify to each of our // sub menus, but only if they have a shell folder child. HRESULT hres = E_FAIL; BOOL fDone = FALSE; CMenuToolbarBase* pmtb = _pmtbBottom; // Start With the bottom toolbar. This is // is an optimization because typically // menus that have both a Shell Folder portion // and a static portion have the majority // of the change activity in the bottom portion. // This can be NULL on a shutdown, when we're deregistering change notifies if (pmtb && pmtb->_hwndMB) { HWND hwnd = pmtb->_hwndMB; for (int iButton = 0; !fDone; iButton++) { IShellChangeNotify* ptscn; int idCmd = GetButtonCmd(hwnd, iButton); // If it's not a seperator, see if there is a sub menu with a shell folder child. if (idCmd != -1 && SUCCEEDED(pmtb->v_GetSubMenu(idCmd, &SID_MenuShellFolder, IID_PPV_ARG(IShellChangeNotify, &ptscn)))) { IShellMenu* psm; // Don't forward this notify if the sub menu has specifically registered for change notify (By not passing // DontRegisterChangeNotify. if (SUCCEEDED(ptscn->QueryInterface(IID_PPV_ARG(IShellMenu, &psm)))) { UINT uIdParent = 0; DWORD dwFlags = 0; // Get the flags psm->GetShellFolder(&dwFlags, NULL, IID_NULL, NULL); psm->GetMenuInfo(NULL, &uIdParent, NULL, NULL); // If this menupane is an "Optimized" pane, (meaning that we don't register for change notify // and forward from a top level menu down) then we want to forward. We also // forward if this is a child of Menu Folder. If it is a child, // then it also does not register for change notify, but does not explicitly set it in it's flags // (review: Should we set it in it's flags?) // If it is not an optimized pane, then don't forward. if ((dwFlags & SMSET_DONTREGISTERCHANGENOTIFY) || uIdParent == MNFOLDER_IS_PARENT) { // There is!, then pass to the child the change. hres = ptscn->OnChange(lEvent, pidl1, pidl2); // Update Dir on a Recursive change notify forces us to update everyone... Good thing // this does not happen alot and is caused by user interaction most of the time. } psm->Release(); } ptscn->Release(); } // Did we go through all of the buttons on this toolbar? if (iButton >= ToolBar_ButtonCount(hwnd) - 1) { // Yes, then we need to switch to the next toolbar. if (_pmtbTop != _pmtbBottom && pmtb != _pmtbTop) { pmtb = _pmtbTop; hwnd = pmtb->_hwndMB; iButton = -1; // -1 because at the end of the loop the for loop will increment. } else { // No; Then we must be done. fDone = TRUE; } } } } else hres = S_OK; // Return success because we're shutting down. return hres; } // Resize the parent menubar VOID CMenuBand::ResizeMenuBar() { // If we're not shown, then we do not need to do any kind of resize. // NOTE: Horizontal menubands are always shown. Don't do any of the // vertical stuff if we're horizontal. if (!_fShow) return; // If we're horizontal, don't do any Vertical sizing stuff. if (!_fVertical) { // BandInfoChanged is only for Horizontal Menubands. _BandInfoChanged(); return; } // We need to update the buttons before a resize so that the band is the right size. _UpdateButtons(); // Have the menubar think about changing its height IUnknown_QueryServiceExec(_punkSite, SID_SMenuPopup, &CGID_MENUDESKBAR, MBCID_RESIZE, 0, NULL, NULL); } STDMETHODIMP CMenuBand::QueryInterface(REFIID riid, void **ppvObj) { HRESULT hres; static const QITAB qit[] = { // Do not need IOleWindow (base class for IDeskBar) // because CToolBand::IDeskBand::IDockingWindow::IOleWindow // handles it. QITABENT(CMenuBand, IDeskBar), // Base class for IMenuPopup QITABENT(CMenuBand, IMenuPopup), QITABENT(CMenuBand, IMenuBand), QITABENT(CMenuBand, IShellMenu), QITABENT(CMenuBand, IShellMenu2), QITABENT(CMenuBand, IWinEventHandler), QITABENT(CMenuBand, IShellMenuAcc), { 0 }, }; hres = QISearch(this, (LPCQITAB)qit, riid, ppvObj); if (FAILED(hres)) hres = SUPERCLASS::QueryInterface(riid, ppvObj); if (FAILED(hres) && IsEqualGUID(riid, CLSID_MenuBand)) { AddRef(); *ppvObj = (LPVOID)this; hres = S_OK; } return hres; } /*---------------------------------------------------------- Purpose: IServiceProvider::QueryService method */ STDMETHODIMP CMenuBand::QueryService(REFGUID guidService, REFIID riid, void **ppvObj) { HRESULT hr = E_FAIL; *ppvObj = NULL; // assume error if (IsEqualIID(guidService, SID_SMenuPopup) || IsEqualIID(guidService, SID_SMenuBandChild) || IsEqualIID(guidService, SID_SMenuBandParent) || (_fTopLevel && IsEqualIID(guidService, SID_SMenuBandTop))) { if (IsEqualIID(riid, IID_IAccessible) || IsEqualIID(riid, IID_IDispatch)) { hr = E_OUTOFMEMORY; CAccessible* pacc = new CAccessible(SAFECAST(this, IMenuBand*)); if (pacc) { hr = pacc->InitAcc(); if (SUCCEEDED(hr)) { hr = pacc->QueryInterface(riid, ppvObj); } pacc->Release(); } } else hr = QueryInterface(riid, ppvObj); } else if (IsEqualIID(guidService, SID_SMenuBandBottom) || IsEqualIID(guidService, SID_SMenuBandBottomSelected)) { // SID_SMenuBandBottom queries down BOOL fLookingForSelected = IsEqualIID(SID_SMenuBandBottomSelected, guidService); // Are we the leaf node? if (!_fInSubMenu) { if ( fLookingForSelected && (_pmtbTracked == NULL || ToolBar_GetHotItem(_pmtbTracked->_hwndMB) == -1)) { hr = E_FAIL; } else { hr = QueryInterface(riid, ppvObj); // Yes; QI ourselves } } else { // No; QS down... IMenuPopup* pmp = _pmpSubMenu; if (_pmpTrackPopup) pmp = _pmpTrackPopup; ASSERT(pmp); hr = IUnknown_QueryService(pmp, guidService, riid, ppvObj); if (FAILED(hr) && fLookingForSelected && _pmtbTracked != NULL) { hr = QueryInterface(riid, ppvObj); // Yes; QI ourselves } } } else if (IsEqualIID(guidService, SID_MenuShellFolder)) { // This is a method of some other menu in the scheme to get to specifically the MenuShellfolder, // This is for the COM Identity property. if (_pmtbShellFolder) hr = _pmtbShellFolder->QueryInterface(riid, ppvObj); } else hr = SUPERCLASS::QueryService(guidService, riid, ppvObj); return hr; } /*---------------------------------------------------------- Purpose: IWinEventHandler::IsWindowOwner method */ STDMETHODIMP CMenuBand::IsWindowOwner(HWND hwnd) { if (( _pmtbShellFolder && (_pmtbShellFolder->IsWindowOwner(hwnd) == S_OK) ) || (_pmtbMenu && (_pmtbMenu->IsWindowOwner(hwnd) == S_OK))) return S_OK; return S_FALSE; } #define MB_EICH_FLAGS (EICH_SSAVETASKBAR | EICH_SWINDOWMETRICS | EICH_SPOLICY | EICH_SSHELLMENU | EICH_KWINPOLICY) /*---------------------------------------------------------- Purpose: IWinEventHandler::OnWinEvent method Processes messages passed on from the bandsite. */ STDMETHODIMP CMenuBand::OnWinEvent(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plres) { HRESULT hres = NOERROR; EnterModeless(); // Could our metrics be changing? (We keep track of this only for the // toplevel menu) BOOL fProcessSettingChange = FALSE; switch (uMsg) { case WM_SETTINGCHANGE: fProcessSettingChange = !lParam || (SHIsExplorerIniChange(wParam, lParam) & MB_EICH_FLAGS) || wParam == SPI_SETDROPSHADOW || wParam == SPI_SETFLATMENU; break; case WM_SYSCOLORCHANGE: case WM_DISPLAYCHANGE: fProcessSettingChange = TRUE; break; } if (_fTopLevel && fProcessSettingChange && _pmbState && !_pmbState->IsProcessingChangeNotify()) { // There is a race condition that can occur during a refresh // that's really nasty. It causes another one to get pumped in the // middle of processing this one, Yuck! _pmbState->PushChangeNotify(); // There is a race condiction that can occur when the menuband is created, // but not yet initialized. This has been hit by the IEAK group.... if (_pmtbTop) { // Yes; create a new metrics object and tell the submenus // about it. CMenuBandMetrics* pmbm = new CMenuBandMetrics(); if (pmbm) { ATOMICRELEASE(_pmbm); _pmbm = pmbm; _pmbm->Init(_pmtbTop->_hwndMB); if (_pmtbMenu) _pmtbMenu->SetMenuBandMetrics(_pmbm); if (_pmtbShellFolder) _pmtbShellFolder->SetMenuBandMetrics(_pmbm); _CallCB(SMC_REFRESH, wParam, lParam); // We need to force a button update at some point so that the new sizes are calculated // Setting this flag will cause the buttons to be updatted before the next time it // is shown. If, however, the menu is currently displayed, then the ResizeMenuBar will // recalculate immediatly. _fForceButtonUpdate = TRUE; RECT rcOld; RECT rcNew; // Resize the MenuBar GetClientRect(_hwndParent, &rcOld); ResizeMenuBar(); GetClientRect(_hwndParent, &rcNew); // If the rect sizes haven't changed, then we need to re-layout the // band because the button widths may have changed. if (EqualRect(&rcOld, &rcNew) && _fVertical) _pmtbTop->NegotiateSize(); } } if (_pmtbMenu) hres = _pmtbMenu->OnWinEvent(hwnd, uMsg, wParam, lParam, plres); if (_pmtbShellFolder) hres = _pmtbShellFolder->OnWinEvent(hwnd, uMsg, wParam, lParam, plres); _pmbState->PopChangeNotify(); } else { if (_pmtbMenu && (_pmtbMenu->IsWindowOwner(hwnd) == S_OK) ) hres = _pmtbMenu->OnWinEvent(hwnd, uMsg, wParam, lParam, plres); if (_pmtbShellFolder && (_pmtbShellFolder->IsWindowOwner(hwnd) == S_OK) ) hres = _pmtbShellFolder->OnWinEvent(hwnd, uMsg, wParam, lParam, plres); } ExitModeless(); return hres; } /*---------------------------------------------------------- Purpose: IOleWindow::GetWindow method */ STDMETHODIMP CMenuBand::GetWindow(HWND * phwnd) { if (_pmtbMenu) { *phwnd = _pmtbMenu->_hwndMB; return NOERROR; } else if (_pmtbShellFolder) { *phwnd = _pmtbShellFolder->_hwndMB; return NOERROR; } else { *phwnd = NULL; return E_FAIL; } } /*---------------------------------------------------------- Purpose: IOleWindow::ContextSensitiveHelp method */ STDMETHODIMP CMenuBand::ContextSensitiveHelp(BOOL bEnterMode) { return SUPERCLASS::ContextSensitiveHelp(bEnterMode); } /*---------------------------------------------------------- Purpose: Handle WM_CHAR for accelerators This is handled for any vertical menu. Since we have two toolbars (potentially), this function determines which toolbar gets the message depending on the accelerator. */ HRESULT CMenuBand::_HandleAccelerators(MSG * pmsg) { TCHAR ch = (TCHAR)pmsg->wParam; HWND hwndTop = _pmtbTop->_hwndMB; HWND hwndBottom = _pmtbBottom->_hwndMB; // Here's how this works: the menu can have one or two toolbars. // // One toolbar: we simply forward the message onto the toolbar // and let it handle any potential accelerators. // // Two toolbars: get the count of accelerators that match the // given char for each toolbar. If only one toolbar has at // least one match, forward the message onto that toolbar. // Otherwise, forward the message onto the currently tracked // toolbar and let it negotiate which accelerator button to // choose (we might get a TBN_WRAPHOTITEM). // // If no match occurs, we beep. Beep beep. // if (!_pmtbTracked) SetTracked(_pmtbTop); ASSERT(_pmtbTracked); if (_pmtbTop != _pmtbBottom) { int iNumBottomAccel; int iNumTopAccel; // Tell the dup handler not to handle this one.... _fProcessingDup = TRUE; ToolBar_HasAccelerator(hwndTop, ch, &iNumTopAccel); ToolBar_HasAccelerator(hwndBottom, ch, &iNumBottomAccel); BOOL bBottom = (0 < iNumBottomAccel); BOOL bTop = (0 < iNumTopAccel); // Does one or the other (but not both) have an accelerator? if (bBottom ^ bTop) { // Yes; do the work here for that specific toolbar HWND hwnd = bBottom ? hwndBottom : hwndTop; int cAccel = bBottom ? iNumBottomAccel : iNumTopAccel; int idCmd; pmsg->message = WM_NULL; // no need to forward the message // This should never really fail since we just checked EVAL( ToolBar_MapAccelerator(hwnd, ch, &idCmd) ); DWORD dwFlags = HICF_ACCELERATOR | HICF_RESELECT; if (cAccel == 1) dwFlags |= HICF_TOGGLEDROPDOWN; int iPos = ToolBar_CommandToIndex(hwnd, idCmd); ToolBar_SetHotItem2(hwnd, iPos, dwFlags); } // No; were there no accelerators? else if ( !bTop ) { // Yes if (_fVertical) { MessageBeep(MB_OK); } else { _CancelMode(MPOS_FULLCANCEL); } } // Else allow the message to go to the top toolbar _fProcessingDup = FALSE; } return NOERROR; } /*---------------------------------------------------------- Purpose: Callback for the get message filter. We handle the keyboard messages here (rather than IInputObject:: TranslateAcceleratorIO) so that we can redirect the message *and* have the message pump still call TranslateMessage to generate WM_CHAR and WM_SYSCHAR messages. */ LRESULT CMenuBand::GetMsgFilterCB(MSG * pmsg, BOOL bRemove) { // (See the note in CMBMsgFilter::GetMsgHook about bRemove.) if (bRemove && !_fVertical && (pmsg->message == g_nMBPopupOpen) && _pmtbTracked) { // Menu is being popped open, send a WM_MENUSELECT equivalent. _pmtbTracked->v_SendMenuNotification((UINT)pmsg->wParam, FALSE); } if (_fTopLevel && // Only do this for the top level _dwFlags & SMINIT_USEMESSAGEFILTER && // They want to use the message filter // instead of IsMenuMessage bRemove && // Only do this if we're removing it. WM_SYSCHAR == pmsg->message) // We only care about WM_SYSCHAR { // We intercept Alt-key combos (when pressed together) here, // to prevent USER from going into a false menu loop check. // There are compatibility problems if we let that happen. // // Sent by USER32 when the user hits an Alt-char combination. // We need to translate this into popping down the correct // menu. Normally we intercept this in the message pump // if (_OnSysChar(pmsg, TRUE) == S_OK) { pmsg->message = WM_NULL; } } // If a user menu is up, then we do not want to intercept those messages. Intercepting // messages intended for the poped up user menu causes havoc with keyboard accessibility. // We also don't want to process messages if we're displaying a sub menu (It should be // handling them). BOOL fTracking = FALSE; if (_pmtbMenu) fTracking = _pmtbMenu->v_TrackingSubContextMenu(); if (_pmtbShellFolder && !fTracking) fTracking = _pmtbShellFolder->v_TrackingSubContextMenu(); if (!_fInSubMenu && !fTracking) { // We don't process these messages when we're in a (modal) submenu switch (pmsg->message) { case WM_SYSKEYDOWN: case WM_KEYDOWN: if (bRemove && (VK_ESCAPE == pmsg->wParam || VK_MENU == pmsg->wParam)) { TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Received Esc in msg filter", DBG_THIS); DWORD dwSelect = (VK_ESCAPE == pmsg->wParam) ? MPOS_CANCELLEVEL : MPOS_FULLCANCEL; _CancelMode(dwSelect); pmsg->message = WM_NULL; return 1; } // Fall thru case WM_CHAR: // Hitting the spacebar should invoke the system menu if (!_fVertical && WM_CHAR == pmsg->message && TEXT(' ') == (TCHAR)pmsg->wParam) { // We need to leave this modal loop before bringing // up the system menu (otherwise the user would need to // hit Alt twice to get out.) Post the message. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Leaving menu mode for system menu", DBG_THIS); UIActivateIO(FALSE, NULL); // Say the Alt-key is down to catch DefWindowProc's attention pmsg->lParam |= 0x20000000; pmsg->message = WM_SYSCHAR; // Use the parent of the toolbar, because toolbar does not // forward WM_SYSCHAR onto DefWindowProc. pmsg->hwnd = GetParent(_pmtbTop->_hwndMB); return 1; } else if (_fVertical && WM_CHAR == pmsg->message && pmsg->wParam != VK_RETURN) { // We do not want to pass VK_RETURN to _HandleAccelerators // because it will try to do a character match. When it fails // it will beep. Then we pass the VK_RETURN to the tracked toolbar // and it executes the command. // Handle accelerators here _HandleAccelerators(pmsg); } // Fall thru case WM_KEYUP: // Collection point for most key messages... if (NULL == _pmtbTracked) { // Normally we default to the top toolbar, unless that toolbar // cannot receive the selection (As is the case on the top level // start menu where the fast items are (Empty). // Can the top toolbar be cycled into? if (_pmtbTop && !_pmtbTop->DontShowEmpty()) { // Yes; SetTracked(_pmtbTop); // default to the top toolbar } else { // No; Set the tracked to the bottom, and hope that he can.... SetTracked(_pmtbBottom); } } // F10 has special meaning for menus. // - F10 alone, should toggle the selection of the first item // in a horizontal menu // - Shift-F10 should display a context menu. if (VK_F10 == pmsg->wParam) { // Is this the Shift-F10 Case? if (GetKeyState(VK_SHIFT) < 0) { // Yes. We need to force this message into a context menu // message. pmsg->message = WM_CONTEXTMENU; pmsg->lParam = -1; pmsg->wParam = (WPARAM)_pmtbTracked->_hwndMB; return 0; } else if (!_fVertical) //No; Then we need to toggle in the horizontal case { if (_pmtbMenu) { // Set the hot item to the first one. int iHot = 0; if (ToolBar_GetHotItem(_pmtbMenu->_hwndMB) != -1) iHot = -1; // We're toggling the selection off. ToolBar_SetHotItem(_pmtbMenu->_hwndMB, iHot); } return 0; } } // Redirect to the toolbar if (_pmtbTracked) pmsg->hwnd = _pmtbTracked->_hwndMB; return 0; case WM_NULL: // Handle this here (we do nothing) to avoid mistaking this for // g_nMBPopupOpen below, in case g_nMBPopupOpen is 0 if // RegisterWindowMessage fails. return 0; default: // We used to handle g_nMBPopupOpen here. But we can't because calling TrackPopupMenu // (via CTrackPopupBar::Popup) w/in a GetMessageFilter is very bad. break; } } if (bRemove) { // These messages must be processed even when no submenu is open switch (pmsg->message) { case WM_CLOSE: // Being deactivated. Bail out of menus. TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): sending MPOS_FULLCANCEL", DBG_THIS); _CancelMode(MPOS_FULLCANCEL); break; default: if (IsInRange(pmsg->message, WM_MOUSEFIRST, WM_MOUSELAST)) { // If we move the mouse, collapse the tip. Careful not to blow away a balloon tip... if (_pmbState) _pmbState->HideTooltip(FALSE); if (_pmtbShellFolder) _pmtbShellFolder->v_ForwardMouseMessage(pmsg->message, pmsg->wParam, pmsg->lParam); if (_pmtbMenu) _pmtbMenu->v_ForwardMouseMessage(pmsg->message, pmsg->wParam, pmsg->lParam); // Don't let the message be dispatched now that we've // forwarded it. pmsg->message = WM_NULL; } else if (pmsg->message == g_nMBFullCancel) { // Popup TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Received private full cancel message", DBG_THIS); _SubMenuOnSelect(MPOS_CANCELLEVEL); _CancelMode(MPOS_FULLCANCEL); return 1; } break; } } return 0; } /*---------------------------------------------------------- Purpose: Handle WM_SYSCHAR This is handled for the toplevel menu only. */ HRESULT CMenuBand::_OnSysChar(MSG * pmsg, BOOL bFirstDibs) { TCHAR ch = (TCHAR)pmsg->wParam; // HACKHACK (scotth): I'm only doing all this checking because I don't // understand why the doc-obj case sometimes (and sometimes doesn't) // intercept this in its message filter. if (!bFirstDibs && _fSysCharHandled) { _fSysCharHandled = FALSE; return S_FALSE; } if (TEXT(' ') == (TCHAR)pmsg->wParam) { _fAltSpace = TRUE; // In the words of Spock..."Remember" // start menu alt+space TraceMsg(DM_MISC, "cmb._osc: alt+space _fTopLevel(1)"); UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_UIINPUT, UIBL_INPMENU); } else if (!_fInSubMenu) { int idBtn; ASSERT(_fTopLevel); // There is a brief instant when we're merging a menu and pumping messages // This results in a null _pmtbMenu. if (_pmtbMenu) { // Only a toplevel menubar follows this codepath. This means only // the static menu toolbar will exist (and not the shellfolder toolbar). _pmtbTracked = _pmtbMenu; HWND hwnd = _pmtbTracked->_hwndMB; if (ToolBar_MapAccelerator(hwnd, ch, &idBtn)) { // Post a message since we're already in a menu loop TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): WM_SYSCHAR: Posting CMBPopup message", DBG_THIS); UIActivateIO(TRUE, NULL); _pmtbTracked->PostPopup(idBtn, TRUE, TRUE); // browser menu alt+key, start menu alt+key TraceMsg(DM_MISC, "cmb._osc: alt+key _fTopLevel(1)"); UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_UIINPUT, UIBL_INPMENU); return S_OK; } } } // Set or reset _fSysCharHandled = bFirstDibs ? TRUE : FALSE; return S_FALSE; } HRESULT CMenuBand::_ProcessMenuPaneMessages(MSG* pmsg) { if (pmsg->message == g_nMBPopupOpen) { // Popup the submenu. Since the top-level menuband receives this first, the // command must be piped down the chain to the bottom-most menuband. IOleCommandTarget * poct; QueryService(SID_SMenuBandBottom, IID_PPV_ARG(IOleCommandTarget, &poct)); if (poct) { BOOL bSetItem = LOWORD(pmsg->lParam); BOOL bInitialSelect = HIWORD(pmsg->lParam); VARIANTARG vargIn; TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Received private popup menu message", DBG_THIS); DWORD dwOpt = 0; vargIn.vt = VT_I4; vargIn.lVal = (LONG)pmsg->wParam; if (bSetItem) dwOpt |= MBPUI_SETITEM; if (bInitialSelect) dwOpt |= MBPUI_INITIALSELECT; poct->Exec(&CGID_MenuBand, MBANDCID_POPUPITEM, dwOpt, &vargIn, NULL); poct->Release(); return S_OK; } } else if (pmsg->message == g_nMBDragCancel) { // If we got a drag cancel, make sure that the bottom most // menu does not have the drag enter. IUnknown_QueryServiceExec(SAFECAST(this, IOleCommandTarget*), SID_SMenuBandBottom, &CGID_MenuBand, MBANDCID_DRAGCANCEL, 0, NULL, NULL); return S_OK; } else if (pmsg->message == g_nMBOpenChevronMenu) { VARIANTARG v; v.vt = VT_I4; v.lVal = (LONG)pmsg->wParam; IUnknown_Exec(_punkSite, &CGID_DeskBand, DBID_PUSHCHEVRON, _dwBandID, &v, NULL); } else if (pmsg->message == g_nMBFullCancel) { _SubMenuOnSelect(MPOS_CANCELLEVEL); _CancelMode(MPOS_FULLCANCEL); return S_OK; } return S_FALSE; } /*---------------------------------------------------------- Purpose: IMenuBand::IsMenuMessage method The thread's message pump calls this function to see if any messages need to be redirected to the menu band. This returns S_OK if the message is handled. The message pump should not pass it onto TranslateMessage or DispatchMessage if it does. */ STDMETHODIMP CMenuBand::IsMenuMessage(MSG * pmsg) { HRESULT hres = S_FALSE; ASSERT(IS_VALID_WRITE_PTR(pmsg, MSG)); #ifdef DEBUG if (g_dwDumpFlags & DF_TRANSACCELIO) DumpMsg(TEXT("CMB::IsMM"), pmsg); #endif if (!_fShow) goto Return; switch (pmsg->message) { case WM_SYSKEYDOWN: // blow this off if it's a repeated keystroke if (!(pmsg->lParam & 0x40000000)) { SendMessage(_hwndParent, WM_CHANGEUISTATE ,MAKEWPARAM(UIS_CLEAR, UISF_HIDEACCEL), 0); // Are we pressing the Alt key to activate the menu? if (!_fMenuMode && pmsg->wParam == VK_MENU && _pmbState) { // Yes; The the menu was activated because of a keyboard, // Set the global state to show the keyboard cues. _pmbState->SetKeyboardCue(TRUE); // Since this only happens on the top level menu, // We only have to tell the "Top" menu to update it's state. _pmtbTop->SetKeyboardCue(); } } break; case WM_SYSKEYUP: // If we're in menu mode, ignore this message. // if (_fMenuMode) hres = S_OK; break; case WM_SYSCHAR: // We intercept Alt-key combos (when pressed together) here, // to prevent USER from going into a false menu loop check. // There are compatibility problems if we let that happen. // // Sent by USER32 when the user hits an Alt-char combination. // We need to translate this into popping down the correct // menu. Normally we intercept this in the message pump // // Outlook Express needs a message hook in order to filter this // message for perf we do not use that method. // Athena fix 222185 (lamadio) We also don't want to do this if we are not active! // otherwise when WAB is on top of OE, we'll steal it's messages // (lamadio): If the Message filter is "engaged", then we can process accelerators. // Engaged does not mean that the filter is running. if (GetMessageFilter()->IsEngaged()) { hres = (_OnSysChar(pmsg, TRUE) == S_OK) ? S_OK : S_FALSE; } break; case WM_KEYDOWN: case WM_CHAR: case WM_KEYUP: if (_fMenuMode) { // All keystrokes should be handled or eaten by menubands // if we're engaged. We must do this, otherwise hosted // components like mshtml or word will try to handle the // keystroke in CBaseBrowser. // Also, don't bother forwarding tabs if (VK_TAB != pmsg->wParam) { // Since we're answer S_OK, dispatch it ourselves. TranslateMessage(pmsg); DispatchMessage(pmsg); } hres = S_OK; } break; case WM_CONTEXTMENU: // Since the start button has the keyboard focus, // the start button will handle this. We need to forward this off to the // currently tracked item at the bottom of the chain LRESULT lres; IWinEventHandler* pweh; if (_fMenuMode && SUCCEEDED(QueryService(SID_SMenuBandBottomSelected, IID_PPV_ARG(IWinEventHandler, &pweh)))) { pweh->OnWinEvent(HWND_BROADCAST, pmsg->message, pmsg->wParam, pmsg->lParam, &lres); pweh->Release(); hres = S_OK; } break; default: // We only want to process the pane messages in IsMenuMessage when there is no // top level HWND. This is for the Deskbar menus. Outlook Express needs the // TranslateMenuMessage entry point if (_pmbState->GetSubclassedHWND() == NULL) hres = _ProcessMenuPaneMessages(pmsg); break; } Return: if (!_fMenuMode && hres != S_OK) hres = E_FAIL; return hres; } BOOL HasWindowTopmostOwner(HWND hwnd) { HWND hwndOwner = hwnd; while (hwndOwner = GetWindowOwner(hwndOwner)) { if (GetWindowLong(hwndOwner, GWL_EXSTYLE) & WS_EX_TOPMOST) return TRUE; } return FALSE; } /*---------------------------------------------------------- Purpose: IMenuBand::TranslateMenuMessage method The main app's window proc calls this so the menuband catches messages that are dispatched from a different message pump (than the thread's main pump). Translates messages specially for menubands. Some messages are processed while the menuband is active. Others are only processed when it is not. Messages that are not b/t WM_KEYFIRST and WM_KEYLAST are handled here (the browser does not send these messages to IInputObject:: TranslateAcceleratorIO). Returns: S_OK if message is processed */ STDMETHODIMP CMenuBand::TranslateMenuMessage(MSG * pmsg, LRESULT * plRet) { ASSERT(IS_VALID_WRITE_PTR(pmsg, MSG)); #ifdef DEBUG if (g_dwDumpFlags & DF_TRANSACCELIO) DumpMsg(TEXT("CMB::TMM"), pmsg); #endif switch (pmsg->message) { case WM_SYSCHAR: // In certain doc-obj situations, the OLE message filter (??) // grabs this before the main thread's message pump gets a // whack at it. So we handle it here too, in case we're in // this scenario. // // See the comments in IsMenuMessage regarding this message. return _OnSysChar(pmsg, FALSE); case WM_INITMENUPOPUP: // Normally the LOWORD(lParam) is the index of the menu that // is being popped up. TrackPopupMenu (which CMenuISF uses) // always sends this message with an index of 0. This breaks // clients (like DefView) who check this value. We need to // massage this value if we find we're the source of the // WM_INITMENUPOPUP. // // (This is not in TranslateAcceleratorIO b/c TrackPopupMenu's // message pump does not call it. The wndproc must forward // the message to this function for us to get it.) if (_fInSubMenu && _pmtbTracked) { // Massage lParam to use the right index int iPos = ToolBar_CommandToIndex(_pmtbTracked->_hwndMB, _nItemCur); pmsg->lParam = MAKELPARAM(iPos, HIWORD(pmsg->lParam)); // Return S_FALSE so this message will still be handled } break; case WM_UPDATEUISTATE: if (_pmbState) { // we don't care about UISF_HIDEFOCUS if (UISF_HIDEACCEL == HIWORD(pmsg->wParam)) _pmbState->SetKeyboardCue(UIS_CLEAR == LOWORD(pmsg->wParam) ? TRUE : FALSE); } break; case WM_ACTIVATE: { CMBMsgFilter* pmf = GetMessageFilter(); // Debug note: to debug menubands on ntsd, set the prototype // flag accordingly. This will keep menubands from going // away the moment the focus changes. // Becomming inactive? if (WA_INACTIVE == LOWORD(pmsg->wParam)) { // Yes; Free up the global object // Athena fix (lamadio) 08.02.1998: Athena uses menubands. Since they // have a band per window in one thread, we needed a mechanism to switch // between them. So we used the Msgfilter to forward messages. Since there // are multiple windows, we need to set correct one. // But, On a deactivate, we need to NULL it out incase a window, // running in the same thread, has normal USER menu. We don't want to steal // their messages. if (pmf->GetTopMostPtr() == this) pmf->SetTopMost(NULL); pmf->DisEngage(_pmbState->GetContext()); HWND hwndLostTo = (HWND)(pmsg->lParam); // We won't bail on the menus if we're loosing activation to a child. if (!IsAncestor(hwndLostTo, _pmbState->GetWorkerWindow(NULL))) { if (_fMenuMode && !(g_dwPrototype & PF_USINGNTSD) && !_fDragEntered) { // Being deactivated. Bail out of menus. // (Only the toplevel band gets this message.) if (_fInSubMenu) { IMenuPopup* pmp = _pmpSubMenu; if (_pmpTrackPopup) pmp = _pmpTrackPopup; ASSERT(pmp); // This should be valid. If not, someone messed up. pmp->OnSelect(MPOS_FULLCANCEL); } _CancelMode(MPOS_FULLCANCEL); } } } else if (WA_ACTIVE == LOWORD(pmsg->wParam) || WA_CLICKACTIVE == LOWORD(pmsg->wParam)) { // If I have activation, the Worker Window needs to be bottom... // // NOTE: Don't do this if the worker window has a topmost owner // (such as the tray). Setting a window to HWND_NOTOPMOST moves // its owner windows to HWND_NOTOPMOST as well, which in this case // was breaking the tray's "always on top" feature. // HWND hwndWorker = _pmbState->GetWorkerWindow(NULL); if (hwndWorker && !HasWindowTopmostOwner(hwndWorker) && !_fDragEntered) SetWindowPos(hwndWorker, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE); // Set the context because when a menu heirarchy becomes active because the // subclassed HWND becomes active, we need to reenable the message hook. pmf->SetContext(this, TRUE); // When we get reactivated, we need to position ourself above the start bar. Exec(&CGID_MenuBand, MBANDCID_REPOSITION, TRUE, NULL, NULL); // Becomming activated. We need to reengage the message hook so that // we get the correct messages. pmf->ReEngage(_pmbState->GetContext()); // Are we in menu mode? if (_fMenuMode) { // Need to reengage some things. // Take the capture back because we have lost it to context menus or dialogs. pmf->RetakeCapture(); } pmf->SetTopMost(this); } // // Memphis and NT5 grey their horizontal menus when the windows is inactive. // if (!_fVertical && _pmtbMenu) { // This needs to stay here because of the above check... if (WA_INACTIVE == LOWORD(pmsg->wParam)) { _fAppActive = FALSE; } else { _fAppActive = TRUE; } // Reduces flicker by using this instead of an InvalidateWindow/UpdateWindow Pair RedrawWindow(_pmtbMenu->_hwndMB, NULL, NULL, RDW_ERASE | RDW_INVALIDATE); } } break; case WM_SYSCOMMAND: if ( !_fMenuMode ) { switch (pmsg->wParam & 0xFFF0) { case SC_KEYMENU: // The user either hit the Alt key by itself or Alt-space. // If it was Alt-space, let DefWindowProc handle it so the // system menu comes up. Otherwise, we'll handle it to // toggle the menuband. // Was it Alt-space? if (_fAltSpace) { // Yes; let it go TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Caught the Alt-space", DBG_THIS); _fAltSpace = FALSE; } else if (_fShow) { // No; activate the menu TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Caught the WM_SYSCOMMAND, SC_KEYMENU", DBG_THIS); UIActivateIO(TRUE, NULL); // We sit in a modal loop here because typically // WM_SYSCOMMAND doesn't return until the menu is finished. // while (_fMenuMode) { MSG msg; if (GetMessage(&msg, NULL, 0, 0)) { if ( S_OK != IsMenuMessage(&msg) ) { TranslateMessage(&msg); DispatchMessage(&msg); } } } *plRet = 0; return S_OK; // Caller shouldn't handle this } break; } } break; default: // We only want to process the pane messages in IsMenuMessage when there is no // top level HWND. This is for the Deskbar menus. Outlook Express needs the // TranslateMenuMessage entry point if (_pmbState->GetSubclassedHWND() != NULL) return _ProcessMenuPaneMessages(pmsg); break; } return S_FALSE; } /*---------------------------------------------------------- Purpose: IObjectWithSite::SetSite method Called by the menusite to host this band. Since the menuband contains two toolbars, we set their parent window to be the site's hwnd. */ STDMETHODIMP CMenuBand::SetSite(IUnknown* punkSite) { // Do this first because SetParent needs to query to the top level browser for // sftbar who queries to the top level browser to get the drag and drop window. HRESULT hres = SUPERCLASS::SetSite(punkSite); if (_psmcb && _fTopLevel && !(_dwFlags & SMINIT_NOSETSITE)) IUnknown_SetSite(_psmcb, punkSite); IUnknown_GetWindow(punkSite, &_hwndParent); // Need this for Closing an expanded vertical menu. Start Menu knows to do this when it's top level, // but the Favorites needs to know when it's parent is the horizontal menu. VARIANT var = {0}; if (SUCCEEDED(IUnknown_QueryServiceExec(punkSite, SID_SMenuBandParent, &CGID_MenuBand, MBANDCID_ISVERTICAL, 0, NULL, &var)) && var.boolVal == VARIANT_FALSE) { ASSERT(VT_BOOL == var.vt); _fParentIsHorizontal = TRUE; } if (_fNoBorder) { IUnknown_QueryServiceExec(punkSite, SID_SMenuPopup, &CGID_MENUDESKBAR, MBCID_NOBORDER, _fNoBorder, NULL, NULL); } // Tell the toolbars who their new parent is if (_pmtbMenu) _pmtbMenu->SetParent(_hwndParent); if (_pmtbShellFolder) _pmtbShellFolder->SetParent(_hwndParent); return hres; } /*---------------------------------------------------------- Purpose: IShellMenu::Initialize method */ STDMETHODIMP CMenuBand::Initialize(IShellMenuCallback* psmcb, UINT uId, UINT uIdAncestor, DWORD dwFlags) { // Initalized can be called with NULL values to only set some of them. // Default to Vertical if (!(dwFlags & SMINIT_HORIZONTAL) && !(dwFlags & SMINIT_VERTICAL) && !(dwFlags & SMINIT_MULTICOLUMN)) dwFlags |= SMINIT_VERTICAL; HRESULT hr = _Initialize(dwFlags); if (SUCCEEDED(hr)) { DEBUG_CODE( _fInitialized = TRUE; ); if (uIdAncestor != ANCESTORDEFAULT) _uIdAncestor = uIdAncestor; if (_uId != -1) _uId = uId; if (psmcb) { if (!SHIsSameObject(psmcb, _psmcb)) { if (_punkSite && _fTopLevel && !(dwFlags & SMINIT_NOSETSITE)) IUnknown_SetSite(_psmcb, NULL); ATOMICRELEASE(_psmcb); _psmcb = psmcb; _psmcb->AddRef(); // We do not set the site in case this callback is shared between 2 bands (Menubar/Chevron menu) if (_punkSite && _fTopLevel && !(dwFlags & SMINIT_NOSETSITE)) IUnknown_SetSite(_psmcb, _punkSite); // Only call this if we're setting a new one. Pass the address of the user associated // data section. This is so that the callback can associate data with this pane only _CallCB(SMC_CREATE, 0, (LPARAM)&_pvUserData); } } } return hr; } /*---------------------------------------------------------- Purpose: IShellMenu::GetMenuInfo method */ STDMETHODIMP CMenuBand::GetMenuInfo(IShellMenuCallback** ppsmc, UINT* puId, UINT* puIdAncestor, DWORD* pdwFlags) { if (ppsmc) { *ppsmc = _psmcb; if (_psmcb) ((IShellMenuCallback*)*ppsmc)->AddRef(); } if (puId) *puId = _uId; if (puIdAncestor) *puIdAncestor = _uIdAncestor; if (pdwFlags) *pdwFlags = _dwFlags; return NOERROR; } HRESULT CMenuBand::_AddToolbar(CMenuToolbarBase* pmtb, DWORD dwFlags) { if (_pszTheme) { pmtb->SetTheme(_pszTheme); } if (_fNoBorder) { pmtb->SetNoBorder(_fNoBorder); } pmtb->SetSite(SAFECAST(this, IMenuBand*)); HRESULT hr = S_OK; if (_hwndParent) hr = pmtb->CreateToolbar(_hwndParent); if (SUCCEEDED(hr)) { // Treat this like a two-element stack, where this function // behaves like a "push". The one additional trick is we // could be pushing onto the top or the bottom of the "stack". if (dwFlags & SMSET_BOTTOM) { if (_pmtbBottom) { // I don't need to release, because _pmtbTop and _pmtbBottom are aliases for // _pmtbShellFolder and _pmtbMenu _pmtbTop = _pmtbBottom; _pmtbTop->SetToTop(TRUE); } _pmtbBottom = pmtb; _pmtbBottom->SetToTop(FALSE); } else // Default to Top... { if (_pmtbTop) { _pmtbBottom = _pmtbTop; _pmtbBottom->SetToTop(FALSE); } _pmtbTop = pmtb; _pmtbTop->SetToTop(TRUE); } // _pmtbBottom should never be the only toolbar that exists in the menuband. if (!_pmtbTop) _pmtbTop = _pmtbBottom; // The menuband determines there is a single toolbar by comparing // the bottom with the top. So make the bottom the same if necessary. if (!_pmtbBottom) _pmtbBottom = _pmtbTop; } return hr; } /*---------------------------------------------------------- Purpose: IShellMenu::GetShellFolder method */ STDMETHODIMP CMenuBand::GetShellFolder(DWORD* pdwFlags, LPITEMIDLIST* ppidl, REFIID riid, void** ppvObj) { HRESULT hres = E_FAIL; if (_pmtbShellFolder) { *pdwFlags = _pmtbShellFolder->GetFlags(); hres = S_OK; if (ppvObj) { // HACK HACK. this should QI for a mnfolder specific interface to do this. hres = _pmtbShellFolder->GetShellFolder(ppidl, riid, ppvObj); } } return hres; } /*---------------------------------------------------------- Purpose: IShellMenu::SetShellFolder method */ STDMETHODIMP CMenuBand::SetShellFolder(IShellFolder* psf, LPCITEMIDLIST pidlFolder, HKEY hKey, DWORD dwFlags) { ASSERT(_fInitialized); HRESULT hr = E_OUTOFMEMORY; // If we're processing a change notify, we cannot do anything that will modify state. // NOTE: if we don't have a state, we can't possibly processing a change notify if (_pmbState && _pmbState->IsProcessingChangeNotify()) return E_PENDING; // Only one shellfolder menu can exist per menuband. Additionally, // a shellfolder menu can exist either at the top of the menu, or // at the bottom (when it coexists with a static menu). // Is there already a shellfolder menu? if (_pmtbShellFolder) { IShellFolderBand* psfb; _pmtbShellFolder->QueryInterface(IID_PPV_ARG(IShellFolderBand, &psfb)); ASSERTMSG(psfb != NULL, "CMenuBand::SetShellFolder should have gotten interface"); hr = psfb->InitializeSFB(psf, pidlFolder); psfb->Release(); } else { _pmtbShellFolder = new CMenuSFToolbar(this, psf, pidlFolder, hKey, dwFlags); if (_pmtbShellFolder) { hr = _AddToolbar(_pmtbShellFolder, dwFlags); } } return hr; } /*---------------------------------------------------------- Purpose: IMenuBand::GetMenu method */ STDMETHODIMP CMenuBand::GetMenu(HMENU* phmenu, HWND* phwnd, DWORD* pdwFlags) { HRESULT hres = E_FAIL; // HACK HACK. this should QI for a menustatic specific interface to do this. if (_pmtbMenu) hres = _pmtbMenu->GetMenu(phmenu, phwnd, pdwFlags); return hres; } /*---------------------------------------------------------- Purpose: IMenuBand::SetMenu method */ STDMETHODIMP CMenuBand::SetMenu(HMENU hmenu, HWND hwnd, DWORD dwFlags) { // Passing a NULL hmenu is valid. It means destroy our menu object. ASSERT(_fInitialized); HRESULT hr = E_FAIL; // Only one static menu can exist per menuband. Additionally, // a static menu can exist either at the top of the menu, or // at the bottom (when it coexists with a shellfolder menu). // Is there already a static menu? if (_pmtbMenu) { // Since we're merging in a new menu, make sure to update the cache... _hmenu = hmenu; // Yes // HACK HACK. this should QI for a menustatic specific interface to do this. return _pmtbMenu->SetMenu(hmenu, hwnd, dwFlags); } else { // This is to work around a problem in the interface definintion: We have // no method of setting the Subclassed HWND outside of a SetMenu. So I'm just piggybacking // off of this. A better fix would be to introduce IMenuBand2::SetSubclass(HWND). IMenuBand // actually implements the "Subclassing", so extending this interface would be worthwhile. _hwndMenuOwner = hwnd; if (_fTopLevel) { _pmbState->SetSubclassedHWND(hwnd); } if (hmenu) { _hmenu = hmenu; _pmtbMenu = new CMenuStaticToolbar(this, hmenu, hwnd, _uId, dwFlags); if (_pmtbMenu) { hr = _AddToolbar(_pmtbMenu, dwFlags); } else hr = E_OUTOFMEMORY; } } return hr; } /*---------------------------------------------------------- Purpose: IShellMenu::SetMenuToolbar method */ STDMETHODIMP CMenuBand::SetMenuToolbar(IUnknown* punk, DWORD dwFlags) { HRESULT hr = E_INVALIDARG; CMenuToolbarBase* pmtb; if (punk && SUCCEEDED(punk->QueryInterface(CLSID_MenuToolbarBase, (void**)&pmtb))) { ASSERT(_pmtbShellFolder == NULL); _pmtbShellFolder = pmtb; hr = _AddToolbar(pmtb, dwFlags); } return hr; } /*---------------------------------------------------------- Purpose: IShellMenu::InvalidateItem method */ STDMETHODIMP CMenuBand::InvalidateItem(LPSMDATA psmd, DWORD dwFlags) { HRESULT hres = S_FALSE; // If psmd is NULL, we need to just dump the toolbars and do a full reset. if (psmd == NULL) { // If we're processing a change notify, we cannot do anything that will modify state. if (_pmbState && _pmbState->IsProcessingChangeNotify()) return E_PENDING; if (_pmbState) _pmbState->PushChangeNotify(); // Tell the callback we're refreshing so that it can // reset any cached state _CallCB(SMC_REFRESH); // Reinitialize the callback if requested if (dwFlags & SMINV_INITMENU) { _CallCB(SMC_INITMENU); } _fExpanded = FALSE; // We don't need to refill if the caller only wanted to // refresh the sub menus. // Refresh the Shell Folder first because // It may have no items after it's done, and the // menuband may rely on this to add a seperator if (_pmtbShellFolder) _pmtbShellFolder->v_Refresh(); // Refresh the Static menu if (_pmtbMenu) _pmtbMenu->v_Refresh(); if (_pmpSubMenu) { _fInSubMenu = FALSE; IUnknown_SetSite(_pmpSubMenu, NULL); ATOMICRELEASE(_pmpSubMenu); } if (_pmbState) _pmbState->PopChangeNotify(); } else { if (_pmtbTop) hres = _pmtbTop->v_InvalidateItem(psmd, dwFlags); // We refresh everything at this level if the psmd is null if (_pmtbBottom && hres != S_OK) hres = _pmtbBottom->v_InvalidateItem(psmd, dwFlags); } return hres; } /*---------------------------------------------------------- Purpose: IShellMenu::GetState method */ STDMETHODIMP CMenuBand::GetState(LPSMDATA psmd) { if (_pmtbTracked) return _pmtbTracked->v_GetState(-1, psmd); // todo: might want to put stuff from _CallCB (below) in here return E_FAIL; } HRESULT CMenuBand::_CallCB(DWORD dwMsg, WPARAM wParam, LPARAM lParam) { if (!_psmcb) return S_FALSE; // We don't need to check callback mask here because these are not maskable events. SMDATA smd = {0}; smd.punk = SAFECAST(this, IShellMenu*); smd.uIdParent = _uId; smd.uIdAncestor = _uIdAncestor; smd.hwnd = _hwnd; smd.hmenu = _hmenu; smd.pvUserData = _pvUserData; if (_pmtbShellFolder) _pmtbShellFolder->GetShellFolder(&smd.pidlFolder, IID_PPV_ARG(IShellFolder, &smd.psf)); HRESULT hres = _psmcb->CallbackSM(&smd, dwMsg, wParam, lParam); ILFree(smd.pidlFolder); if (smd.psf) smd.psf->Release(); return hres; } /*---------------------------------------------------------- Purpose: IInputObject::TranslateAcceleratorIO This is called by the base browser only when the menuband "has the focus", and only for messages b/t WM_KEYFIRST and WM_KEYLAST. This isn't very useful for menubands. See the explanations in GetMsgFilterCB, IsMenuMessage and TranslateMenuMessage. In addition, menubands cannot ever have the activation, so this method should never be called. Returns S_OK if handled. */ STDMETHODIMP CMenuBand::TranslateAcceleratorIO(LPMSG pmsg) { AssertMsg(0, TEXT("Menuband has the activation but it shouldn't!")); return S_FALSE; } /*---------------------------------------------------------- Purpose: IInputObject::HasFocusIO */ STDMETHODIMP CMenuBand::HasFocusIO() { // We consider a menuband has the focus even if it has submenus // that are currently cascaded out. All menubands in the chain // have the focus. return _fMenuMode ? S_OK : S_FALSE; } /*---------------------------------------------------------- Purpose: IMenuPopup::SetSubMenu method The child menubar calls us with its IMenuPopup pointer. */ STDMETHODIMP CMenuBand::SetSubMenu(IMenuPopup * pmp, BOOL fSet) { ASSERT(IS_VALID_CODE_PTR(pmp, IMenuPopup)); if (fSet) { _fInSubMenu = TRUE; } else { if (_pmtbTracked) { _pmtbTracked->PopupClose(); } _fInSubMenu = FALSE; _nItemSubMenu = -1; } return S_OK; } HRESULT CMenuBand::_SiteSetSubMenu(IMenuPopup * pmp, BOOL bSet) { HRESULT hres; IMenuPopup * pmpSite; hres = IUnknown_QueryService(_punkSite, SID_SMenuPopup, IID_PPV_ARG(IMenuPopup, &pmpSite)); if (SUCCEEDED(hres)) { hres = pmpSite->SetSubMenu(pmp, bSet); pmpSite->Release(); } return hres; } /*---------------------------------------------------------- Purpose: Tell the GetMsg filter that this menuband is ready to listen to messages. */ HRESULT CMenuBand::_EnterMenuMode(void) { ASSERT(!_fMenuMode); // Must not push onto stack more than once if (g_dwProfileCAP & 0x00002000) StartCAP(); DEBUG_CODE( _nMenuLevel = g_nMenuLevel++; ) _fMenuMode = TRUE; _fInSubMenu = FALSE; _nItemMove = -1; _fCascadeAnimate = TRUE; _hwndFocusPrev = NULL; if (_fTopLevel) { // REVIEW (scotth): some embedded controls (like the surround // video ctl on the carpoint website) have another thread that // eats all the messages when the control has the focus. // This prevents us from getting any messages once we're in // menu mode. I don't understand why USER menus work yet. // One way to work around this bug is to detect this case and // set the focus to our main window for the duration. if (GetWindowThreadProcessId(GetFocus(), NULL) != GetCurrentThreadId()) { IShellBrowser* psb; if (SUCCEEDED(QueryService(SID_STopLevelBrowser, IID_PPV_ARG(IShellBrowser, &psb)))) { HWND hwndT; psb->GetWindow(&hwndT); _hwndFocusPrev = SetFocus(hwndT); psb->Release(); } } _hCursorOld = GetCursor(); SetCursor(g_hCursorArrow); HideCaret(NULL); } _SiteSetSubMenu(this, TRUE); if (_pmtbTop) { HWND hwnd = _pmtbTop->_hwndMB; if (!_fVertical && -1 == _nItemNew) { // The Alt key always highlights the first menu item initially SetTracked(_pmtbTop); ToolBar_SetHotItem(hwnd, 0); NotifyWinEvent(EVENT_OBJECT_FOCUS, _pmtbTop->_hwndMB, OBJID_CLIENT, GetIndexFromChild(TRUE, 0)); } _pmtbTop->Activate(TRUE); // The toolbar usually tracks mouse events. However, as the mouse // moves over submenus, we still want the parent menubar to // behave as if it has retained the focus (that is, keep the // last selected item highlighted). This also prevents the toolbar // from handling WM_MOUSELEAVE messages unnecessarily. ToolBar_SetAnchorHighlight(hwnd, TRUE); TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Entering menu mode", DBG_THIS); NotifyWinEvent(_fVertical? EVENT_SYSTEM_MENUPOPUPSTART: EVENT_SYSTEM_MENUSTART, hwnd, OBJID_CLIENT, CHILDID_SELF); } if (_pmtbBottom) { _pmtbBottom->Activate(TRUE); ToolBar_SetAnchorHighlight(_pmtbBottom->_hwndMB, TRUE); // Turn off anchoring } GetMessageFilter()->Push(_pmbState->GetContext(), this, _punkSite); return S_OK; } void CMenuBand::_ExitMenuMode(void) { _fMenuMode = FALSE; _nItemCur = -1; _fPopupNewMenu = FALSE; _fInitialSelect = FALSE; CMBMsgFilter* pmf = GetMessageFilter(); if (_pmtbTop) { HWND hwnd = _pmtbTop->_hwndMB; ToolBar_SetAnchorHighlight(hwnd, FALSE); // Turn off anchoring if (!_fVertical) { // Use the first item, since we're assuming every menu must have // at least one item _pmtbTop->v_SendMenuNotification(0, TRUE); // The user may have clicked outside the menu, which would have // cancelled it. But since we set the ANCHORHIGHLIGHT attribute, // the toolbar won't receive a message to cause it to // remove the highlight. So do it explicitly now. SetTracked(NULL); UpdateWindow(hwnd); } _pmtbTop->Activate(FALSE); NotifyWinEvent(_fVertical? EVENT_SYSTEM_MENUPOPUPEND: EVENT_SYSTEM_MENUEND, hwnd, OBJID_CLIENT, CHILDID_SELF); } if (_pmtbBottom) { _pmtbBottom->Activate(FALSE); ToolBar_SetAnchorHighlight(_pmtbBottom->_hwndMB, FALSE); // Turn off anchoring } pmf->Pop(_pmbState->GetContext()); _SiteSetSubMenu(this, FALSE); if (_fTopLevel) { SetCursor(_hCursorOld); ShowCaret(NULL); pmf->SetContext(this, FALSE); // We do this here, because ShowDW(FALSE) does not get called on the // top level menu band. This resets the state, so that the accelerators // are not shown. if (_pmbState) _pmbState->SetKeyboardCue(FALSE); // Tell the menus to update their state to the current global cue state. if (_pmtbTop) _pmtbTop->SetKeyboardCue(); if (_pmtbTop != _pmtbBottom && _pmtbBottom) _pmtbBottom->SetKeyboardCue(); } if (_hwndFocusPrev) SetFocus(_hwndFocusPrev); if (_fTopLevel) { // // The top-level menu has gone away. Win32 focus and ui-activation don't // actually change when this happens, so the browser and focused dude have // no idea that something happened and won't generate any AA event. So, we // do it here for them. Note that if there was a selection inside the focused // dude, we'll lose it. This is the best we can do for now, as we don't // currently have a way to tell the focused/ui-active guy (who knows about the // current selection) to reannounce focus. // HWND hwndFocus = GetFocus(); NotifyWinEvent(EVENT_OBJECT_FOCUS, hwndFocus, OBJID_CLIENT, CHILDID_SELF); } TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): Exited menu mode", DBG_THIS); DEBUG_CODE( g_nMenuLevel--; ) DEBUG_CODE( _nMenuLevel = -1; ) if (g_dwProfileCAP & 0x00002000) StopCAP(); } /*---------------------------------------------------------- Purpose: IInputObject::UIActivateIO Menubands CANNOT take the activation. Normally a band would return S_OK and call the site's OnFocusChangeIS method, so that its TranslateAcceleratorIO method would receive keyboard messages. However, menus are different. The window/toolbar that currently has the activation must retain that activation when the menu pops down. Because of this, menubands use a GetMessage filter to intercept messages. */ STDMETHODIMP CMenuBand::UIActivateIO(BOOL fActivate, LPMSG lpMsg) { HRESULT hres; ASSERT(NULL == lpMsg || IS_VALID_WRITE_PTR(lpMsg, MSG)); if (lpMsg != NULL) { // don't allow TAB to band (or any other 'non-explicit' activation). // (if we just cared about TAB we'd check IsVK_TABCycler). // all kinds of badness would result if we did. // the band can't take focus (see above), so it can't obey the // UIAct/OnFocChg rules (e.g. can't call OnFocusChangeIS), so // our basic activation-tracking assumptions would be broken. return S_FALSE; } if (fActivate) { TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): UIActivateIO(%d)", DBG_THIS, fActivate); if (!_fMenuMode) { _EnterMenuMode(); // The toplevel menuband does not set the real activation. // But the children do, so activation can be communicated // with the parent menuband. if (_fVertical) { IUnknown_OnFocusChangeIS(_punkSite, SAFECAST(this, IInputObject*), TRUE); } else { IUnknown_Exec(_punkSite, &CGID_Theater, THID_TOOLBARACTIVATED, 0, NULL, NULL); } } if (_fPopupNewMenu) { _nItemCur = _nItemNew; ASSERT(-1 != _nItemCur); ASSERT(_pmtbTracked); _fPopupNewMenu = FALSE; _nItemNew = -1; // Popup a menu hres = _pmtbTracked->PopupOpen(_nItemCur); if (FAILED(hres)) { // Don't fail the activation TraceMsg(TF_ERROR, "%d (pmb=%#08lx): PopupOpen failed", DBG_THIS); MessageBeep(MB_OK); } else if (S_FALSE == hres) { // The submenu was modal and is finished now _ExitMenuMode(); } } } else if (_fMenuMode) { TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): UIActivateIO(%d)", DBG_THIS, fActivate); ASSERT( !_fInSubMenu ); if (!_fTopLevel) IUnknown_OnFocusChangeIS(_punkSite, SAFECAST(this, IInputObject*), FALSE); _ExitMenuMode(); } return S_FALSE; } /*---------------------------------------------------------- Purpose: IDeskBand::GetBandInfo method */ HRESULT CMenuBand::GetBandInfo(DWORD dwBandID, DWORD fViewMode, DESKBANDINFO* pdbi) { HRESULT hres = NOERROR; _dwBandID = dwBandID; // critical for perf! (BandInfoChanged) pdbi->dwMask &= ~DBIM_TITLE; // no title (ever, for now) // We expect that _pmtbBottom should never be the only toolbar // that exists in the menuband. ASSERT(NULL == _pmtbBottom || _pmtbTop); pdbi->dwModeFlags = DBIMF_USECHEVRON; if (_pmtbTop) { // If the buttons need to be updated in the toolbars, the we should // do this before we start asking them about their sizes.... if (_fForceButtonUpdate) { _UpdateButtons(); } if (_fVertical) { pdbi->ptMaxSize.y = 0; pdbi->ptMaxSize.x = 0; SIZE size = {0}; if (_pmtbMenu) { // size param zero here => it's just an out param _pmtbMenu->GetSize(&size); // HACKHACK (lamadio): On downlevel, LARGE metrics mode causes // Start menu to push the programs menu item off screen. if (size.cy > (3 * GetSystemMetrics(SM_CYSCREEN) / 4)) { Exec(&CGID_MenuBand, MBANDCID_SETICONSIZE, ISFBVIEWMODE_SMALLICONS, NULL, NULL); size.cx = 0; size.cy = 0; _pmtbMenu->GetSize(&size); } pdbi->ptMaxSize.y = size.cy; pdbi->ptMaxSize.x = size.cx; } if (_pmtbShellFolder) { // size param should be non-zero here => it's an in/out param _pmtbShellFolder->GetSize(&size); pdbi->ptMaxSize.y += size.cy + ((_pmtbMenu && !_fExpanded)? 1 : 0); // Minor sizing problem pdbi->ptMaxSize.x = max(size.cx, pdbi->ptMaxSize.x); } pdbi->ptMinSize = pdbi->ptMaxSize; } else { HWND hwnd = _pmtbTop->_hwndMB; ShowDW(TRUE); SIZE rgSize; if ( SendMessage( hwnd, TB_GETMAXSIZE, 0, (LPARAM) &rgSize )) { pdbi->ptActual.y = rgSize.cy; SendMessage(hwnd, TB_GETIDEALSIZE, FALSE, (LPARAM)&pdbi->ptActual); } // make our min size identical to the size of the first button // (we're assuming that the toolbar has at least one button) RECT rc; SendMessage(hwnd, TB_GETITEMRECT, 0, (WPARAM)&rc); pdbi->ptMinSize.x = RECTWIDTH(rc); pdbi->ptMinSize.y = RECTHEIGHT(rc); } } return hres; } /*---------------------------------------------------------- Purpose: IOleService::Exec method */ STDMETHODIMP CMenuBand::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdExecOpt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut) { // Don't do anything if we're closing. if (_fClosing) return E_FAIL; if (pguidCmdGroup == NULL) { /*NOTHING*/ } else if (IsEqualGUID(CGID_MENUDESKBAR, *pguidCmdGroup)) { switch (nCmdID) { case MBCID_GETSIDE: if (pvarargOut) { BOOL fOurChoice = FALSE; pvarargOut->vt = VT_I4; if (!_fTopLevel) { // if we are not the top level menu, we // must continue with the direction our parent was in IMenuPopup* pmpParent; IUnknown_QueryService(_punkSite, SID_SMenuPopup, IID_PPV_ARG(IMenuPopup, &pmpParent)); if (pmpParent) { if (FAILED(IUnknown_Exec(pmpParent, pguidCmdGroup, nCmdID, nCmdExecOpt, pvarargIn, pvarargOut))) fOurChoice = TRUE; pmpParent->Release(); } } else fOurChoice = TRUE; if (!fOurChoice) { // only use the parent's side hint if it is in the same orientation (ie, horizontal menubar to vertical popup // means we need to make a new choice) BOOL fParentVertical = (pvarargOut->lVal == MENUBAR_RIGHT || pvarargOut->lVal == MENUBAR_LEFT); if (BOOLIFY(_fVertical) != BOOLIFY(fParentVertical)) fOurChoice = TRUE; } if (fOurChoice) { if (_fVertical) { HWND hWndMenuBand; // // The MenuBand is Mirrored , then start the first Menu Window // as Mirrored. [samera] // if ((SUCCEEDED(GetWindow(&hWndMenuBand))) && (IS_WINDOW_RTL_MIRRORED(hWndMenuBand)) ) pvarargOut->lVal = MENUBAR_LEFT; else pvarargOut->lVal = MENUBAR_RIGHT; } else pvarargOut->lVal = MENUBAR_BOTTOM; } } return S_OK; } } else if (IsEqualGUID(CGID_MenuBand, *pguidCmdGroup)) { switch (nCmdID) { case MBANDCID_GETFONTS: if (pvarargOut) { if (EVAL(_pmbm)) { // this is not marshal-safe, but we're inproc pvarargOut->vt = VT_UNKNOWN; _pmbm->QueryInterface(IID_PPV_ARG(IUnknown, &pvarargOut->punkVal)); return S_OK; } else return E_FAIL; } else return E_INVALIDARG; break; case MBANDCID_SETFONTS: if (pvarargIn && VT_UNKNOWN == pvarargIn->vt && pvarargIn->punkVal) { // this is not marshal-safe, but we're inproc ATOMICRELEASE(_pmbm); pvarargIn->punkVal->QueryInterface(CLSID_MenuBandMetrics, (void**)&_pmbm); _fForceButtonUpdate = TRUE; // Force Update of Toolbars: if (_pmtbMenu) _pmtbMenu->SetMenuBandMetrics(_pmbm); if (_pmtbShellFolder) _pmtbShellFolder->SetMenuBandMetrics(_pmbm); } else return E_INVALIDARG; break; case MBANDCID_RECAPTURE: GetMessageFilter()->RetakeCapture(); break; case MBANDCID_NOTAREALSITE: _fParentIsNotASite = BOOLIFY(nCmdExecOpt); break; case MBANDCID_ITEMDROPPED: { _fDragEntered = FALSE; HWND hwndWorker = _pmbState->GetWorkerWindow(NULL); if (hwndWorker && !HasWindowTopmostOwner(hwndWorker)) SetWindowPos(hwndWorker, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE); } break; case MBANDCID_DRAGENTER: _fDragEntered = TRUE; break; case MBANDCID_DRAGLEAVE: _fDragEntered = FALSE; break; case MBANDCID_SELECTITEM: { int iPos = nCmdExecOpt; // If they are passing vararg in, then this is an ID, not a position if (pvarargIn && pvarargIn->vt == VT_I4) { _nItemNew = pvarargIn->lVal; _fPopupNewItemOnShow = TRUE; } // This can be called outside of a created band. if (_pmtbTop) { if (iPos == MBSI_NONE) { SetTracked(NULL); } else { CMenuToolbarBase* pmtb = (iPos == MBSI_LASTITEM) ? _pmtbBottom : _pmtbTop; ASSERT(pmtb); SetTracked(pmtb); _pmtbTracked->SetHotItem(1, iPos, -1, HICF_OTHER); // If the new hot item is in the obscured part of the menu, then the // above call will have reentered & nulled out _pmtbTracked (since we // drop down the chevron menu if the new hot item is obscured). So we // need to revalidate _pmtbTracked. if (!_pmtbTracked) break; NotifyWinEvent(EVENT_OBJECT_FOCUS, _pmtbTracked->_hwndMB, OBJID_CLIENT, GetIndexFromChild(TRUE, iPos)); } } } break; case MBANDCID_KEYBOARD: // If we've been executed because of a keyboard, then set the global // state to reflect that. This is sent by MenuBar when it's ::Popup // member is called with the flag MPPF_KEYBOARD. This is for start menu. if (_pmbState) _pmbState->SetKeyboardCue(TRUE); break; case MBANDCID_POPUPITEM: if (pvarargIn && VT_I4 == pvarargIn->vt) { // we don't want to popup a sub menu if we're tracking a context menu... if ( !((_pmtbBottom && _pmtbBottom->v_TrackingSubContextMenu()) || (_pmtbTop && _pmtbTop->v_TrackingSubContextMenu()))) { // No tracked item? Well default to the top (For the chevron menu) if (!_pmtbTracked) { SetTracked(_pmtbTop); } // We don't want to display the sub menu if we're not shown. // We do this because we could have been dismissed before the message // was routed. if (_fShow && _pmtbTracked) { int iItem; int iPos; if (nCmdExecOpt & MBPUI_ITEMBYPOS) { iPos = pvarargIn->lVal; iItem = GetButtonCmd(_pmtbTracked->_hwndMB, pvarargIn->lVal); } else { iPos = ToolBar_CommandToIndex(_pmtbTracked->_hwndMB, pvarargIn->lVal); iItem = pvarargIn->lVal; } if (nCmdExecOpt & MBPUI_SETITEM) { // Set the hot item explicitly since this can be // invoked by the keyboard and the mouse could be // anywhere. _pmtbTracked->SetHotItem(1, iPos, -1, HICF_OTHER); // If the new hot item is in the obscured part of the menu, then the // above call will have reentered & nulled out _pmtbTracked (since we // drop down the chevron menu if the new hot item is obscured). So we // need to revalidate _pmtbTracked. if (!_pmtbTracked) break; NotifyWinEvent(EVENT_OBJECT_FOCUS, _pmtbTracked->_hwndMB, OBJID_CLIENT, GetIndexFromChild(TRUE, iPos) ); } _pmtbTracked->PopupHelper(iItem, nCmdExecOpt & MBPUI_INITIALSELECT); } } } break; case MBANDCID_ISVERTICAL: if (pvarargOut) { pvarargOut->vt = VT_BOOL; pvarargOut->boolVal = (_fVertical)? VARIANT_TRUE: VARIANT_FALSE; } break; case MBANDCID_SETICONSIZE: ASSERT(nCmdExecOpt == ISFBVIEWMODE_SMALLICONS || nCmdExecOpt == ISFBVIEWMODE_LARGEICONS); _uIconSize = nCmdExecOpt; if (_pmtbTop) _pmtbTop->v_UpdateIconSize(nCmdExecOpt, TRUE); if (_pmtbBottom) _pmtbBottom->v_UpdateIconSize(nCmdExecOpt, TRUE); break; case MBANDCID_SETSTATEOBJECT: if (pvarargIn && VT_INT_PTR == pvarargIn->vt) { _pmbState = (CMenuBandState*)pvarargIn->byref; } break; case MBANDCID_ISINSUBMENU: if (_fInSubMenu || (_pmtbTracked && _pmtbTracked->v_TrackingSubContextMenu())) return S_OK; else return S_FALSE; break; case MBANDCID_ISTRACKING: if (_pmtbTracked && _pmtbTracked->v_TrackingSubContextMenu()) return S_OK; else return S_FALSE; break; case MBANDCID_REPOSITION: // Don't reposition unless we're shown (Avoids artifacts onscreen of a bad positioning) if (_fShow) { // Don't forget to reposition US!!! IMenuPopup* pmdb; DWORD dwFlags = MPPF_REPOSITION | MPPF_NOANIMATE; // If we should force a reposition. This is so that we get // the trickle down reposition so things overlap correctly if (nCmdExecOpt) dwFlags |= MPPF_FORCEZORDER; if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SMenuPopup, IID_PPV_ARG(IMenuPopup, &pmdb)))) { pmdb->Popup(NULL, NULL, dwFlags); pmdb->Release(); } // Reposition the Tracked sub menu based on the current popped up item // since this pane has now moved // If they have a sub menu, tell them to reposition as well. if (_fInSubMenu && _pmtbTracked) { IUnknown_QueryServiceExec(_pmpSubMenu, SID_SMenuBandChild, &CGID_MenuBand, MBANDCID_REPOSITION, nCmdExecOpt, NULL, NULL); } _pmbState->PutTipOnTop(); } break; case MBANDCID_REFRESH: InvalidateItem(NULL, SMINV_REFRESH); break; case MBANDCID_EXPAND: if (_pmtbShellFolder) _pmtbShellFolder->Expand(TRUE); if (_pmtbMenu) _pmtbMenu->Expand(TRUE); break; case MBANDCID_DRAGCANCEL: // If one of the Sub bands in the menu heirarchy has the drag // (Either because of Drag enter or because of the drop) then // we do not want to cancel. if (!_pmbState->HasDrag()) _CancelMode(MPOS_FULLCANCEL); break; case MBANDCID_EXECUTE: ASSERT(pvarargIn != NULL); if (_pmtbTop && _pmtbTop->IsWindowOwner((HWND)pvarargIn->ullVal) == S_OK) _pmtbTop->v_ExecItem((int)nCmdExecOpt); else if (_pmtbBottom && _pmtbBottom->IsWindowOwner((HWND)pvarargIn->ullVal) == S_OK) _pmtbBottom->v_ExecItem((int)nCmdExecOpt); _SiteOnSelect(MPOS_EXECUTE); break; } // Don't bother passing CGID_MenuBand commands to CToolBand return S_OK; } return SUPERCLASS::Exec(pguidCmdGroup, nCmdID, nCmdExecOpt, pvarargIn, pvarargOut); } /*---------------------------------------------------------- Purpose: IDockingWindow::CloseDW method. */ STDMETHODIMP CMenuBand::CloseDW(DWORD dw) { // We don't want to destroy the band if it's cached. // That means it's the caller's respocibility to Unset this bit and call CloseDW explicitly if (_dwFlags & SMINIT_CACHED) return S_OK; if (_pmtbMenu) { _pmtbMenu->v_Close(); } if (_pmtbShellFolder) { _pmtbShellFolder->v_Close(); } if (_pmpSubMenu) { _fInSubMenu = FALSE; IUnknown_SetSite(_pmpSubMenu, NULL); ATOMICRELEASE(_pmpSubMenu); } // Since we're blowing away all of the menus, // Top and bottom are invalid _pmtbTracked = _pmtbTop = _pmtbBottom = NULL; // We don't want our base class to blow this window away. It belongs to someone else. _hwnd = NULL; _fClosing = TRUE; return SUPERCLASS::CloseDW(dw); } /*---------------------------------------------------------- Purpose: IDockingWindow::ShowDW method Notes: for the start menu (non-browser) case, we bracket* the top-level popup operation w/ a LockSetForegroundWindow so that another app can't steal the foreground and collapse our menu. (nt5:172813: don't do it for the browser case since a) we don't want to and b) ShowDW(FALSE) isn't called until exit the browser so we'd be permanently locked!) */ STDMETHODIMP CMenuBand::ShowDW(BOOL fShow) { CMBMsgFilter* pmf = GetMessageFilter(); // Prevent rentrancy when we're already shown. ASSERT((int)_fShow == BOOLIFY(_fShow)); if ((int)_fShow == BOOLIFY(fShow)) return NOERROR; HRESULT hr = SUPERCLASS::ShowDW(fShow); if (SUCCEEDED(hr)) { if (!fShow) { _fShow = FALSE; if (_fTopLevel) { if (_fVertical) { // (_fTopLevel && _fVertical) => start menu LockSetForegroundWindow(LSFW_UNLOCK); } else if (_dwFlags & SMINIT_USEMESSAGEFILTER) { pmf->SetHook(FALSE, TRUE); pmf->SetTopMost(this); } } if ((_fTopLevel || _fParentIsHorizontal) && _pmbState) { // Reset to not have the drag when we collapse. _pmbState->HasDrag(FALSE); _pmbState->SetExpand(FALSE); _pmbState->SetUEMState(0); } hr = _CallCB(SMC_EXITMENU); } else { hr = _CallCB(SMC_INITMENU); _fClosing = FALSE; _fShow = TRUE; _GetFontMetrics(); if (_fTopLevel) { // We set the context here so that the ReEngage causes the message filter // to start taking messages on a TopLevel::Show. This prevents a problem // where tracking doesn't work when switching between Favorites and Start Menu _pmbState->SetContext(this); pmf->SetContext(this, TRUE); pmf->ReEngage(_pmbState->GetContext()); if (_hwndMenuOwner && _fVertical) SetForegroundWindow(_hwndMenuOwner); if (_fVertical) { // (_fTopLevel && _fVertical) => start menu LockSetForegroundWindow(LSFW_LOCK); } else if (_dwFlags & SMINIT_USEMESSAGEFILTER) { pmf->SetHook(TRUE, TRUE); pmf->SetTopMost(this); } _pmbState->CreateFader(); } } if (_pmtbShellFolder) _pmtbShellFolder->v_Show(_fShow, _fForceButtonUpdate); // Menu needs to be last so that it can update the seperator. if (_pmtbMenu) _pmtbMenu->v_Show(_fShow, _fForceButtonUpdate); if (_fPopupNewItemOnShow) { HWND hwnd = _pmbState ? _pmbState->GetSubclassedHWND() : NULL; if (hwnd || _pmtbMenu) { PostMessage(hwnd ? hwnd : _pmtbMenu->_hwndMB, g_nMBPopupOpen, _nItemNew, MAKELPARAM(TRUE, TRUE)); } _fPopupNewItemOnShow = FALSE; } _fForceButtonUpdate = FALSE; } return hr; } void CMenuBand::_GetFontMetrics() { if (_fTopLevel) { if (!_pmbm) _pmbm = new CMenuBandMetrics(); if (_pmbm && _pmtbTop) { // We need only 1 HWND _pmbm->Init(_pmtbTop->_hwndMB); } } else if (!_pmbm) { AssertMsg(0, TEXT("When this menuband was created, someone forgot to set the metrics")); VARIANTARG vargOut; HRESULT hres = IUnknown_QueryServiceExec(_punkSite, SID_SMenuBandTop, &CGID_MenuBand, MBANDCID_GETFONTS, 0, NULL, &vargOut); if (SUCCEEDED(hres)) { if (vargOut.vt == VT_UNKNOWN && vargOut.punkVal) { vargOut.punkVal->QueryInterface(CLSID_MenuBandMetrics, (void**)&_pmbm); } VariantClear(&vargOut); } } } /*---------------------------------------------------------- Purpose: IMenuPopup::OnSelect method This allows the child menubar to tell us when and how to bail out of the menu. */ STDMETHODIMP CMenuBand::OnSelect(DWORD dwType) { int iIndex; switch (dwType) { case MPOS_CHILDTRACKING: // this means that our child did get tracked over it, so we should abort any timeout to destroy it if (_pmtbTracked) { HWND hwnd = _pmtbTracked->_hwndMB; if (_nItemTimer) { _pmtbTracked->KillPopupTimer(); // Use the command id of the SubMenu that we actually have cascaded out. iIndex = ToolBar_CommandToIndex(hwnd, _nItemSubMenu); ToolBar_SetHotItem(hwnd, iIndex); } KillTimer(hwnd, MBTIMER_DRAGOVER); _SiteOnSelect(dwType); } break; case MPOS_SELECTLEFT: if (!_fVertical) _OnSelectArrow(-1); else { // Cancel the child submenu. Hitting left arrow is like // hitting escape. _SubMenuOnSelect(MPOS_CANCELLEVEL); } break; case MPOS_SELECTRIGHT: if (!_fVertical) _OnSelectArrow(1); else { // The right arrow gets propagated up to the top, so // a fully cascaded menu will be cancelled and the // top level menuband will move to the next menu to the // right. _SiteOnSelect(dwType); } break; case MPOS_CANCELLEVEL: // Forward onto submenu _SubMenuOnSelect(dwType); break; case MPOS_FULLCANCEL: case MPOS_EXECUTE: DEBUG_CODE( TraceMsg(TF_MENUBAND, "%d (pmb=%#08lx): CMenuToolbarBase received %s", DBG_THIS, MPOS_FULLCANCEL == dwType ? TEXT("MPOS_FULLCANCEL") : TEXT("MPOS_EXECUTE")); ) _CancelMode(dwType); break; } return S_OK; } void CMenuBand::SetTrackMenuPopup(IUnknown* punk) { ATOMICRELEASE(_pmpTrackPopup); if (punk) { punk->QueryInterface(IID_PPV_ARG(IMenuPopup, &_pmpTrackPopup)); } } /*---------------------------------------------------------- Purpose: Set the currently tracked toolbar. Only one of the toolbars can have the "activation" at one time. */ BOOL CMenuBand::SetTracked(CMenuToolbarBase* pmtb) { if (pmtb == _pmtbTracked) return FALSE; if (_pmtbTracked) { // Tell the existing toolbar we're leaving him SendMessage(_pmtbTracked->_hwndMB, TB_SETHOTITEM2, -1, HICF_LEAVING); } _pmtbTracked = pmtb; if (_pmtbTracked) { // This is for accessibility. HWND hwnd = _pmtbTracked->_hwndMB; int iHotItem = ToolBar_GetHotItem(hwnd); if (iHotItem >= 0) { // Toolbar Items are 0 based, Accessibility apps require 1 based NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, GetIndexFromChild(_pmtbTracked->GetFlags() & SMSET_TOP, iHotItem)); } } return TRUE; } void CMenuBand::_OnSelectArrow(int iDir) { _fKeyboardSelected = TRUE; int iIndex; if (!_pmtbTracked) { if (iDir < 0) { SetTracked(_pmtbBottom); iIndex = ToolBar_ButtonCount(_pmtbTracked->_hwndMB) - 1; } else { SetTracked(_pmtbTop); iIndex = 0; } // This can happen when going to the chevron. if (_pmtbTracked) _pmtbTracked->SetHotItem(iDir, iIndex, -1, HICF_ARROWKEYS); } else { HWND hwnd = _pmtbTracked->_hwndMB; iIndex = ToolBar_GetHotItem(hwnd); int iCount = ToolBar_ButtonCount(hwnd); // Set the hot item explicitly since this is invoked by the // keyboard and the mouse could be anywhere. // cycle iIndex by iDir (add extra iCount to avoid negative number problems iIndex = (iIndex + iCount + iDir) % iCount; ToolBar_SetHotItem(hwnd, iIndex); } if (_pmtbTracked) { NotifyWinEvent(EVENT_OBJECT_FOCUS, _pmtbTracked->_hwndMB, OBJID_CLIENT, GetIndexFromChild(_pmtbTracked->GetFlags() & SMSET_TOP, iIndex)); } _fKeyboardSelected = FALSE; } void CMenuBand::_CancelMode(DWORD dwType) { // Tell the hosting site to cancel this level if (_fParentIsNotASite) UIActivateIO(FALSE, NULL); else _SiteOnSelect(dwType); } HRESULT CMenuBand::OnPosRectChangeDB (LPRECT prc) { // We want the HMENU portion to ALWAYS have the maximum allowed. RECT rcMenu = {0}; SIZE sizeMenu = {0}; SIZE sizeSF = {0}; SIZE sizeMax; if (_pmtbMenu) _pmtbMenu->GetSize(&sizeMenu); if (_pmtbShellFolder) _pmtbShellFolder->GetSize(&sizeSF); if (sizeSF.cx > sizeMenu.cx) sizeMax = sizeSF; else sizeMax = sizeMenu; if (_pmtbMenu) { if (_pmtbMenu->GetFlags() & SMSET_TOP) { rcMenu.bottom = sizeMenu.cy; rcMenu.right = prc->right; } else { rcMenu.bottom = prc->bottom; rcMenu.right = prc->right; rcMenu.top = prc->bottom - sizeMenu.cy; rcMenu.left = 0; } if (RECTHEIGHT(rcMenu) > RECTHEIGHT(*prc)) { rcMenu.bottom -= RECTHEIGHT(rcMenu) - RECTHEIGHT(*prc); } _pmtbMenu->SetWindowPos(&sizeMax, &rcMenu, 0); } if (_pmtbShellFolder) { RECT rc = *prc; if (_pmtbShellFolder->GetFlags() & SMSET_TOP) { rc.bottom = prc->bottom - RECTHEIGHT(rcMenu); } else { rc.top = prc->top + RECTHEIGHT(rcMenu); } _pmtbShellFolder->SetWindowPos(&sizeMax, &rc, 0); } return NOERROR; } HRESULT IUnknown_OnSelect(IUnknown* punk, DWORD dwType, REFGUID guid) { HRESULT hres; IMenuPopup * pmp; hres = IUnknown_QueryService(punk, guid, IID_PPV_ARG(IMenuPopup, &pmp)); if (SUCCEEDED(hres)) { pmp->OnSelect(dwType); pmp->Release(); } return hres; } HRESULT CMenuBand::_SiteOnSelect(DWORD dwType) { return IUnknown_OnSelect(_punkSite, dwType, SID_SMenuPopup); } HRESULT CMenuBand::_SubMenuOnSelect(DWORD dwType) { IMenuPopup* pmp = _pmpSubMenu; if (_pmpTrackPopup) pmp = _pmpTrackPopup; return IUnknown_OnSelect(pmp, dwType, SID_SMenuPopup); } HRESULT CMenuBand::GetTop(CMenuToolbarBase** ppmtbTop) { *ppmtbTop = _pmtbTop; if (*ppmtbTop) { (*ppmtbTop)->AddRef(); return NOERROR; } return E_FAIL; } HRESULT CMenuBand::GetBottom(CMenuToolbarBase** ppmtbBottom) { *ppmtbBottom = _pmtbBottom; if (*ppmtbBottom) { (*ppmtbBottom)->AddRef(); return NOERROR; } return E_FAIL; } HRESULT CMenuBand::GetTracked(CMenuToolbarBase** ppmtbTracked) { *ppmtbTracked = _pmtbTracked; if (*ppmtbTracked) { (*ppmtbTracked)->AddRef(); return NOERROR; } return E_FAIL; } HRESULT CMenuBand::GetParentSite(REFIID riid, void** ppvObj) { if (_punkSite) return _punkSite->QueryInterface(riid, ppvObj); return E_FAIL; } HRESULT CMenuBand::GetState(BOOL* pfVertical, BOOL* pfOpen) { *pfVertical = _fVertical; *pfOpen = _fMenuMode; return NOERROR; } HRESULT CMenuBand::DoDefaultAction(VARIANT* pvarChild) { if (pvarChild->lVal != CHILDID_SELF) { CMenuToolbarBase* pmtb = (pvarChild->lVal & TOOLBAR_MASK)? _pmtbTop : _pmtbBottom; int idCmd = GetButtonCmd(pmtb->_hwndMB, (pvarChild->lVal & ~TOOLBAR_MASK) - 1); SendMessage(pmtb->_hwndMB, TB_SETHOTITEM2, idCmd, HICF_OTHER | HICF_TOGGLEDROPDOWN); } else { _CancelMode(MPOS_CANCELLEVEL); } return NOERROR; } /*---------------------------------------------------------- Purpose: IShellMenuAcc::GetSubMenu method */ HRESULT CMenuBand::GetSubMenu(VARIANT* pvarChild, REFIID riid, void** ppvObj) { HRESULT hres = E_FAIL; CMenuToolbarBase* pmtb = (pvarChild->lVal & TOOLBAR_MASK)? _pmtbTop : _pmtbBottom; int idCmd = GetButtonCmd(pmtb->_hwndMB, (pvarChild->lVal & ~TOOLBAR_MASK) - 1); *ppvObj = NULL; if (idCmd != -1 && pmtb) { hres = pmtb->v_GetSubMenu(idCmd, &SID_SMenuBandChild, riid, ppvObj); } return hres; } /*---------------------------------------------------------- Purpose: IShellMenu2::GetSubMenu method */ HRESULT CMenuBand::GetSubMenu(UINT idCmd, REFIID riid, void** ppvObj) { HRESULT hres = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); if (_pmtbMenu) { hres = _pmtbMenu->v_GetSubMenu(idCmd, &SID_SMenuBandChild, riid, ppvObj); } if (hres == HRESULT_FROM_WIN32(ERROR_NOT_FOUND) && _pmtbShellFolder) { hres = _pmtbShellFolder->v_GetSubMenu(idCmd, &SID_SMenuBandChild, riid, ppvObj); } return hres; } HRESULT CMenuBand::SetToolbar(HWND hwnd, DWORD dwFlags) { HRESULT hr = E_OUTOFMEMORY; CMenuToolbarBase *pmtb = ToolbarMenu_Create(hwnd); if (pmtb) { hr = SetMenuToolbar(SAFECAST(pmtb, IWinEventHandler*), dwFlags); // DONT release! The menus break com identity rules because of a foobar when they were // initially designed. } return hr; } HRESULT CMenuBand::SetMinWidth(int cxMenu) { if (_pmtbMenu) { // Yes // HACK HACK. this should QI for a menustatic specific interface to do this. return _pmtbMenu->SetMinWidth(cxMenu); } else return E_FAIL; } HRESULT CMenuBand::SetNoBorder(BOOL fNoBorder) { HRESULT hr = S_OK; _fNoBorder = fNoBorder; if (_pmtbMenu) { // Yes // HACK HACK. this should QI for a menustatic specific interface to do this. hr = _pmtbMenu->SetNoBorder(fNoBorder); } if (_pmtbShellFolder) { _pmtbShellFolder->SetNoBorder(fNoBorder); } if (_punkSite) { hr = IUnknown_QueryServiceExec(_punkSite, SID_SMenuPopup, &CGID_MENUDESKBAR, MBCID_NOBORDER, fNoBorder, NULL, NULL); } return hr; } HRESULT CMenuBand::SetTheme(LPCWSTR pszTheme) { HRESULT hr = S_OK; Str_SetPtr(&_pszTheme, pszTheme); if (_pmtbMenu) { // Yes // HACK HACK. this should QI for a menustatic specific interface to do this. hr = _pmtbMenu->SetTheme(_pszTheme); } if (_pmtbShellFolder) { _pmtbShellFolder->SetTheme(_pszTheme); } return hr; } HRESULT CMenuBand::IsEmpty() { BOOL fReturn = TRUE; if (_pmtbShellFolder) fReturn = _pmtbShellFolder->IsEmpty(); if (fReturn && _pmtbMenu) fReturn = _pmtbMenu->IsEmpty(); return fReturn? S_OK : S_FALSE; } //---------------------------------------------------------------------------- // CMenuBandMetrics // //---------------------------------------------------------------------------- COLORREF GetLumColor(int isys, int iLumAdjust) { WORD iHue; WORD iLum; WORD iSat; COLORREF clr = (COLORREF)GetSysColor(isys); HDC hdc = GetDC(NULL); // Office CommandBars use this same algorithm for their "intellimenus" // colors. We prefer to call them "expando menus"... if (hdc) { int cColors = GetDeviceCaps(hdc, BITSPIXEL); ReleaseDC(NULL, hdc); switch (cColors) { case 4: // 16 Colors case 8: // 256 Colors // Default to using Button Face break; default: // 256+ colors ColorRGBToHLS(clr, &iHue, &iLum, &iSat); if (iLum > 220) iLum -= iLumAdjust; else if (iLum <= 20) iLum += 2 * iLumAdjust; else iLum += iLumAdjust; clr = ColorHLSToRGB(iHue, iLum, iSat); break; } } return clr; } ULONG CMenuBandMetrics::AddRef() { return ++_cRef; } ULONG CMenuBandMetrics::Release() { ASSERT(_cRef > 0); if (--_cRef > 0) return _cRef; delete this; return 0; } HRESULT CMenuBandMetrics::QueryInterface(REFIID riid, LPVOID * ppvObj) { if (IsEqualIID(riid, IID_IUnknown)) { *ppvObj = SAFECAST(this, IUnknown*); } else if (IsEqualIID(riid, CLSID_MenuBandMetrics)) { *ppvObj = this; } else { *ppvObj = NULL; return E_FAIL; } AddRef(); return S_OK; } CMenuBandMetrics::CMenuBandMetrics() : _cRef(1) { } void CMenuBandMetrics::Init(HWND hwnd) { if (_fInit) return; _SetMenuFont(); _SetColors(); HIGHCONTRAST hc = {sizeof(HIGHCONTRAST)}; if (SystemParametersInfoA(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0)) { _fHighContrastMode = (HCF_HIGHCONTRASTON & hc.dwFlags); } if (g_dwPrototype & 0x00000100) { SystemParametersInfo(SPI_SETFLATMENU, 0, IntToPtr(TRUE), SPIF_SENDCHANGE); SystemParametersInfo(SPI_SETDROPSHADOW, 0, IntToPtr(TRUE), SPIF_SENDCHANGE); } SystemParametersInfo(SPI_GETFLATMENU, 0, (PVOID)&_fFlatMenuMode, 0); _SetArrowFont(hwnd); _SetChevronFont(hwnd); #ifndef DRAWEDGE _SetPaintMetrics(hwnd); #endif _SetTextBrush(hwnd); _fInit = TRUE; } CMenuBandMetrics::~CMenuBandMetrics() { if (_hFontMenu) DeleteObject(_hFontMenu); if (_hFontArrow) DeleteObject(_hFontArrow); if (_hFontChevron) DeleteObject(_hFontChevron); if (_hbrText) DeleteObject(_hbrText); #ifndef DRAWEDGE if (_hPenHighlight) DeleteObject(_hPenHighlight); if (_hPenShadow) DeleteObject(_hPenShadow); #endif } HFONT CMenuBandMetrics::_CalcFont(HWND hwnd, LPCTSTR pszFont, DWORD dwCharSet, TCHAR ch, int* pcx, int* pcy, int* pcxMargin, int iOrientation, int iWeight) { ASSERT(hwnd); HFONT hFontOld, hFontRet = NULL; TEXTMETRIC tm; RECT rect={0}; int cx = 0, cy = 0, cxM = 0; HDC hdc = GetDC(hwnd); if (hdc) { hFontOld = (HFONT)SelectObject(hdc, _hFontMenu); GetTextMetrics(hdc, &tm); // Set the font height (based on original USER code) cy = ((tm.tmHeight + tm.tmExternalLeading + GetSystemMetrics(SM_CYBORDER)) & 0xFFFE) - 1; // Use the menu font's avg character width as the margin. cxM = tm.tmAveCharWidth; // Not exactly how USER does it, but close // Shlwapi wraps the ansi/unicode behavior. hFontRet = CreateFontWrap(cy, 0, iOrientation, 0, iWeight, 0, 0, 0, dwCharSet, 0, 0, 0, 0, pszFont); if (TPTR(hFontRet)) { // Calc width of arrow using this new font SelectObject(hdc, hFontRet); if (DrawText(hdc, &ch, 1, &rect, DT_CALCRECT | DT_SINGLELINE | DT_LEFT | DT_VCENTER)) cx = rect.right; else cx = tm.tmMaxCharWidth; } else { cx = tm.tmMaxCharWidth; } SelectObject(hdc, hFontOld); ReleaseDC(hwnd, hdc); } *pcx = cx; *pcy = cy; *pcxMargin = cxM; return hFontRet; } /* Call after _SetMenuFont() */ void CMenuBandMetrics::_SetChevronFont(HWND hwnd) { ASSERT(!_hFontChevron); TCHAR szPath[MAX_PATH]; NONCLIENTMETRICSA ncm; ncm.cbSize = sizeof(ncm); // Should only fail with bad parameters... EVAL(SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0)); // Obtain the font's metrics SHAnsiToTChar(ncm.lfMenuFont.lfFaceName, szPath, ARRAYSIZE(szPath)); _hFontChevron = _CalcFont(hwnd, szPath, DEFAULT_CHARSET, CH_MENUARROW, &_cxChevron, &_cyChevron, &_cxChevron, -900, FW_NORMAL); } /* Call after _SetMenuFont() */ void CMenuBandMetrics::_SetArrowFont(HWND hwnd) { ASSERT(!_hFontArrow); ASSERT(_hFontMenu); // Obtain the font's metrics if (_hFontMenu) { _hFontArrow = _CalcFont(hwnd, szfnMarlett, SYMBOL_CHARSET, CH_MENUARROW, &_cxArrow, &_cyArrow, &_cxMargin, 0, FW_NORMAL); } else { _cxArrow = _cyArrow = _cxMargin = 0; } } void CMenuBandMetrics::_SetMenuFont() { NONCLIENTMETRICS ncm; ncm.cbSize = sizeof(ncm); // Should only fail with bad parameters... SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0); _hFontMenu = CreateFontIndirect(&ncm.lfMenuFont); } void CMenuBandMetrics::_SetColors() { _clrBackground = GetSysColor(COLOR_MENU); _clrMenuText = GetSysColor(COLOR_MENUTEXT); _clrDemoted = GetLumColor(COLOR_MENU, 20); _clrMenuGrad = GetLumColor(COLOR_MENU, -20); } #ifndef DRAWEDGE // Office "IntelliMenu" style void CMenuBandMetrics::_SetPaintMetrics(HWND hwnd) { DWORD dwSysHighlight = GetSysColor(COLOR_3DHIGHLIGHT); DWORD dwSysShadow = GetSysColor(COLOR_3DSHADOW); _hPenHighlight = CreatePen(PS_SOLID, 1, dwSysHighlight); _hPenShadow = CreatePen(PS_SOLID, 1, dwSysShadow); } #endif void CMenuBandMetrics::_SetTextBrush(HWND hwnd) { _hbrText = CreateSolidBrush(GetSysColor(COLOR_MENUTEXT)); } CMenuBandState::CMenuBandState() { // We will default to NOT show the keyboard cues. This // is overridden based on the User Settings. _fKeyboardCue = FALSE; } CMenuBandState::~CMenuBandState() { ATOMICRELEASE(_ptFader); ATOMICRELEASE(_pScheduler); if (IsWindow(_hwndToolTip)) DestroyWindow(_hwndToolTip); if (IsWindow(_hwndWorker)) // JANK : Fix for bug #101302 DestroyWindow(_hwndWorker); } int CMenuBandState::GetKeyboardCue() { return _fKeyboardCue; } void CMenuBandState::SetKeyboardCue(BOOL fKC) { _fKeyboardCue = fKC; } IShellTaskScheduler* CMenuBandState::GetScheduler() { HRESULT hr = S_OK; if (!_pScheduler) { hr = CoCreateInstance(CLSID_ShellTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellTaskScheduler, &_pScheduler)); } ASSERT((SUCCEEDED(hr) && _pScheduler) || (FAILED(hr) && !_pScheduler)); if (SUCCEEDED(hr)) _pScheduler->AddRef(); return _pScheduler; } HRESULT CMenuBandState::FadeRect(LPCRECT prc) { HRESULT hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); BOOL fFade = FALSE; SystemParametersInfo(SPI_GETSELECTIONFADE, 0, &fFade, 0); if (_ptFader && fFade) { hr = _ptFader->FadeRect(prc); } return hr; } void CMenuBandState::CreateFader() { // We do this on first show, because in the Constuctor of CMenuBandState, // the Window classes might not be registered yet (As is the case with start menu). if (!_ptFader) { CoCreateInstance(CLSID_FadeTask, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IFadeTask, &_ptFader)); } } void CMenuBandState::CenterOnButton(HWND hwndTB, BOOL fBalloon, int idCmd, LPTSTR pszTitle, LPTSTR pszTip) { // Balloon style holds presidence over info tips if (_fTipShown && _fBalloonStyle) return; if (!_hwndToolTip) { _hwndToolTip = CreateWindow(TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, g_hinst, NULL); if (_hwndToolTip) { // set the version so we can have non buggy mouse event forwarding SendMessage(_hwndToolTip, CCM_SETVERSION, COMCTL32_VERSION, 0); SendMessage(_hwndToolTip, TTM_SETMAXTIPWIDTH, 0, (LPARAM)300); } } if (_hwndToolTip) { // Collapse the previous tip because we're going to be doing some stuff to it before displaying again. SendMessage(_hwndToolTip, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)0); // Balloon tips don't have a border, but regular tips do. Swap now... SHSetWindowBits(_hwndToolTip, GWL_STYLE, TTS_BALLOON | WS_BORDER, (fBalloon) ? TTS_BALLOON : WS_BORDER); if (pszTip && pszTip[0]) { POINT ptCursor; RECT rcItemScreen, rcItemTB; TOOLINFO ti = {0}; ti.cbSize = sizeof(ti); // This was pretty bad: I kept adding tools, but never deleteing them. Now we get rid of the current // one then add the new one. if (SendMessage(_hwndToolTip, TTM_ENUMTOOLS, 0, (LPARAM)&ti)) { SendMessage(_hwndToolTip, TTM_DELTOOL, 0, (LPARAM)&ti); // Delete the current tool. } SendMessage(hwndTB, TB_GETRECT, idCmd, (LPARAM)&rcItemScreen); rcItemTB = rcItemScreen; MapWindowPoints(hwndTB, HWND_DESKTOP, (POINT*)&rcItemScreen, 2); ti.cbSize = sizeof(ti); ti.uFlags = TTF_TRANSPARENT | (fBalloon? TTF_TRACK : 0); // Check if the cursor is within the bounds of the hot item. // If it is, then proceed as usual. // If it isn't, then the hot item was activated via the keyboard, so the tooltip // shouldn't be hung from the cursor. Stick it on the hot item instead. // Set the vertical offset to use later. // Notice the correction for the bottom: gsierra wanted it up a couple of pixels. int nOffset = -3; GetCursorPos(&ptCursor); if (!PtInRect(&rcItemScreen, ptCursor)) { ti.uFlags |= TTF_TRACK; // Force the tool tip to track along the bottom. nOffset = 1; } // The tooltip won't pick up the hot item's rect right, so // do it manually. ti.rect = rcItemTB; SendMessage(_hwndToolTip, TTM_TRACKPOSITION, 0, MAKELONG((rcItemScreen.left + rcItemScreen.right)/2, rcItemScreen.bottom + nOffset)); ti.hwnd = hwndTB; ti.uId = (UINT_PTR)hwndTB; SendMessage(_hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); ti.lpszText = pszTip; SendMessage(_hwndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); SendMessage(_hwndToolTip, TTM_SETTITLE, TTI_INFO, (LPARAM)pszTitle); SetWindowPos(_hwndToolTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); SendMessage(_hwndToolTip, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&ti); _fTipShown = TRUE; _fBalloonStyle = fBalloon; } } } void CMenuBandState::HideTooltip(BOOL fAllowBalloonCollapse) { if (_hwndToolTip && _fTipShown) { // Now we're going to latch the Balloon style. The rest of menuband blindly // collapses the tooltip when selection changes. Here's where we say "Don't collapse // the chevron balloon tip because of a selection change." if ((_fBalloonStyle && fAllowBalloonCollapse) || !_fBalloonStyle) { SendMessage(_hwndToolTip, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)0); _fTipShown = FALSE; } } } void CMenuBandState::PutTipOnTop() { // Force the tooltip to the topmost. if (_hwndToolTip) { SetWindowPos(_hwndToolTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); } } HWND CMenuBandState::GetWorkerWindow(HWND hwndParent) { if (!_hwndSubclassed) return NULL; if (!_hwndWorker) { // We need a worker window, so that dialogs show up on top of our menus. // HiddenWndProc is included from sftbar.h _hwndWorker = SHCreateWorkerWindow(HiddenWndProc, _hwndSubclassed, WS_EX_TOOLWINDOW, WS_POPUP, 0, (void*)_hwndSubclassed); } //hwndParent is unused at this time. I plan on using it to prevent the parenting to the subclassed window. return _hwndWorker; }