#include "shellprv.h" #include "common.h" #include "menuband.h" #include "dpastuff.h" // COrderList_* #include "resource.h" #include "mnbase.h" #include "oleacc.h" #include "iaccess.h" #include "uemapp.h" #include "util.h" #ifdef UXCTRL_VERSION #include #include #endif // Conflicts with one defined in winuserp.h #undef WINEVENT_VALID //It's tripping on this... #include "winable.h" const TCHAR c_wzMenuBandTheme[] = TEXT("MenuBand"); #define DM_MISC 0 // miscellany #define MAXUEMTIMEOUT 2000 /*---------------------------------------------------------- Purpose: Return the button command given the position. */ int GetButtonCmd(HWND hwnd, int iPos) { ASSERT(IsWindow(hwnd)); int nRet = -1; // Punt on failure TBBUTTON tbb; if (ToolBar_GetButton(hwnd, iPos, &tbb)) { nRet = tbb.idCommand; } return nRet; } void* ItemDataFromPos(HWND hwndTB, int iPos) { TBBUTTONINFO tbbi; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_LPARAM | TBIF_BYINDEX; if (ToolBar_GetButtonInfo(hwndTB, iPos, &tbbi) >= 0) { return (void*)tbbi.lParam; } return NULL; } long GetIndexFromChild(BOOL fTop, int iIndex) { return (fTop? TOOLBAR_MASK: 0) | iIndex + 1; } //-------------------------------------------------------------------------------- // // CMenuToolbarBase // //-------------------------------------------------------------------------------- CMenuToolbarBase::~CMenuToolbarBase() { Str_SetPtr(&_pszTheme, NULL); if (_hTheme) { CloseThemeData(_hTheme); } } CMenuToolbarBase::CMenuToolbarBase(CMenuBand* pmb, DWORD dwFlags) : _pcmb(pmb) { #ifdef DEBUG _cRef = 1; #endif _dwFlags = dwFlags; _nItemTimer = -1; _idCmdChevron = -1; _fFirstTime = TRUE; } // *** IObjectWithSite methods *** HRESULT CMenuToolbarBase::SetSite(IUnknown *punkSite) { ASSERT(punkSite && IS_VALID_READ_PTR(punkSite, CMenuBand*)); // We are guaranteed the lifetime of this object is contained within // the menuband, so we don't addref pcmb. if (SUCCEEDED(punkSite->QueryInterface(CLSID_MenuBand, (void**)&_pcmb))) { punkSite->Release(); } else { ASSERT(0); } _fVerticalMB = !BOOLIFY(_pcmb->_dwFlags & SMINIT_HORIZONTAL); _fTopLevel = BOOLIFY(_pcmb->_dwFlags & SMINIT_TOPLEVEL); return S_OK; } HRESULT CMenuToolbarBase::GetSite(REFIID riid, void ** ppvSite) { if (!_pcmb) return E_FAIL; return _pcmb->QueryInterface(riid, ppvSite); } // *** IUnknown methods *** STDMETHODIMP_(ULONG) CMenuToolbarBase::AddRef() { DEBUG_CODE(_cRef++); if (_pcmb) { return _pcmb->AddRef(); } return 0; } STDMETHODIMP_(ULONG) CMenuToolbarBase::Release() { ASSERT(_cRef > 0); DEBUG_CODE(_cRef--); if (_pcmb) { return _pcmb->Release(); } return 0; } HRESULT CMenuToolbarBase::QueryInterface(REFIID riid, void** ppvObj) { HRESULT hres; if (IsEqualGUID(riid, CLSID_MenuToolbarBase) && ppvObj) { AddRef(); *ppvObj = (void*)this; hres = S_OK; } else hres = _pcmb->QueryInterface(riid, ppvObj); return hres; } void CMenuToolbarBase::SetToTop(BOOL bToTop) { // A menu toolbar can be at the top or the bottom of the menu. // This is an exclusive attribute. if (bToTop) { _dwFlags |= SMSET_TOP; _dwFlags &= ~SMSET_BOTTOM; } else { _dwFlags |= SMSET_BOTTOM; _dwFlags &= ~SMSET_TOP; } } void CMenuToolbarBase::KillPopupTimer() { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first TraceMsg(TF_MENUBAND, "(pmb=%#08lx): Killing Popout Timer...", this); KillTimer(_hwndMB, MBTIMER_POPOUT); _nItemTimer = -1; } void CMenuToolbarBase::SetWindowPos(LPSIZE psize, LPRECT prc, DWORD dwFlags) { if (_hwndMB) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first DWORD rectWidth = RECTWIDTH(*prc); TraceMsg(TF_MENUBAND, "CMTB::SetWindowPos %d - (%d,%d,%d,%d)", psize?psize->cx:0, prc->left, prc->top, prc->right, prc->bottom); ::SetWindowPos(_hwndMB, NULL, prc->left, prc->top, rectWidth, RECTHEIGHT(*prc), SWP_NOZORDER | SWP_NOACTIVATE | dwFlags); // hackhack: we only do this when multicolumn. this call is to facilitate the size negotiation between // static menu and folder menu. Set the width of the toolbar to the width of the button in case // of non-multicolumn. if (!(_fMulticolumnMB) && psize) { int cx = psize->cx; ToolBar_SetButtonWidth(_hwndMB, cx, cx); } // Force this to redraw. I put this here because the HMenu portion was painting after the shell // folder portion was done enumerating the folder, which is pretty slow. I wanted the HMENU portion // to paint right away... RedrawWindow(_hwndMB, NULL, NULL, RDW_UPDATENOW); } } // NOTE: if psize is (0,0) we use tb button size as param in figuring out ideal tb size // else we use max of psize length and tb button length as our metric void CMenuToolbarBase::GetSize(SIZE* psize) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (_hwndMB) { LRESULT lButtonSize; lButtonSize = TB_GetButtonSizeWithoutThemeBorder(_hwndMB, _hTheme); if (psize->cx || psize->cy) { int cx = max(psize->cx, LOWORD(lButtonSize)); int cy = max(psize->cy, HIWORD(lButtonSize)); lButtonSize = MAKELONG(cx, cy); } if (_fVerticalMB) { psize->cx = LOWORD(lButtonSize); SendMessage(_hwndMB, TB_GETIDEALSIZE, TRUE, (LPARAM)psize); } else { psize->cy = HIWORD(lButtonSize); SendMessage(_hwndMB, TB_GETIDEALSIZE, FALSE, (LPARAM)psize); } TraceMsg(TF_MENUBAND, "CMTB::GetSize (%d, %d)", psize->cx, psize->cy); } } /*---------------------------------------------------------- Purpose: Timer handler. Used to pop open/close cascaded submenus. */ LRESULT CMenuToolbarBase::_OnTimer(WPARAM wParam) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first switch (wParam) { case MBTIMER_INFOTIP: { // Do we have a hot item to display the tooltip for? int iHotItem = ToolBar_GetHotItem(_hwndMB); KillTimer(_hwndMB, wParam); if (iHotItem >= 0) { // Yep. TCHAR szTip[MAX_PATH]; int idCmd = GetButtonCmd(_hwndMB, iHotItem); // Ask the superclass for the tip if (S_OK == v_GetInfoTip(idCmd, szTip, ARRAYSIZE(szTip))) { // Now display it. Yawn. _pcmb->_pmbState->CenterOnButton(_hwndMB, FALSE, idCmd, NULL, szTip); } } } break; case MBTIMER_CHEVRONTIP: KillTimer(_hwndMB, wParam); _pcmb->_pmbState->HideTooltip(TRUE); break; case MBTIMER_FLASH: { _cFlashCount++; if (_cFlashCount == COUNT_ENDFLASH) { _cFlashCount = 0; KillTimer(_hwndMB, wParam); ToolBar_MarkButton(_hwndMB, _idCmdChevron, FALSE); _SetTimer(MBTIMER_UEMTIMEOUT); // Now that we've flashed, let's show the Chevron tip. // This is for a confused user: If they've hovered over an item for too long, // or this is the first time they've seen intellimenus, then we flash and display // the tooltip. We only want to display this if we are shown: We would end up with // and dangling tooltip if you happen to move to another menu while it was flashing. // Ummm, is the Chevron still visible? if (_fShowMB && _idCmdChevron != -1) { TCHAR szTip[MAX_PATH]; TCHAR szTitle[MAX_PATH]; if (S_OK == v_CallCBItem(_idCmdChevron, SMC_CHEVRONGETTIP, (WPARAM)szTitle, (LPARAM)szTip)) { _pcmb->_pmbState->CenterOnButton(_hwndMB, TRUE, _idCmdChevron, szTitle, szTip); _SetTimer(MBTIMER_CHEVRONTIP); } } } else ToolBar_MarkButton(_hwndMB, _idCmdChevron, (_cFlashCount % 2) == 0); } break; case MBTIMER_UEMTIMEOUT: { POINT pt; RECT rect; // Don't fire timeouts when we're in edit mode. if (_fEditMode) { KillTimer(_hwndMB, wParam); break; } GetWindowRect(_hwndMB, &rect); GetCursorPos(&pt); if (PtInRect(&rect, pt)) { TraceMsg(TF_MENUBAND, "*** UEM TimeOut. At Tick Count (%d) ***", GetTickCount()); _FireEvent(UEM_TIMEOUT); } else { TraceMsg(TF_MENUBAND, " *** UEM TimeOut. At Tick Count (%d)." " Mouse outside menu. Killing *** ", GetTickCount()); KillTimer(_hwndMB, wParam); } } break; case MBTIMER_EXPAND: KillTimer(_hwndMB, wParam); if (_fShowMB) { v_CallCBItem(_idCmdChevron, SMC_CHEVRONEXPAND, 0, 0); Expand(TRUE); _fClickHandled = TRUE; _SetTimer(MBTIMER_CLICKUNHANDLE); } break; case MBTIMER_DRAGPOPDOWN: // There has not been a drag enter in this band for a while, // so we'll try to cancel the menus. KillTimer(_hwndMB, wParam); PostMessage(_pcmb->_pmbState->GetSubclassedHWND(), g_nMBDragCancel, 0, 0); break; case MBTIMER_DRAGOVER: { TraceMsg(TF_MENUBAND, "CMenuToolbarBase::OnTimer(DRAG)"); KillTimer(_hwndMB, wParam); DAD_ShowDragImage(FALSE); // Does this item cascade? int idBtn = GetButtonCmd(_hwndMB, v_GetDragOverButton()); DWORD dwFlags = v_GetFlags(idBtn); if (dwFlags & SMIF_SUBMENU) { TraceMsg(TF_MENUBAND, "CMenuToolbarBase::OnTimer(DRAG): Is a submenu"); // Yes; pop it open if (!_fVerticalMB) _pcmb->_fInvokedByDrag = TRUE; _DoPopup(idBtn, FALSE); } else if (dwFlags & SMIF_DRAGNDROP) { v_CallCBItem(idBtn, SMC_EXEC, 0, 1); } else if (idBtn == _idCmdChevron) { Expand(TRUE); } else { _pcmb->_SubMenuOnSelect(MPOS_CANCELLEVEL); } } break; case MBTIMER_POPOUT: { int nItemTimer = _nItemTimer; KillPopupTimer(); // Popup a new submenu? if (-1 != nItemTimer) { if (nItemTimer != _pcmb->_nItemCur) { // Yes; post message since the currently expanded submenu // may be a CTrackPopup object, which posts its cancel mode. TraceMsg(TF_MENUBAND, "(pmb=%#08lx): Timer went off. Expanding...", this); PostPopup(nItemTimer, FALSE, FALSE); } } else { // No; just collapse the currently open submenu TraceMsg(TF_MENUBAND, "(pmb=%#08lx): _OnTimer sending MPOS_CANCELLEVEL to submenu popup", this); _pcmb->_SubMenuOnSelect(MPOS_CANCELLEVEL); } break; } case MBTIMER_CLOSE: KillTimer(_hwndMB, wParam); TraceMsg(TF_MENUBAND, "(pmb=%#08lx): _OnTimer sending MPOS_FULLCANCEL", this); if (_fVerticalMB) _pcmb->_SiteOnSelect(MPOS_FULLCANCEL); else { _pcmb->_SubMenuOnSelect(MPOS_FULLCANCEL); } break; } return 1; } void CMenuToolbarBase::_DrawMenuArrowGlyph( HDC hdc, RECT * prc, COLORREF rgbText ) { SIZE size = {_pcmb->_pmbm->_cxArrow, _pcmb->_pmbm->_cyArrow}; // // If the DC is mirrred, then the Arrow should be mirrored // since it is done thru TextOut, NOT the 2D graphics APIs [samera] // _DrawMenuGlyph(hdc, _pcmb->_pmbm->_hFontArrow, prc, (IS_DC_RTL_MIRRORED(hdc)) ? CH_MENUARROWRTLA : CH_MENUARROWA, rgbText, &size); } void CMenuToolbarBase::_DrawMenuGlyph( HDC hdc, HFONT hFont, RECT * prc, CHAR ch, COLORREF rgbText, LPSIZE psize) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (_pcmb->_pmbm->_hFontArrow) { SIZE size; int cx, cy, y, x; HFONT hFontOld; int iOldBk = SetBkMode(hdc, TRANSPARENT); hFontOld = (HFONT)SelectObject(hdc, hFont); if (psize == NULL) { GetTextExtentPoint32A( hdc, &ch, 1, &size); psize = &size; } cy = prc->bottom - prc->top; y = prc->top + ((cy - psize->cy) / 2); cx = prc->right - prc->left; x = prc->left + ((cx - psize->cx) /2); COLORREF rgbOld = SetTextColor(hdc, rgbText); TextOutA(hdc, x, y, &ch, 1); SetTextColor(hdc, rgbOld); SetBkMode(hdc, iOldBk); SelectObject(hdc, hFontOld); } } void CMenuToolbarBase::SetMenuBandMetrics(CMenuBandMetrics* pmbm) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first // This can be called before the toolbar is created. // So we'll check this condition. When the toolbar is created, then // the toolbar will get the metrics at that point. if (!_hwndMB) return; //Loop through toolbar. for (int iButton = ToolBar_ButtonCount(_hwndMB)-1; iButton >= 0; iButton--) { IOleCommandTarget* poct; int idCmd = GetButtonCmd(_hwndMB, iButton); // If it's not a seperator, see if there is a sub menu. if (idCmd != -1 && SUCCEEDED(v_GetSubMenu(idCmd, NULL, IID_PPV_ARG(IOleCommandTarget, &poct)))) { VARIANT Var; Var.vt = VT_UNKNOWN; Var.punkVal = SAFECAST(pmbm, IUnknown*); // Exec to set new Metrics. poct->Exec(&CGID_MenuBand, MBANDCID_SETFONTS, 0, &Var, NULL); poct->Release(); } } _SetFontMetrics(); // return } void CMenuToolbarBase::_SetFontMetrics() { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (_hwndMB && _pcmb->_pmbm) { SendMessage(_hwndMB, WM_SETFONT, (WPARAM)_pcmb->_pmbm->_hFontMenu, FALSE); IUnknown_QueryServiceExec(_pcmb->_punkSite, SID_SMenuPopup, &CGID_MENUDESKBAR, MBCID_SETFLAT, _pcmb->_pmbm->_fFlatMenuMode, NULL, NULL); } } HRESULT CMenuToolbarBase::CreateToolbar(HWND hwndParent) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first ASSERT( _hwndMB != NULL ); DWORD dwToolBarStyle = TBSTYLE_TRANSPARENT; // if we're set up as a popup, don't do any transparent stuff if (_fVerticalMB) { dwToolBarStyle = TBSTYLE_CUSTOMERASE; // Vertical Toolbars don't get Transparent DWORD dwExtendedStyle = 0; // This is for TBMenu which actually has a Horizontal menubar within the // Vertical menuband. if (!_fHorizInVerticalMB) dwExtendedStyle |= TBSTYLE_EX_VERTICAL; if (_fMulticolumnMB) dwExtendedStyle |= TBSTYLE_EX_MULTICOLUMN; ToolBar_SetExtendedStyle(_hwndMB, dwExtendedStyle, TBSTYLE_EX_VERTICAL | TBSTYLE_EX_MULTICOLUMN); ToolBar_SetListGap(_hwndMB, LIST_GAP); } ToolBar_SetExtendedStyle(_hwndMB, TBSTYLE_EX_DOUBLEBUFFER, TBSTYLE_EX_DOUBLEBUFFER); SHSetWindowBits(_hwndMB, GWL_STYLE, TBSTYLE_TRANSPARENT | TBSTYLE_CUSTOMERASE, dwToolBarStyle ); ToolBar_SetInsertMarkColor(_hwndMB, GetSysColor(COLOR_MENUTEXT)); v_UpdateIconSize(_pcmb->_uIconSize, FALSE); _SetFontMetrics(); if (_pszTheme) { SendMessage(_hwndMB, TB_SETWINDOWTHEME, 0, (LPARAM)_pszTheme); _RefreshTheme(); } return S_OK; } HRESULT CMenuToolbarBase::_SetMenuBand(IShellMenu* psm) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first HRESULT hres = E_FAIL; IBandSite* pmbs = NULL; if (!_pcmb->_pmpSubMenu) { hres = CoCreateInstance(CLSID_MenuDeskBar, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IMenuPopup, &_pcmb->_pmpSubMenu)); if (SUCCEEDED(hres)) { IUnknown_SetSite(_pcmb->_pmpSubMenu, SAFECAST(_pcmb, IOleCommandTarget*)); hres = CoCreateInstance(CLSID_MenuBandSite, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IBandSite, &pmbs)); if (SUCCEEDED(hres)) { hres = _pcmb->_pmpSubMenu->SetClient(pmbs); // Don't release pmbs here. We are using below } // Menu band will Release _pmpSubMenu. } } else { IUnknown* punk; _pcmb->_pmpSubMenu->GetClient(&punk); if (punk) { hres = punk->QueryInterface(IID_PPV_ARG(IBandSite, &pmbs)); punk->Release(); } } if (pmbs) { if (SUCCEEDED(hres)) hres = pmbs->AddBand(psm); pmbs->Release(); } return hres; } HRESULT CMenuToolbarBase::GetSubMenu(int idCmd, GUID* pguidService, REFIID riid, void** ppvObj) { // pguidService is for asking a for specifically the Shell Folder portion or the Static portion HRESULT hres = E_FAIL; if (v_GetFlags(idCmd) & SMIF_TRACKPOPUP || _pcmb->_dwFlags & SMINIT_DEFAULTTOTRACKPOPUP) { hres = v_CreateTrackPopup(idCmd, riid, (void**)ppvObj); if (SUCCEEDED(hres)) { _pcmb->SetTrackMenuPopup((IUnknown*)*ppvObj); } } else { IShellMenu* psm; hres = v_GetSubMenu(idCmd, pguidService, IID_PPV_ARG(IShellMenu, &psm)); if (SUCCEEDED(hres)) { if (_pszTheme) { IShellMenu2* psm2; if (SUCCEEDED(psm->QueryInterface(IID_PPV_ARG(IShellMenu2, &psm2)))) { if (_fNoBorder) { psm2->SetNoBorder(_fNoBorder); } psm2->SetTheme(_pszTheme); psm2->Release(); } } TraceMsg(TF_MENUBAND, "GetUIObject psm %#lx", psm); _pcmb->SetTracked(this); hres = _SetMenuBand(psm); psm->Release(); // Did we succeed in getting a menupopup? if (SUCCEEDED(hres)) { // Yep; Sweet! _pcmb->_pmpSubMenu->QueryInterface(riid, ppvObj); HWND hwnd; IUnknown_GetWindow(_pcmb->_pmpSubMenu, &hwnd); PostMessage(_pcmb->_pmbState->GetSubclassedHWND(), g_nMBAutomation, (WPARAM)hwnd, (LPARAM)-1); } } } return hres; } HRESULT CMenuToolbarBase::PositionSubmenu(int idCmd) { IMenuPopup* pmp = NULL; HRESULT hres = E_FAIL; DWORD dwFlags = 0; if (_pcmb->_fInSubMenu) { // Since the selection has probrably changed, we use the cached item id // to calculate the postion rect idCmd = _pcmb->_nItemSubMenu; dwFlags = MPPF_REPOSITION | MPPF_NOANIMATE; pmp = _pcmb->_pmpSubMenu; pmp->AddRef(); ASSERT(pmp); // If _fInSubmenu is set, then this must be valid hres = S_OK; } else { // Only do these when we're not repositioning. if (_pcmb->_fInitialSelect) dwFlags |= MPPF_INITIALSELECT; if (!_pcmb->_fCascadeAnimate) dwFlags |= MPPF_NOANIMATE; _pcmb->_nItemSubMenu = idCmd; hres = GetSubMenu(idCmd, NULL, IID_PPV_ARG(IMenuPopup, &pmp)); } ASSERT(idCmd != -1); // Make sure at this point we have an item. if (SUCCEEDED(hres)) { ASSERT(pmp); // Make sure the menuitem is pressed _PressBtn(idCmd, TRUE); RECT rc; RECT rcTB; RECT rcTemp; POINT pt; if (!SendMessage(_hwndMB, TB_GETRECT, idCmd, (LPARAM)&rc)) { // Under weird conditions, idCmd can be invalid. // (See bug 403077.) So just blow off the reposition // and keep using the old position. hres = E_FAIL; } else { GetClientRect(_hwndMB, &rcTB); if (rc.right > rcTB.right) { rc.right = rcTB.right; } // Is the button rect within the boundries of the // visible toolbar? if (!IntersectRect(&rcTemp, &rcTB, &rc)) { // No; Then we need to bias that rect into // the visible region of the toolbar. // We only want to bias one side if (rc.left > rcTB.right) { rc.left = rcTB.right - (rc.right - rc.left); rc.right = rcTB.right; } } MapWindowPoints(_hwndMB, HWND_DESKTOP, (POINT*)&rc, 2); if (_fVerticalMB) { pt.x = rc.right; pt.y = rc.top; } else { // // If the shell dropdown (toolbar button) menus are mirrored, // then take the right edge as the anchor point // if (IS_WINDOW_RTL_MIRRORED(_hwndMB)) pt.x = rc.right; else pt.x = rc.left; pt.y = rc.bottom; } // Since toolbar buttons expand almost to the end of the basebar, // shrink the exclude rect so if overlaps. // NOTE: the items are GetSystemMetrics(SM_CXEDGE) larger than before. So adjust to that. if (_pcmb->_fExpanded) InflateRect(&rc, -GetSystemMetrics(SM_CXEDGE), 0); // We want to stop showing the chevron tip when we cascade into another menu _pcmb->_pmbState->HideTooltip(TRUE); // Only animate the first show at this level. _pcmb->_fCascadeAnimate = FALSE; hres = pmp->Popup((POINTL*)&pt, (RECTL*)&rc, dwFlags); } pmp->Release(); } return hres; } /*---------------------------------------------------------- Purpose: Cascade to the _nItemCur item's menu popup. If the popup call was modal, S_FALSE is returned; otherwise it is S_OK, or error. */ HRESULT CMenuToolbarBase::PopupOpen(int idBtn) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first HRESULT hres = E_FAIL; // Tell the current submenu popup to cancel. This must be done // before the PostMessage b/c CTrackPopupBar itself posts a message // which it must receive before we receive our post. TraceMsg(TF_MENUBAND, "(pmb=%#08lx): PostPopup sending MPOS_CANCELLEVEL to submenu popup", this); if (_pcmb->_fInSubMenu) _pcmb->_SubMenuOnSelect(MPOS_CANCELLEVEL); hres = PositionSubmenu(idBtn); // Modal? if (S_FALSE == hres) { // Yes; take the capture back GetMessageFilter()->RetakeCapture(); // return S_OK so we stay in the menu mode hres = S_OK; } else if (FAILED(hres)) _PressBtn(idBtn, FALSE); // Since CTrackPopupBar is modal, it should be a useless blob // of bits in memory by now... _pcmb->SetTrackMenuPopup(NULL); return hres; } /*---------------------------------------------------------- Purpose: Called to hide a modeless menu. */ void CMenuToolbarBase::PopupClose(void) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (-1 != _pcmb->_nItemCur) { _PressBtn(_pcmb->_nItemCur, FALSE); NotifyWinEvent(EVENT_OBJECT_FOCUS, _hwndMB, OBJID_CLIENT, GetIndexFromChild(_dwFlags & SMSET_TOP, ToolBar_CommandToIndex(_hwndMB, _pcmb->_nItemCur))); _pcmb->_fInSubMenu = FALSE; _pcmb->_fInvokedByDrag = FALSE; _pcmb->_nItemCur = -1; } } LRESULT CMenuToolbarBase::_OnWrapHotItem(NMTBWRAPHOTITEM* pnmwh) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (_fProcessingWrapHotItem || !_pcmb->_pmtbTracked || (_pcmb->_pmtbTop == _pcmb->_pmtbBottom && !_fHasDemotedItems)) return 0; _fProcessingWrapHotItem = TRUE; // If we want ourselves to not be wrapped into (Like for empty items) // Then forward the wrap message to the other toolbar if (_pcmb->_pmtbTracked->_dwFlags & SMSET_TOP && !(_pcmb->_pmtbBottom->_fDontShowEmpty)) { _pcmb->SetTracked(_pcmb->_pmtbBottom); } else if (!(_pcmb->_pmtbTop->_fDontShowEmpty)) { _pcmb->SetTracked(_pcmb->_pmtbTop); } int iIndex; if (pnmwh->iDir < 0) { HWND hwnd = _pcmb->_pmtbTracked->_hwndMB; iIndex = ToolBar_ButtonCount(hwnd) - 1; int idCmd = GetButtonCmd(hwnd, iIndex); // We do not want to wrap onto a chevron. if (idCmd == _idCmdChevron) iIndex -= 1; } else { iIndex = 0; } _pcmb->_pmtbTracked->SetHotItem(pnmwh->iDir, iIndex, -1, pnmwh->nReason); _fProcessingWrapHotItem = FALSE; return 1; } LRESULT CMenuToolbarBase::_OnWrapAccelerator(NMTBWRAPACCELERATOR* pnmwa) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first int iHotItem = -1; int iNumTopAccel = 0; int iNumBottomAccel = 0; if (_pcmb->_fProcessingDup) return 0; // Check to see if there is only one toolbar. if (_pcmb->_pmtbTop == _pcmb->_pmtbBottom) return 0; ToolBar_HasAccelerator(_pcmb->_pmtbTop->_hwndMB, pnmwa->ch, &iNumTopAccel); ToolBar_HasAccelerator(_pcmb->_pmtbBottom->_hwndMB, pnmwa->ch, &iNumBottomAccel); _pcmb->_fProcessingDup = TRUE; CMenuToolbarBase* pmbtb = NULL; if (_pcmb->_pmtbTracked->_dwFlags & SMSET_TOP) { ToolBar_MapAccelerator(_pcmb->_pmtbBottom->_hwndMB, pnmwa->ch, &iHotItem); pmbtb = _pcmb->_pmtbBottom; } else { ToolBar_MapAccelerator(_pcmb->_pmtbTop->_hwndMB, pnmwa->ch, &iHotItem); pmbtb = _pcmb->_pmtbTop; } _pcmb->_fProcessingDup = FALSE; if (iHotItem != -1) { _pcmb->SetTracked(pmbtb); int idCmd = ToolBar_CommandToIndex(pmbtb->_hwndMB, iHotItem); DWORD dwFlags = HICF_ACCELERATOR; // If either (but not both) toolbars have the accelerator, and it is exactly one, // then cause the drop down. if ( (iNumTopAccel >= 1) ^ (iNumBottomAccel >= 1) && (iNumTopAccel == 1 || iNumBottomAccel == 1) ) dwFlags |= HICF_TOGGLEDROPDOWN; SendMessage(pmbtb->_hwndMB, TB_SETHOTITEM2, idCmd, dwFlags); pnmwa->iButton = -1; return 1; } return 0; } LRESULT CMenuToolbarBase::_OnDupAccelerator(NMTBDUPACCELERATOR* pnmda) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (_pcmb->_fProcessingDup || (_pcmb->_pmtbBottom == _pcmb->_pmtbTop)) return 0; _pcmb->_fProcessingDup = TRUE; int iNumTopAccel = 0; int iNumBottomAccel = 0; if (_pcmb->_pmtbTop) ToolBar_HasAccelerator(_pcmb->_pmtbTop->_hwndMB, pnmda->ch, &iNumTopAccel); if (_pcmb->_pmtbBottom) ToolBar_HasAccelerator(_pcmb->_pmtbBottom->_hwndMB, pnmda->ch, &iNumBottomAccel); _pcmb->_fProcessingDup = FALSE; if (0 == iNumTopAccel && 0 == iNumBottomAccel) { // We want to return 1 if Both of them have one. //Otherwise, return 0, and let the toolbar handle it itself. return 0; } pnmda->fDup = TRUE; return 1; } /*---------------------------------------------------------- Purpose: Handle WM_NOTIFY */ LRESULT CMenuToolbarBase::_OnNotify(LPNMHDR pnm) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first LRESULT lres = 0; CMBMsgFilter* pmf = GetMessageFilter(); // These are notifies we handle even when disengaged from the message hook. switch (pnm->code) { case NM_CUSTOMDRAW: { BOOL fHandled = FALSE; NMCUSTOMDRAW *pnmcd = (NMCUSTOMDRAW*)pnm; // first we give the callback a chance to handle it if (_pcmb->_dwFlags & SMINIT_CUSTOMDRAW) { fHandled = (S_OK == v_CallCBItem((int)pnmcd->dwItemSpec, SMC_CUSTOMDRAW, (WPARAM)&lres, (LPARAM)pnmcd)); } if (!fHandled) { // We now custom draw even the TopLevelMenuBand (for the correct font) lres = v_OnCustomDraw(pnmcd); } } break; case NM_THEMECHANGED: { _RefreshTheme(); } } // Is the Global Message filter Disengaged? This will happen when the Subclassed window // looses activation to a dialog box of some kind. if (lres == 0 && !pmf->IsEngaged()) { // Yes; We've lost activation so we don't want to track like a normal menu... // For hot item change, return 1 so that the toolbar does not change the hot item. if (pnm->code == TBN_HOTITEMCHANGE && _pcmb->_fMenuMode) return 1; // For all other items, don't do anything.... return 0; } switch (pnm->code) { case NM_RELEASEDCAPTURE: pmf->RetakeCapture(); break; case NM_KEYDOWN: BLOCK { LPNMKEY pnmk = (LPNMKEY)pnm; lres = _OnKey(TRUE, pnmk->nVKey, pnmk->uFlags); } break; case NM_CHAR: { LPNMCHAR pnmc = (LPNMCHAR)pnm; if (pnmc->ch == TEXT(' ')) return TRUE; if (pnmc->dwItemNext == -1 && !_pcmb->_fVertical) { // If it's horizontal, then it must be top level. ASSERT(_pcmb->_fTopLevel); _pcmb->_CancelMode(MPOS_FULLCANCEL); } } break; case TBN_HOTITEMCHANGE: lres = _OnHotItemChange((LPNMTBHOTITEM)pnm); break; case NM_LDOWN: // We need to kill the expand timer, because the user might // move out of the chevron and accidentally select another item. if ( (int)((LPNMCLICK)pnm)->dwItemSpec == _idCmdChevron && _idCmdChevron != -1) { KillTimer(_hwndMB, MBTIMER_EXPAND); _fIgnoreHotItemChange = TRUE; } break; case NM_CLICK: { int idCmd = (int)((LPNMCLICK)pnm)->dwItemSpec; _fIgnoreHotItemChange = FALSE; if ( idCmd == -1 ) { _pcmb->_SubMenuOnSelect(MPOS_CANCELLEVEL); _pcmb->SetTracked(NULL); lres = 1; } else if ( idCmd == _idCmdChevron ) { // Retake the capture on the button-up, b/c the toolbar took // it away for a moment. pmf->RetakeCapture(); v_CallCBItem(_idCmdChevron, SMC_CHEVRONEXPAND, 0, 0); Expand(TRUE); _fClickHandled = TRUE; _SetTimer(MBTIMER_CLICKUNHANDLE); lres = 1; } else if (!_fEmpty) { TraceMsg(TF_MENUBAND, "(pmb=%#08lx): upclick %d", this, idCmd); // Retake the capture on the button-up, b/c the toolbar took // it away for a moment. pmf->RetakeCapture(); if (v_GetFlags(idCmd) & SMIF_SUBMENU) // Submenus support double click { if (_iLastClickedTime == 0) // First time it was clicked { _iLastClickedTime = GetTickCount(); _idCmdLastClicked = idCmd; } // Did they click on the same item twice? else if (idCmd != _idCmdLastClicked) { _iLastClickedTime = _idCmdLastClicked = 0; } else { // Was this item double clicked on? if ((GetTickCount() - _iLastClickedTime) < GetDoubleClickTime()) { // We need to post this back to ourselves, because // the Tray will become in active when double clicking // on something like programs. This happens because the // Toolbar will set capture back to itself and the tray // doesn't get any more messages. PostMessage(_hwndMB, g_nMBExecute, idCmd, 0); _fClickHandled = TRUE; } _iLastClickedTime = _idCmdLastClicked = 0; } } // Sent on the button-up. Handle the same way. if (!_fClickHandled && -1 != idCmd) _DropDownOrExec(idCmd, FALSE); _fClickHandled = FALSE; lres = 1; } } break; case TBN_DROPDOWN: lres = _OnDropDown((LPNMTOOLBAR)pnm); break; #ifdef UNICODE case TBN_GETINFOTIPA: { LPNMTBGETINFOTIPA pnmTT = (LPNMTBGETINFOTIPA)pnm; UINT uiCmd = pnmTT->iItem; TCHAR szTip[MAX_PATH]; if ( S_OK == v_GetInfoTip(pnmTT->iItem, szTip, ARRAYSIZE(szTip)) ) { SHUnicodeToAnsi(szTip, pnmTT->pszText, pnmTT->cchTextMax); } else { // Set the lpszText to NULL to prevent the toolbar from setting // the button text by default pnmTT->pszText = NULL; } lres = 1; break; } #endif case TBN_GETINFOTIP: { LPNMTBGETINFOTIP pnmTT = (LPNMTBGETINFOTIP)pnm; UINT uiCmd = pnmTT->iItem; if ( S_OK != v_GetInfoTip(pnmTT->iItem, pnmTT->pszText, pnmTT->cchTextMax) ) { // Set the lpszText to NULL to prevent the toolbar from setting // the button text by default pnmTT->pszText = NULL; } lres = 1; break; } case NM_RCLICK: // When we go into a context menu, stop monitoring. KillTimer(_hwndMB, MBTIMER_EXPAND); KillTimer(_hwndMB, MBTIMER_UEMTIMEOUT); break; case TBN_WRAPHOTITEM: lres = _OnWrapHotItem((NMTBWRAPHOTITEM*)pnm); break; case TBN_WRAPACCELERATOR: lres = _OnWrapAccelerator((NMTBWRAPACCELERATOR*)pnm); break; case TBN_DUPACCELERATOR: lres = _OnDupAccelerator((NMTBDUPACCELERATOR*)pnm); break; case TBN_DRAGOVER: // This message is sent when drag and drop within the toolbar indicates that it // is about to mark a button. Since this gets messed up because of LockWindowUpdate // we tell it not to do anything. lres = 1; break; } return(lres); } BOOL CMenuToolbarBase::_SetTimer(int nTimer) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first long lTimeOut; // If we're on NT5 or Win98, use the cool new SPI if (SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, &g_lMenuPopupTimeout, 0)) { // Woo-hoo, all done. } else if (g_lMenuPopupTimeout == -1) { // NT4 or Win95. Grovel the registry (yuck). DWORD dwType; TCHAR szDelay[6]; // int is 5 characters + null. DWORD cbSize = ARRAYSIZE(szDelay); g_lMenuPopupTimeout = MBTIMER_TIMEOUT; if (ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, TEXT("Control Panel\\Desktop"), TEXT("MenuShowDelay"), &dwType, (void*)szDelay, &cbSize)) { g_lMenuPopupTimeout = (UINT)StrToInt(szDelay); } } lTimeOut = g_lMenuPopupTimeout; switch (nTimer) { case MBTIMER_EXPAND: case MBTIMER_DRAGPOPDOWN: lTimeOut *= 2; if (lTimeOut < MAXUEMTIMEOUT) lTimeOut = MAXUEMTIMEOUT; break; case MBTIMER_UEMTIMEOUT: if (!_fHasDemotedItems || _pcmb->_pmbState->GetExpand() || _fEditMode) return TRUE; lTimeOut *= 5; // We want a minimum of MAXUEMTIMEOUT for people who set the expand rate to zero if (lTimeOut < MAXUEMTIMEOUT) lTimeOut = MAXUEMTIMEOUT; TraceMsg(TF_MENUBAND, "*** UEM SetTimeOut to (%d) milliseconds" "at Tick Count (%d).*** ", GetTickCount()); break; case MBTIMER_CHEVRONTIP: lTimeOut = 60 * 1000; // Please make the intellimenu's balloon tip go // away after one minute of no action. break; case MBTIMER_INFOTIP: lTimeOut = 500; // Half a second hovering over an item? break; } TraceMsg(TF_MENUBAND, "(pmb=%#08lx): Setting %d Timer to %d milliseconds at tickcount %d", this, nTimer, lTimeOut, GetTickCount()); return (BOOL)SetTimer(_hwndMB, nTimer, lTimeOut, NULL); } BOOL CMenuToolbarBase::_HandleObscuredItem(int idCmd) { RECT rc; GetClientRect(_hwndMB, &rc); int iButton = (int)SendMessage(_hwndMB, TB_COMMANDTOINDEX, idCmd, 0); if (SHIsButtonObscured(_hwndMB, &rc, iButton)) { // clear hot item ToolBar_SetHotItem(_hwndMB, -1); _pcmb->_SubMenuOnSelect(MPOS_FULLCANCEL); _pcmb->_CancelMode(MPOS_FULLCANCEL); // This is for the track menus. HWND hwnd = _pcmb->_pmbState->GetSubclassedHWND(); PostMessage(hwnd? hwnd: _hwndMB, g_nMBOpenChevronMenu, (WPARAM)idCmd, 0); return TRUE; } return FALSE; } LRESULT CMenuToolbarBase::_OnHotItemChange(NMTBHOTITEM * pnmhot) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first LRESULT lres = 0; if (_pcmb->_fMenuMode && _pcmb->_fShow && !_fIgnoreHotItemChange) { // Always kill the expand timer when something changes KillTimer(_hwndMB, MBTIMER_EXPAND); KillTimer(_hwndMB, MBTIMER_INFOTIP); // Is this toolbar being entered? if (!(pnmhot->dwFlags & HICF_LEAVING)) { // Yes; set it to be the currently tracking toolbar TraceMsg(TF_MENUBAND, "CMTB::OnHotItemChange. Setting Tracked....", this); _pcmb->SetTracked(this); _pcmb->_pmbState->HideTooltip(FALSE); _SetTimer(MBTIMER_INFOTIP); } // If the Toolbar has keybaord focus, we need to send OBJID_CLIENT so that we track correctly. if (!(pnmhot->dwFlags & HICF_LEAVING)) { NotifyWinEvent(EVENT_OBJECT_FOCUS, _hwndMB, OBJID_CLIENT, GetIndexFromChild(_dwFlags & SMSET_TOP, ToolBar_CommandToIndex(_hwndMB, pnmhot->idNew))); } DEBUG_CODE( TraceMsg(TF_MENUBAND, "(pmb=%#08lx): TBN_HOTITEMCHANGE (state:%#02lx, %d-->%d)", this, pnmhot->dwFlags, (pnmhot->dwFlags & HICF_ENTERING) ? -1 : pnmhot->idOld, (pnmhot->dwFlags & HICF_LEAVING) ? -1 : pnmhot->idNew); ) // While in edit mode, we do not automatically cascade // submenus, unless while dropping. But the dropping case // is handled in HitTest, not here. So don't deal with that // here. // Is this because an accelerator key was hit? if (pnmhot->dwFlags & HICF_ACCELERATOR) { KillPopupTimer(); KillTimer(_hwndMB, MBTIMER_UEMTIMEOUT); // Yes; now that TBSTYLE_DROPDOWN is used, let _DropDownOrExec handle it // in response to TBN_DROPDOWN. } // Is this because direction keys were hit? else if (pnmhot->dwFlags & HICF_ARROWKEYS) { // Yes KillPopupTimer(); KillTimer(_hwndMB, MBTIMER_UEMTIMEOUT); if (!_fVerticalMB && _HandleObscuredItem(pnmhot->idNew)) { lres = 1; } else { // It doesn't make sense that we would get these keyboard // notifications if there is a submenu open...it should get // the messages ASSERT(!_pcmb->_fInSubMenu); v_SendMenuNotification(pnmhot->idNew, FALSE); // Since the only way that the chevron can get the highlight is // through a keyboard down, then we expand. if (_fHasDemotedItems && pnmhot->idNew == (int)_idCmdChevron) { v_CallCBItem(_idCmdChevron, SMC_CHEVRONEXPAND, 0, 0); Expand(TRUE); lres = 1; // We already handled the hot item change } } _pcmb->_pmbState->HideTooltip(FALSE); _SetTimer(MBTIMER_INFOTIP); } // Is this because the mouse moved or an explicit sendmessage? else if (!(pnmhot->dwFlags & HICF_LEAVING) && (pnmhot->idNew != _pcmb->_nItemCur || // Ignore if we're moving over same item (_nItemTimer != -1 && _pcmb->_nItemCur == pnmhot->idNew))) // we need to go through here to reset if the user went back to the cascaded guy { // Yes if (!_fVerticalMB) // Horizontal menus will always have an underlying hmenu { if (_HandleObscuredItem(pnmhot->idNew)) { lres = 1; } else if (_pcmb->_fInSubMenu) { // Only popup a menu since we're already in one (as mouse // moves across bar). TraceMsg(TF_MENUBAND, "(pmb=%#08lx): TBN_HOTITEMCHG: Posting CMBPopup message", this); PostPopup(pnmhot->idNew, FALSE, _pcmb->_fKeyboardSelected); // Will handle menu notification on receipt of message } else v_SendMenuNotification(pnmhot->idNew, FALSE); } else if (!_fEditMode) { v_SendMenuNotification(pnmhot->idNew, FALSE); // check to see if we have just entered a new item and it is a sub-menu... // Did we already set a timer? if (-1 != _nItemTimer) { // Yes; kill it b/c the mouse moved to another item KillPopupTimer(); } // if we're not over the currently expanded guy // Have we moved over an item that expands OR // are we moving away from a cascaded item? DWORD dwFlags = v_GetFlags(pnmhot->idNew); // Reset the user timer KillTimer(_hwndMB, MBTIMER_UEMTIMEOUT); // UEMStuff if (!(dwFlags & SMIF_SUBMENU)) { _SetTimer(MBTIMER_UEMTIMEOUT); _FireEvent(UEM_HOT_ITEM); } if ( (pnmhot->dwFlags & HICF_MOUSE) && _pcmb->_nItemCur != pnmhot->idNew) { if (dwFlags & SMIF_SUBMENU || _pcmb->_fInSubMenu) { // Is this the only item in the menu? if ( _cPromotedItems == 1 && !(_fHasDemotedItems && _pcmb->_fExpanded) && dwFlags & SMIF_SUBMENU) { // Yes; Then we want to pop it open immediatly, // instead of waiting for the timeout PostPopup(pnmhot->idNew, FALSE, FALSE); } else if (_SetTimer(MBTIMER_POPOUT)) { // No; fire a timer to open/close the submenu TraceMsg(TF_MENUBAND, "(pmb=%#08lx): TBN_HOTITEMCHG: Starting timer for id=%d", this, pnmhot->idNew); if (v_GetFlags(pnmhot->idNew) & SMIF_SUBMENU) _nItemTimer = pnmhot->idNew; else _nItemTimer = -1; } } if (_fHasDemotedItems && pnmhot->idNew == (int)_idCmdChevron) { _SetTimer(MBTIMER_EXPAND); } _pcmb->_pmbState->HideTooltip(FALSE); _SetTimer(MBTIMER_INFOTIP); } } } else if (pnmhot->dwFlags & HICF_LEAVING) { v_SendMenuNotification(pnmhot->idOld, TRUE); if (-1 != _nItemTimer && !_fEditMode) { // kill the cascading menu popup timer... TraceMsg(TF_MENUBAND, "(pmb=%#08lx): TBN_HOTITEMCHG: Killing timer", this); KillPopupTimer(); } _pcmb->_pmbState->HideTooltip(FALSE); } if ( !(pnmhot->dwFlags & HICF_LEAVING) ) _pcmb->_SiteOnSelect(MPOS_CHILDTRACKING); } return lres; } LRESULT CMenuToolbarBase::_DropDownOrExec(UINT idCmd, BOOL bKeyboard) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first TraceMsg(TF_MENUBAND, "(pmb=%#08lx): _DropDownOrExec %d", this, idCmd); // Don't do anything when we're in edit mode if (_fEditMode) return 0; if ( v_GetFlags(idCmd) & SMIF_SUBMENU ) { v_SendMenuNotification(idCmd, FALSE); PostPopup(idCmd, FALSE, bKeyboard); } else if (idCmd != -1) { RECT rc; AddRef(); // I might get released in the call. // Fading Selection SHPlaySound(TEXT("MenuCommand")); SendMessage(_hwndMB, TB_GETRECT, idCmd, (LPARAM)&rc); MapWindowPoints(_hwndMB, HWND_DESKTOP, (POINT*)&rc, 2); if (!(GetKeyState(VK_SHIFT) < 0)) { // Initiate the fade (if enabled) before blowing away the menus _pcmb->_pmbState->FadeRect(&rc); _pcmb->_SiteOnSelect(MPOS_EXECUTE); } if (g_dwProfileCAP & 0x00002000) StartCAP(); v_ExecItem(idCmd); if (g_dwProfileCAP & 0x00002000) StopCAP(); Release(); } else MessageBeep(MB_OK); return 0; } /*---------------------------------------------------------- Purpose: Handles TBN_DROPDOWN, which is sent on the button-down. */ LRESULT CMenuToolbarBase::_OnDropDown(LPNMTOOLBAR pnmtb) { DWORD dwInput = _fTopLevel ? 0 : -1; // -1: don't track, 0: do ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first LRESULT lres = 0; // Expected behavior with the mouse: // // 1) For cascading menuitems- // a) expand on button-down // b) collapse on button-up (horizontal menu only) // c) if the button-down occurs on the item that is // already selected, then assume the click indicates // a drag/drop scenario // 2) For other menuitems- // a) execute on button-up #ifdef DEBUG if (_fTopLevel) { // browser menu comes thru here; start menu goes elsewhere (via tray.c) //ASSERT(!_fVertical); TraceMsg(DM_MISC, "cmtbb._odd: _fTopLevel(1) mouse=%d", GetKeyState(VK_LBUTTON) < 0); } #endif // Is this because the mouse button was used? if (GetKeyState(VK_LBUTTON) < 0) { // Yes // Assume it won't be handled. This will allow the toolbar // to see the button-down as a potential drag and drop. lres = TBDDRET_TREATPRESSED; // Clicking on same item that is currently expanded? if (pnmtb->iItem == _pcmb->_nItemCur) { // Is this horizontal? if (!_fVerticalMB) { // Yes; toggle the dropdown _pcmb->_SubMenuOnSelect(MPOS_FULLCANCEL); // Say it is handled, so the button will toggle lres = TBDDRET_DEFAULT; } _fClickHandled = TRUE; // Otherwise don't do anything more, user might be starting a // drag-drop procedure on the cascading menuitem } else { if (v_GetFlags(pnmtb->iItem) & SMIF_SUBMENU) { // Handle on the button-down _fClickHandled = TRUE; lres = _DropDownOrExec(pnmtb->iItem, FALSE); } } if (dwInput != -1) dwInput = UIBL_INPMOUSE; } else { // No; must be the keyboard _fClickHandled = TRUE; lres = _DropDownOrExec(pnmtb->iItem, TRUE); if (dwInput != -1) dwInput = UIBL_INPMENU; } // browser menu (*not* start menu) alt+key, mouse if (dwInput != -1) UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_UIINPUT, dwInput); return lres; } /*---------------------------------------------------------- Purpose: Handle WM_KEYDOWN/WM_KEYUP Returns: TRUE if handled */ BOOL CMenuToolbarBase::_OnKey(BOOL bDown, UINT vk, UINT uFlags) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first int idCmd; HWND hwnd = _hwndMB; _pcmb->_pmbState->SetKeyboardCue(TRUE); // // If the menu window is RTL mirrored, then the arrow keys should // be mirrored to reflect proper cursor movement. [samera] // if (IS_WINDOW_RTL_MIRRORED(hwnd)) { switch (vk) { case VK_LEFT: vk = VK_RIGHT; break; case VK_RIGHT: vk = VK_LEFT; break; } } switch (vk) { case VK_LEFT: if (_fVerticalMB) { _pcmb->_SiteOnSelect(MPOS_SELECTLEFT); return TRUE; } break; case VK_RIGHT: if (_fVerticalMB) goto Cascade; break; case VK_DOWN: case VK_UP: if (!_fVerticalMB) { Cascade: idCmd = GetButtonCmd(hwnd, ToolBar_GetHotItem(hwnd)); if (v_GetFlags(idCmd) & SMIF_SUBMENU) { // Enter the submenu TraceMsg(TF_MENUBAND, "(pmb=%#08lx): _OnKey: Posting CMBPopup message", this); PostPopup(idCmd, FALSE, TRUE); } else if (VK_RIGHT == vk) { // Nothing to cascade to, move to next sibling menu _pcmb->_SiteOnSelect(MPOS_SELECTRIGHT); } return TRUE; } else { #if 0 _pcmb->_OnSelectArrow(vk == VK_UP? -1 : 1); return TRUE; #endif } break; case VK_SPACE: if (!_pcmb->_fExpanded && _fHasDemotedItems) { v_CallCBItem(_idCmdChevron, SMC_CHEVRONEXPAND, 0, 0); Expand(TRUE); } else { // Toolbars map the spacebar to VK_RETURN. Menus don't except // in the horizontal menubar. if (_fVerticalMB) MessageBeep(MB_OK); } return TRUE; #if 0 case VK_RETURN: // Handle this now, rather than letting the toolbar handle it. // This way we don't have to rely on WM_COMMAND, which doesn't // convey whether it was invoked by the keyboard or the mouse. idCmd = GetButtonCmd(hwnd, ToolBar_GetHotItem(hwnd)); _DropDownOrExec(idCmd, TRUE); return TRUE; #endif } return FALSE; } /*---------------------------------------------------------- Purpose: There are two flavors of this function: _DoPopup and PostPopup. Both cancel the existing submenu (relative to this band) and pops open a new submenu. _DoPopup does it atomically. PostPopup posts a message to handle it. */ void CMenuToolbarBase::_DoPopup(int idCmd, BOOL bInitialSelect) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (-1 != idCmd) { PopupHelper(idCmd, bInitialSelect); } } /*---------------------------------------------------------- Purpose: See the _DoPopup comment */ void CMenuToolbarBase::PostPopup(int idCmd, BOOL bSetItem, BOOL bInitialSelect) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (-1 != idCmd) { _pcmb->_SubMenuOnSelect(MPOS_CANCELLEVEL); _pcmb->SetTracked(this); HWND hwnd = _pcmb->_pmbState->GetSubclassedHWND(); PostMessage(hwnd? hwnd: _hwndMB, g_nMBPopupOpen, idCmd, MAKELPARAM(bSetItem, bInitialSelect)); } } /*---------------------------------------------------------- Purpose: Helper function to finally invoke submenu. Use _DoPopup or PostPopup */ void CMenuToolbarBase::PopupHelper(int idCmd, BOOL bInitialSelect) { // We do not want to pop open a sub menu if we are not displayed. This is especially // a problem during drag and drop. if (_fShowMB) { _pcmb->_nItemNew = idCmd; ASSERT(-1 != _pcmb->_nItemNew); _pcmb->SetTracked(this); _pcmb->_fPopupNewMenu = TRUE; _pcmb->_fInitialSelect = BOOLIFY(bInitialSelect); _pcmb->UIActivateIO(TRUE, NULL); _FireEvent(UEM_HOT_FOLDER); _SetTimer(MBTIMER_UEMTIMEOUT); } } // Paints the 3d rect around a button. void CMenuToolbarBase::_PaintButton3D(HDC hdc, int idCmd, LPRECT prc, DWORD dwSMIF) { // Only needed when the menu is expanded. // We don't need no stink'n 3D stuff in Flat mode. if (!_pcmb->_fExpanded || _pcmb->_pmbm->_fFlatMenuMode) return; ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first RECT rcClient; GetClientRect(_hwndMB, &rcClient); #ifndef DRAWEDGE // Draw Left Edge HPEN hPenOld = (HPEN)SelectObject(hdc, _pcmb->_pmbm->_hPenHighlight); MoveToEx(hdc, prc->left, prc->top, NULL); LineTo(hdc, prc->left, prc->bottom); #endif if (!(dwSMIF & SMIF_DEMOTED)) { #ifdef DRAWEDGE DWORD dwEdge = BF_RIGHT; // Don't paint the edge next to the bitmap. if (_uIconSizeMB == ISFBVIEWMODE_SMALLICONS) dwEdge |= BF_LEFT; RECT rc = *prc; #else // Draw Right Edge: SelectObject(hdc, _pcmb->_pmbm->_hPenShadow); MoveToEx(hdc, prc->right-1, prc->top, NULL); LineTo(hdc, prc->right-1, prc->bottom); #endif HWND hwnd = _hwndMB; int iPos = ToolBar_CommandToIndex(hwnd, idCmd); if (iPos == -1) { iPos = ToolBar_ButtonCount(hwnd) - 1; } if (iPos >= 0) { int iNumButtons = ToolBar_ButtonCount(hwnd); int idCmd2 = GetButtonCmd(hwnd, iPos + 1); CMenuToolbarBase* pmtb = this; BOOL fOverflowed = FALSE; // Situations for Drawing the Bottom line // 1) This button is at the bottom. // 2) This button is at the bottom and the toolbar // below is not visible (_fDontShowEmpty). // 3) This button is at the bottom and the button // at the top of the bottom toolbar is demoted. // 4) The button below this one in the toolbar is // demoted. // 5) The botton below this one is demoted and we're // not expanded if (iPos + 1 >= iNumButtons) { if (_pcmb->_pmtbBottom != this && !_pcmb->_pmtbBottom->_fDontShowEmpty) { pmtb = _pcmb->_pmtbBottom; hwnd = pmtb->_hwndMB; idCmd2 = GetButtonCmd(hwnd, 0); } else fOverflowed = TRUE; } else if (prc->bottom == rcClient.bottom && _pcmb->_pmtbBottom == this) // This button is at the top. fOverflowed = TRUE; DWORD dwFlags = pmtb->v_GetFlags(idCmd2); if ((_pcmb->_fExpanded && dwFlags & SMIF_DEMOTED) || fOverflowed) { #ifdef DRAWEDGE dwEdge |= BF_BOTTOM; #else int iLeft = prc->left; if (iPos != iNumButtons - 1) iLeft ++; // Move the next line in. MoveToEx(hdc, iLeft, prc->bottom-1, NULL); LineTo(hdc, prc->right-1, prc->bottom-1); #endif } // Situations for Drawing the Top line // 1) This button is at the top. // 2) This button is at the top and the toolbar // above is not visible (_fDontShowEmpty). // 3) This button is at the top and the button // at the bottom of the top toolbar is demoted. // 4) The button above this one in the toolbar is // demoted. // 5) If the button above this is demoted, and we're // not expanded fOverflowed = FALSE; if (iPos - 1 < 0) { if (_pcmb->_pmtbTop != this && !_pcmb->_pmtbTop->_fDontShowEmpty) { pmtb = _pcmb->_pmtbTop; hwnd = pmtb->_hwndMB; idCmd2 = GetButtonCmd(hwnd, ToolBar_ButtonCount(hwnd) - 1); } else fOverflowed = TRUE; // There is nothing at the top of this menu, draw the line. } else { hwnd = _hwndMB; idCmd2 = GetButtonCmd(hwnd, iPos - 1); pmtb = this; if (prc->top == rcClient.top && _pcmb->_pmtbTop == this) // This button is at the top. fOverflowed = TRUE; } dwFlags = pmtb->v_GetFlags(idCmd2); if ((_pcmb->_fExpanded && dwFlags & SMIF_DEMOTED) || fOverflowed) { #ifdef DRAWEDGE dwEdge |= BF_TOP; #else SelectObject(hdc, _pcmb->_pmbm->_hPenHighlight); MoveToEx(hdc, prc->left, prc->top, NULL); LineTo(hdc, prc->right-1, prc->top); #endif } } #ifdef DRAWEDGE DrawEdge(hdc, &rc, BDR_RAISEDINNER, dwEdge); #endif } #ifndef DRAWEDGE SelectObject(hdc, hPenOld); #endif } int GetTBImageListWidth(HWND hwnd) { int cx = 0; int cy; HIMAGELIST himl = (HIMAGELIST)SendMessage(hwnd, TB_GETIMAGELIST, 0, 0); if (himl) { // Start with the width of the button ImageList_GetIconSize(himl, &cx, &cy); } return cx; } LRESULT CMenuToolbarBase::v_OnCustomDraw(NMCUSTOMDRAW * pnmcd) { // Make it look like a menu NMTBCUSTOMDRAW * ptbcd = (NMTBCUSTOMDRAW *)pnmcd; DWORD dwRet = 0; DWORD dwSMIF = v_GetFlags((UINT)pnmcd->dwItemSpec); // Edit mode never hot tracks, and the selected item being // moved has a black frame around it. Items that cascade are // still highlighted normally, even in edit mode. switch(pnmcd->dwDrawStage) { case CDDS_PREPAINT: dwRet = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT; break; case CDDS_ITEMPREPAINT: if (_fVerticalMB) { if (pnmcd->dwItemSpec == -1) { if (_hTheme) { DrawThemeBackground(_hTheme, pnmcd->hdc, MDP_SEPERATOR, 0, &pnmcd->rc, 0); } else { // a -1 is sent with a seperator RECT rc = pnmcd->rc; rc.top += 3; // Hard coded in toolbar. rc.right -= GetSystemMetrics(SM_CXEDGE); rc.left += GetSystemMetrics(SM_CXEDGE); if (_pcmb->_pmbm->_fFlatMenuMode) { // Sep is a 1 pixel line rc.bottom = rc.top + 1; SHFillRectClr(pnmcd->hdc, &rc, GetSysColor(COLOR_BTNSHADOW)); } else DrawEdge(pnmcd->hdc, &rc, EDGE_ETCHED, BF_TOP); _PaintButton3D(pnmcd->hdc, -1, &pnmcd->rc, dwSMIF); } dwRet = CDRF_SKIPDEFAULT; } else { ptbcd->clrText = _pcmb->_pmbm->_clrMenuText; // This is for Darwin Ads. if (dwSMIF & SMIF_ALTSTATE) { ptbcd->clrText = GetSysColor(COLOR_BTNSHADOW); } ptbcd->rcText.right = ptbcd->rcText.right - _pcmb->_pmbm->_cxMargin + ICONFUDGE; ptbcd->clrBtnFace = (_pcmb->_pmbm->_fFlatMenuMode && !GetSystemMetrics(SM_REMOTESESSION)) ? CLR_NONE : _pcmb->_pmbm->_clrBackground; if (_hTheme) { if (dwSMIF & SMIF_SUBMENU) ptbcd->rcText.right -= _pcmb->_pmbm->_cxArrow; } else { if (_fHasSubMenu) ptbcd->rcText.right -= _pcmb->_pmbm->_cxArrow; } // Draw the chevron. if ( _fHasDemotedItems && _idCmdChevron == (int)pnmcd->dwItemSpec && !_pcmb->_pmbm->_fFlatMenuMode) // In flat mode, we only draw the chevron glyph, which is below. { _DrawChevron(pnmcd->hdc, &pnmcd->rc, (BOOL)(pnmcd->uItemState & CDIS_HOT) || (BOOL)(pnmcd->uItemState & CDIS_MARKED), (BOOL)(pnmcd->uItemState & CDIS_SELECTED)); dwRet |= CDRF_SKIPDEFAULT; } else { if (!_pszTheme) { // Yes; draw with highlight if (pnmcd->uItemState & (CDIS_CHECKED | CDIS_SELECTED | CDIS_HOT)) { if (_pcmb->_pmbm->_fFlatMenuMode) { ptbcd->clrHighlightHotTrack = GetSysColor(COLOR_MENUHILIGHT); ptbcd->clrBtnFace = GetSysColor(COLOR_MENUHILIGHT); } else { ptbcd->clrHighlightHotTrack = GetSysColor(COLOR_HIGHLIGHT); ptbcd->clrBtnFace = GetSysColor(COLOR_HIGHLIGHT); } ptbcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT); dwRet |= TBCDRF_HILITEHOTTRACK; } } // Is this menu empty? if (_fEmpty) { // Yes, draw the empty string as disabled. if (!_hTheme) { pnmcd->uItemState |= CDIS_DISABLED; } ptbcd->clrText = ptbcd->clrBtnFace; // Don't draw the etched effect if it is selected if (pnmcd->uItemState & CDIS_HOT) dwRet |= TBCDRF_NOETCHEDEFFECT; } // When this item is demoted, we only want to paint his background // then we are in edit mode _OR_ it is not selected, checked or hot. if (dwSMIF & SMIF_DEMOTED) { BOOL fDrawDemoted = TRUE; if (_fEditMode) fDrawDemoted = TRUE; if (pnmcd->uItemState & (CDIS_CHECKED | CDIS_SELECTED | CDIS_HOT)) fDrawDemoted = FALSE; if (fDrawDemoted) { RECT rc = pnmcd->rc; // In flat mode we only paint the icon background in a demoted state. This matches office COLORREF crDemoted = _pcmb->_pmbm->_clrDemoted; if (_pszTheme) { crDemoted = RGB(GetRValue(COLOR_MENU) / 2, GetGValue(COLOR_MENU) / 2, GetBValue(COLOR_MENU) / 2); } if (_pcmb->_pmbm->_fFlatMenuMode) { rc.right = rc.left + GetTBImageListWidth(_hwndMB) + ICONBACKGROUNDFUDGE; SHFillRectClr(pnmcd->hdc, &rc, crDemoted); } else { ptbcd->clrBtnFace = CLR_NONE; SHFillRectClr(pnmcd->hdc, &rc, crDemoted); } } } // "New" items get the tooltip look if (dwSMIF & SMIF_NEW && !(pnmcd->uItemState & (CDIS_CHECKED | CDIS_SELECTED | CDIS_HOT))) { if (_hTheme) { DrawThemeBackground(_hTheme, pnmcd->hdc, MDP_NEWAPPBUTTON, 0, &pnmcd->rc, 0); dwRet |= TBCDRF_NOBACKGROUND; } else { ptbcd->clrBtnFace = CLR_NONE; SHFillRectClr(pnmcd->hdc, &pnmcd->rc, GetSysColor(COLOR_INFOBK)); ptbcd->clrText = GetSysColor(COLOR_INFOTEXT); } } // We draw our own highlighting if (!_pszTheme) { dwRet |= (TBCDRF_NOEDGES | TBCDRF_NOOFFSET); } } } } else { // If g_fRunOnMemphis or g_fRunOnNT5 are not defined then the menus will // never be grey. if (!_pcmb->_fAppActive) // menus from user use Button Shadow for non active menus ptbcd->clrText = GetSysColor(COLOR_3DSHADOW); else ptbcd->clrText = _pcmb->_pmbm->_clrMenuText; // If we're in high contrast mode make the menu bar look like // veritcal items on select or in flat mode use the new "selection" ui. if (_pcmb->_pmbm->_fHighContrastMode || _pcmb->_pmbm->_fFlatMenuMode) { // Yes; draw with highlight if (pnmcd->uItemState & (CDIS_CHECKED | CDIS_SELECTED | CDIS_HOT)) { ptbcd->clrHighlightHotTrack = GetSysColor(COLOR_HIGHLIGHT); ptbcd->clrBtnFace = GetSysColor(COLOR_HIGHLIGHT); ptbcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT); if (!_pszTheme) { dwRet |= (TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_HILITEHOTTRACK); } } } } dwRet |= CDRF_NOTIFYPOSTPAINT | TBCDRF_NOMARK; break; case CDDS_ITEMPOSTPAINT: if (_fVerticalMB) { RECT rc = pnmcd->rc; COLORREF rgbText = _pcmb->_pmbm->_clrMenuText; if (_pcmb->_pmbm->_fFlatMenuMode) { // Flat menu mode has a rect around the selection. if (pnmcd->uItemState & (CDIS_SELECTED | CDIS_HOT) && !_pszTheme) { SHOutlineRect(pnmcd->hdc, &pnmcd->rc, GetSysColor(COLOR_HIGHLIGHT)); } // Draw the chevron Glyph if ( _fHasDemotedItems && _idCmdChevron == (int)pnmcd->dwItemSpec) { _DrawChevron(pnmcd->hdc, &pnmcd->rc, (BOOL)(pnmcd->uItemState & CDIS_HOT) || (BOOL)(pnmcd->uItemState & CDIS_MARKED), (BOOL)(pnmcd->uItemState & CDIS_SELECTED)); } } else if (pnmcd->uItemState & (CDIS_SELECTED | CDIS_HOT)) { rgbText = GetSysColor( COLOR_HIGHLIGHTTEXT ); } // Is this item Checked? if (dwSMIF & SMIF_CHECKED) { rc.right = rc.left + (rc.bottom - rc.top); _DrawMenuGlyph(pnmcd->hdc, _pcmb->_pmbm->_hFontArrow , &rc, CH_MENUCHECKA, rgbText, NULL); rc = pnmcd->rc; } // Is this a cascading item? if (dwSMIF & SMIF_SUBMENU) { // Yes; draw the arrow COLORREF crArrow = rgbText; if (_hTheme) { int iState = MDS_NORMAL; if (pnmcd->uItemState & CDIS_DISABLED) iState = MDS_DISABLED; else if ((pnmcd->uItemState & CDIS_HOT) && (pnmcd->uItemState & CDIS_CHECKED)) iState = MDS_HOTCHECKED; else if (pnmcd->uItemState & CDIS_HOT) iState = MDS_HOT; else if (pnmcd->uItemState & CDIS_CHECKED) iState = MDS_CHECKED; GetThemeColor(_hTheme, 0, iState, TMT_TEXTCOLOR, &crArrow); } RECT rcT = rc; RECT rcClient; GetClientRect(_hwndMB, &rcClient); if (rcClient.right < rcT.right) { rcT.right = rcClient.right; } rcT.left = rcT.right - _pcmb->_pmbm->_cxArrow; _DrawMenuArrowGlyph(pnmcd->hdc, &rcT, crArrow); } if (!_pszTheme) { _PaintButton3D(pnmcd->hdc, (UINT)pnmcd->dwItemSpec, &rc, dwSMIF); } } break; case CDDS_PREERASE: if (!_pszTheme) { RECT rcClient; GetClientRect(_hwndMB, &rcClient); ptbcd->clrBtnFace = _pcmb->_pmbm->_clrBackground; SHFillRectClr(pnmcd->hdc, &rcClient, _pcmb->_pmbm->_clrBackground); dwRet = CDRF_SKIPDEFAULT; } break; } return dwRet; } void CMenuToolbarBase::_PressBtn(int idBtn, BOOL bDown) { if (!_fVerticalMB) { DWORD dwState = ToolBar_GetState(_hwndMB, idBtn); if (bDown) dwState |= TBSTATE_PRESSED; else dwState &= ~TBSTATE_PRESSED; ToolBar_SetState(_hwndMB, idBtn, dwState); // Avoid ugly late repaints UpdateWindow(_hwndMB); } } /*---------------------------------------------------------- Purpose: IWinEventHandler::OnWinEvent method Processes messages passed on from the menuband. */ STDMETHODIMP CMenuToolbarBase::OnWinEvent(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* plres) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first HRESULT hres = S_FALSE; EnterModeless(); switch (uMsg) { case WM_SETTINGCHANGE: if ((SHIsExplorerIniChange(wParam, lParam) == EICH_UNKNOWN) || (wParam == SPI_SETNONCLIENTMETRICS)) { v_UpdateIconSize(-1, TRUE); v_Refresh(); goto L_WM_SYSCOLORCHANGE; } break; case WM_SYSCOLORCHANGE: L_WM_SYSCOLORCHANGE: ToolBar_SetInsertMarkColor(_hwndMB, GetSysColor(COLOR_MENUTEXT)); SendMessage(_hwndMB, uMsg, wParam, lParam); InvalidateRect(_hwndMB, NULL, TRUE); hres = S_OK; break; case WM_PALETTECHANGED: InvalidateRect( _hwndMB, NULL, FALSE ); SendMessage( _hwndMB, uMsg, wParam, lParam ); hres = S_OK; break; case WM_NOTIFY: *plres = _OnNotify((LPNMHDR)lParam); hres = S_OK; break; } ExitModeless(); return hres; } void CMenuToolbarBase::v_CalcWidth(int* pcxMin, int* pcxMax) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first ASSERT(IS_VALID_WRITE_PTR(pcxMin, int)); ASSERT(IS_VALID_WRITE_PTR(pcxMax, int)); *pcxMin = 0; *pcxMax = 0; if (_fVerticalMB && _pcmb->_pmbm && _pcmb->_pmbm->_hFontMenu) { HIMAGELIST himl; int cel; int cxItemMax = 0; int cyItemMax = 0; HWND hwnd = _hwndMB; ASSERT(hwnd); HDC hdc = GetDC(hwnd); if (hdc) { HFONT hFontOld = (HFONT) SelectObject(hdc, _pcmb->_pmbm->_hFontMenu); if (hFontOld) { TCHAR sz[MAX_PATH]; cel = ToolBar_ButtonCount(hwnd); // Find the maximum length text for(int i = 0; i < cel; i++) { int idCmd = GetButtonCmd(hwnd, i); if (_idCmdChevron != idCmd && !(!_pcmb->_fExpanded && v_GetFlags(idCmd) & SMIF_DEMOTED) && SendMessage(hwnd, TB_GETBUTTONTEXT, idCmd, (LPARAM)sz) > 0) { RECT rect = {0}; DWORD dwDTFlags = DT_CALCRECT | DT_SINGLELINE | DT_LEFT | DT_VCENTER; if (ShowAmpersand()) dwDTFlags |= DT_NOPREFIX; DrawText(hdc, sz, -1, &rect, dwDTFlags); cxItemMax = max(rect.right, cxItemMax); cyItemMax = max(rect.bottom, cyItemMax); } } SelectObject(hdc, hFontOld); } ReleaseDC(hwnd, hdc); } himl = (HIMAGELIST)SendMessage(hwnd, TB_GETIMAGELIST, 0, 0); if (himl) { int cy; // Start with the width of the button ImageList_GetIconSize(himl, pcxMin, &cy); // We want at least a bit of space around the icon if (_uIconSizeMB != ISFBVIEWMODE_SMALLICONS) { // Old FSMenu code took the height of the larger of // the icon and text then added 2. ToolBar_SetPadding(hwnd, ICONFUDGE, 0); *pcxMin += 10; } else { // Old FSMenu code took the height of the larger of // the icon and text then added cySpacing, which defaults to 6. ToolBar_SetPadding(hwnd, ICONFUDGE, ICONFUDGE); *pcxMin += 3 * GetSystemMetrics(SM_CXEDGE); } cyItemMax = max(cyItemMax, cy) + ICONFUDGE; } RECT rect = {0}; int cxDesired = _pcmb->_pmbm->_cxMargin + cxItemMax + _pcmb->_pmbm->_cxArrow; int cxMax = 0; if (SystemParametersInfoA(SPI_GETWORKAREA, 0, &rect, 0)) { // We're figuring a third of the screen is a good max width cxMax = (rect.right-rect.left) / 3; } *pcxMin += min(cxDesired, cxMax) + LIST_GAP; *pcxMax = *pcxMin; } *pcxMin = max(*pcxMin, _cxMinMenu); TraceMsg(TF_MENUBAND, "CMenuToolbarBase::v_CalcWidth(%d, %d)", *pcxMin, *pcxMax); } void CMenuToolbarBase::_SetToolbarState() { SHSetWindowBits(_hwndMB, GWL_STYLE, TBSTYLE_LIST, TBSTYLE_LIST); } void CMenuToolbarBase::v_ForwardMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { RECT rc; POINT pt; pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); GetWindowRect(_hwndMB, &rc); if (PtInRect(&rc, pt)) { ScreenToClient(_hwndMB, &pt); SendMessage(_hwndMB, uMsg, wParam, MAKELONG(pt.x, pt.y)); } } void CMenuToolbarBase::NegotiateSize() { HWND hwndParent = GetParent(_hwndMB); if (hwndParent && (GetDesktopWindow() != hwndParent)) { RECT rc = {0}; GetClientRect(hwndParent, &rc); _pcmb->OnPosRectChangeDB(&rc); } // If we came in here it's because the Menubar did not change sizes or position. } void CMenuToolbarBase::SetParent(HWND hwndParent) { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first if (hwndParent) { if (!_hwndMB) CreateToolbar(hwndParent); } else { // As an optimization, we implement "disowning" ourselves // as just moving ourselves offscreen. The previous parent // still owns us. The parent is invariably the menusite. RECT rc = {-1,-1,-1,-1}; SetWindowPos(NULL, &rc, 0); } // We want to set the parent all the time because we don't want to destroy the // window with it's parent..... Sizing to -1,-1,-1,-1 causes it not to be displayed. if (_hwndMB) { ::SetParent(_hwndMB, hwndParent); SendMessage(_hwndMB, TB_SETPARENT, (WPARAM)hwndParent, NULL); } } void CMenuToolbarBase::v_OnEmptyToolbar() { ASSERT(_pcmb); // if you hit this assert, you haven't initialized yet.. call SetSite first for (int iNumButtons = ToolBar_ButtonCount(_hwndMB) -1; iNumButtons >= 0; iNumButtons--) { // HACKHACK (lamadio): For some reason, _fEmptyingToolbar gets set to FALSE. // We then Do a TB_DELETEBUTTON, which sends a notify. This does go through on // the top level menubands (Start Menu, Browser menu bar), and deletes the // associated data. We then try and delete it again. // So now, I set null into the sub menu, so that the other code gracefully fails. TBBUTTONINFO tbbi = {0}; tbbi.cbSize = sizeof(tbbi); tbbi.dwMask = TBIF_LPARAM | TBIF_BYINDEX; ToolBar_GetButtonInfo(_hwndMB, iNumButtons, &tbbi); void *pData = (void*)tbbi.lParam; tbbi.lParam = NULL; ToolBar_SetButtonInfo(_hwndMB, iNumButtons, &tbbi); SendMessage(_hwndMB, TB_DELETEBUTTON, iNumButtons, 0); v_OnDeleteButton(pData); } } void CMenuToolbarBase::EmptyToolbar() { if (_hwndMB) { _fEmptyingToolbar = TRUE; v_OnEmptyToolbar(); _fEmptyingToolbar = FALSE; } } void CMenuToolbarBase::v_Close() { EmptyToolbar(); if (_hwndMB) { //Kill timers to prevent race condition KillTimer(_hwndMB, MBTIMER_POPOUT); KillTimer(_hwndMB, MBTIMER_DRAGOVER); KillTimer(_hwndMB, MBTIMER_EXPAND); KillTimer(_hwndMB, MBTIMER_ENDEDIT); KillTimer(_hwndMB, MBTIMER_CLOSE); KillTimer(_hwndMB, MBTIMER_CLICKUNHANDLE); KillTimer(_hwndMB, MBTIMER_DRAGPOPDOWN); DestroyWindow(_hwndMB); _hwndMB = NULL; } } void CMenuToolbarBase::Activate(BOOL fActivate) { if (fActivate == FALSE) { _fEditMode = FALSE; } } int CMenuToolbarBase::_CalcChevronSize() { int dSeg; int dxy = _pcmb->_pmbm->_cyChevron; dxy -= 4; dSeg = dxy / 4; return dSeg * 4 + 4; } void CMenuToolbarBase::_DrawChevron(HDC hdc, LPRECT prect, BOOL fFocus, BOOL fSelected) { RECT rcBox = *prect; RECT rcDrop; const int dExtra = 3; int dxy; rcBox.left += dExtra; rcBox.right -= dExtra; dxy = _CalcChevronSize(); rcDrop.left = ((rcBox.right + rcBox.left) >> 1) - (dxy/4); rcDrop.right = rcDrop.left + dxy - 1; int dSeg = ((RECTWIDTH(rcDrop) - 2) >> 2); rcDrop.top = (rcBox.top + rcBox.bottom)/2 - (2 * dSeg + 1); //rcDrop.bottom = rcBox.top; if (fFocus && !_pszTheme) { if (_pcmb->_pmbm->_fFlatMenuMode) { SHFillRectClr(hdc, prect, GetSysColor(COLOR_MENUHILIGHT)); SHOutlineRect(hdc, prect, GetSysColor(COLOR_HIGHLIGHT)); } else { InflateRect(&rcBox, 0, -3); SHFillRectClr(hdc, &rcBox, _pcmb->_pmbm->_clrDemoted); DrawEdge(hdc, &rcBox, fSelected? BDR_SUNKENINNER : BDR_RAISEDINNER, BF_RECT); if (fSelected) { rcDrop.top += 1; rcDrop.left += 1; } } } HBRUSH hbrOld = SelectBrush(hdc, _pcmb->_pmbm->_hbrText); int y = rcDrop.top + 1; int xBase = rcDrop.left+ dSeg; for (int x = -dSeg; x <= dSeg; x++) { PatBlt(hdc, xBase + x, y, 1, dSeg, PATCOPY); PatBlt(hdc, xBase + x, y+(dSeg<<1), 1, dSeg, PATCOPY); y += (x >= 0) ? -1 : 1; } SelectBrush(hdc, hbrOld); } // Takes into accout Separators, hidden and Disabled items /*---------------------------------------------------------- Purpose: This function sets the nearest legal button to be the hot item, skipping over any separators, or hidden or disabled buttons. */ int CMenuToolbarBase::GetValidHotItem(int iDir, int iIndex, int iCount, DWORD dwFlags) { if (iIndex == MBSI_LASTITEM) { // -2 is special value meaning "last item on toolbar" int cButtons = (int)SendMessage(_hwndMB, TB_BUTTONCOUNT, 0, 0); iIndex = cButtons - 1; } while ( (iCount == -1 || iIndex < iCount) && iIndex >= 0) { TBBUTTON tbb; // Toolbar will trap out of bounds condition when iCount is -1 if (!SendMessage(_hwndMB, TB_GETBUTTON, iIndex, (LPARAM)&tbb)) return -1; int idCmd = GetButtonCmd(_hwndMB, iIndex); if (tbb.fsState & TBSTATE_ENABLED && !(tbb.fsStyle & TBSTYLE_SEP || tbb.fsState & TBSTATE_HIDDEN) && !(v_GetFlags(idCmd) & SMIF_DEMOTED && !_pcmb->_fExpanded) ) { return iIndex; } else iIndex += iDir; } return -1; } BOOL CMenuToolbarBase::SetHotItem(int iDir, int iIndex, int iCount, DWORD dwFlags) { int iPos = GetValidHotItem(iDir, iIndex, iCount, dwFlags); if (iPos >= 0) SendMessage(_hwndMB, TB_SETHOTITEM2, iPos, dwFlags); return (BOOL)(iPos >= 0); } static const BYTE g_rgsStateMap[][3] = { #if defined(FIRST) // T, I, F { 0, 1, 2}, // State 0 { 3, 1, 2}, // State 1 { 4, 1, 2}, // State 2 { 11, 5, 2}, // State 3 { 10, 1, 6}, // State 4 { 7, 1, 2}, // State 5 { 8, 1, 2}, // State 6 { 11, 9, 2}, // State 7 { 10, 1, 10}, // State 8 { 11, 1, 2}, // State 9 { 10, 1, 2}, // State 10 // End State { 12, 1, 2}, // State 11 // Flash. { 10, 1, 2}, // State 12 #elif defined(SECOND) // T, I, F { 0, 1, 2}, // State 0 { 3, 1, 2}, // State 1 { 4, 1, 2}, // State 2 { 11, 5, 6}, // State 3 { 10, 5, 6}, // State 4 { 7, 5, 6}, // State 5 { 8, 9, 6}, // State 6 { 11, 9, 8}, // State 7 { 10, 9, 10}, // State 8 { 11, 9, 8}, // State 9 { 10, 10, 10}, // State 10 // End State { 10, 9, 8}, // State 11 // Flash. { 10, 9, 8}, // State 12 { 10, 9, 8}, // State 13 #elif defined(THIRD) // T, I, F { 0, 1, 2}, // State 0 { 3, 1, 2}, // State 1 { 12, 1, 2}, // State 2 { 11, 5, 6}, // State 3 { 10, 5, 6}, // State 4 { 7, 5, 6}, // State 5 { 13, 5, 6}, // State 6 { 11, 9, 8}, // State 7 { 10, 9, 10}, // State 8 { 11, 9, 8}, // State 9 { 10, 10, 10}, // State 10 // End State { 10, 9, 8}, // State 11 // Flash. { 4, 1, 2}, // State 12 { 8, 5, 6}, // State 13 #else // T, I, F { 0, 1, 2}, // State 0 { 3, 1, 2}, // State 1 { 4, 1, 2}, // State 2 { 11, 5, 6}, // State 3 { 10, 5, 6}, // State 4 { 7, 5, 6}, // State 5 { 8, 5, 6}, // State 6 { 11, 9, 8}, // State 7 { 10, 9, 10}, // State 8 { 11, 9, 8}, // State 9 { 10, 10, 10}, // State 10 // End State { 4, 3, 4}, // State 11 // Flash. #endif }; #define MAX_STATE 13 void CMenuToolbarBase::_FireEvent(BYTE bEvent) { // We don't want to expand and cover up any dialogs. if (_fSuppressUserMonitor) return; if (!_fHasDemotedItems) return; if (UEM_RESET == bEvent) { TraceMsg(TF_MENUBAND, "CMTB::UEM Reset state to 0"); _pcmb->_pmbState->SetUEMState(0); return; } ASSERT(bEvent >= UEM_TIMEOUT && bEvent <= UEM_HOT_FOLDER); BYTE bOldState = _pcmb->_pmbState->GetUEMState(); BYTE bNewState = g_rgsStateMap[_pcmb->_pmbState->GetUEMState()][bEvent]; ASSERT(bOldState >= 0 && bOldState <= MAX_STATE); TraceMsg(TF_MENUBAND, "*** UEM OldState (%d), New State (%d) ***", bOldState, bNewState); _pcmb->_pmbState->SetUEMState(bNewState); switch (bNewState) { case 10: // End State TraceMsg(TF_MENUBAND, "*** UEM Entering State 10. Expanding *** ", bOldState, bNewState); KillTimer(_hwndMB, MBTIMER_UEMTIMEOUT); if (_pcmb->_fInSubMenu) { IUnknown_QueryServiceExec(_pcmb->_pmpSubMenu, SID_SMenuBandChild, &CGID_MenuBand, MBANDCID_EXPAND, 0, NULL, NULL); } else { Expand(TRUE); } _pcmb->_pmbState->SetUEMState(0); break; case 11: // Flash // This gets reset when the flash is done... TraceMsg(TF_MENUBAND, "*** UEM Entering State 11 Flashing *** "); KillTimer(_hwndMB, MBTIMER_UEMTIMEOUT); _FlashChevron(); break; } } void CMenuToolbarBase::_FlashChevron() { if (_idCmdChevron != -1) { _cFlashCount = 0; ToolBar_MarkButton(_hwndMB, _idCmdChevron, FALSE); SetTimer(_hwndMB, MBTIMER_FLASH, MBTIMER_FLASHTIME, NULL); } } LRESULT CMenuToolbarBase::_DefWindowProcMB(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { // Are we being asked for the IAccessible for the client? if (uMsg == WM_GETOBJECT && (OBJID_CLIENT == lParam)) { // Don't process OBJID_MENU. By the time we get here, we ARE the menu. LRESULT lres = 0; CAccessible* pacc = new CAccessible(SAFECAST(_pcmb, IMenuBand*)); if (pacc) { lres = pacc->InitAcc(); if (SUCCEEDED((HRESULT)lres)) { lres = LresultFromObject(IID_IAccessible, wParam, SAFECAST(pacc, IAccessible*)); } pacc->Release(); } return lres; } return 0; } void CMenuToolbarBase::v_Show(BOOL fShow, BOOL fForceUpdate) { // HACKHACK (lamadio): When we create the menubands, we do not set the // TOP level band's fonts until a refresh. This code here fixes it. if (_fFirstTime && _pcmb->_fTopLevel) { SetMenuBandMetrics(_pcmb->_pmbm); } if (fShow) { SetKeyboardCue(); _pcmb->_pmbState->PutTipOnTop(); } else { _fHasDrop = FALSE; KillTimer(_hwndMB, MBTIMER_DRAGPOPDOWN); KillTimer(_hwndMB, MBTIMER_INFOTIP); // Don't show it if we're not displayed :-) _pcmb->_pmbState->HideTooltip(TRUE); } _fSuppressUserMonitor = FALSE; } void CMenuToolbarBase::SetKeyboardCue() { if (_pcmb->_pmbState) { SendMessage(GetParent(_hwndMB), WM_CHANGEUISTATE, MAKEWPARAM(_pcmb->_pmbState->GetKeyboardCue() ? UIS_CLEAR : UIS_SET, UISF_HIDEACCEL), 0); } } HRESULT CMenuToolbarBase::SetMinWidth(int cxMenu) { _cxMinMenu = cxMenu; return S_OK; } HRESULT CMenuToolbarBase::SetNoBorder(BOOL fNoBorder) { _fNoBorder = fNoBorder; return S_OK; } HRESULT CMenuToolbarBase::SetTheme(LPCWSTR pszTheme) { HRESULT hr = S_OK; Str_SetPtr(&_pszTheme, pszTheme); if (_hwndMB) { SendMessage(_hwndMB, TB_SETWINDOWTHEME, 0, (LPARAM)_pszTheme); _RefreshTheme(); } return hr; } void CMenuToolbarBase::_RefreshTheme() { if (_hTheme) { CloseThemeData(_hTheme); } _hTheme = OpenThemeData(_hwndMB, c_wzMenuBandTheme); }