|
|
#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 <uxtheme.h>
#include <tmschema.h>
#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(!hwnd || IsWindow(hwnd)); int nRet = -1; // Punt on failure
TBBUTTON tbb; if (hwnd && ToolBar_GetButton(hwnd, iPos, &tbb)) { nRet = tbb.idCommand; } return nRet; }
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]; // SMC_CHEVRONGETTIP handler assumes 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; } 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; }
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) && !GetSystemMetrics(SM_REMOTECONTROL))) ? 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)) { int cch = SendMessage(hwnd, TB_GETBUTTONTEXT, idCmd, NULL); if (cch > 0 && cch < ARRAYSIZE(sz)) { if (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 == (DWORD)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); }
|