////////////////////////////////////////////////////////////////
// CoolMenu 1997 Microsoft Systems Journal. 
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
#include "StdAfx.h"
#include "CoolMenu.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// if you want to see extra TRACE diagnostics, set below to TRUE
BOOL CCoolMenuManager::bTRACE = FALSE;

#ifdef _DEBUG
#define CMTRACEFN			\
	CTraceFn __fooble;	\
	if (bTRACE)				\
		TRACE
#define CMTRACE			\
	if (bTRACE)				\
		TRACE
#else
#define CMTRACEFN TRACE
#define CMTRACE   TRACE
#endif

// constants used for drawing
const int CXGAP = 1;				// num pixels between button and text
const int CXTEXTMARGIN = 2;		// num pixels after hilite to start text
const int CXBUTTONMARGIN = 2;	// num pixels wider button is than bitmap
const int CYBUTTONMARGIN = 2;	// ditto for height

// DrawText flags
const int DT_MYSTANDARD = DT_SINGLELINE|DT_LEFT|DT_VCENTER;

// identifies owner-draw data as mine
const LONG MYITEMID = MAKELONG(MAKEWORD('m','i'),MAKEWORD('d','0'));

// private struct: one of these for each owner-draw menu item
struct CMyItemData {
	long		magicNum;		// magic number identifying me
	CString	text;				// item text
	UINT		fType;			// original item type flags
	int		iButton;			// index of button image in image list
	CMyItemData()				{ magicNum = MYITEMID; }
	BOOL IsMyItemData()		{ return magicNum == MYITEMID; }
};

IMPLEMENT_DYNAMIC(CCoolMenuManager, CSubclassWnd)

CCoolMenuManager::CCoolMenuManager()
{
	m_szBitmap = m_szButton = CSize(0,0);	// will compute later
	m_bShowButtons = TRUE;						// show buttons by default
	m_bAutoAccel = TRUE;							// auto accelerators by default
	m_hAccel = NULL;								// no accelerators loaded yet
	m_pAccel = NULL;								// no accelerators loaded yet
	m_bUseDrawState = FALSE;					// use DrawEmbossed by default
	m_bDrawDisabledButtonsInColor = FALSE;	// use color for disabled buttons
	FixMFCDotBitmap();
}

CCoolMenuManager::~CCoolMenuManager()
{
	Destroy();
}

//////////////////
// Destroy everything. Called from destructor and Refresh.
//
void CCoolMenuManager::Destroy()
{
	m_ilButtons.DeleteImageList();
	m_mapIDtoImage.RemoveAll();
	m_szBitmap = m_szButton = CSize(0,0);
	m_arToolbarID.RemoveAll();
	m_fontMenu.DeleteObject();
	DestroyAccel();
}

/////////////////
// Destroy accelerators
//
void CCoolMenuManager::DestroyAccel()
{
	m_mapIDtoAccel.RemoveAll();		// delete ACCEL entries in map
	delete m_pAccel;						// delete current accelerators
	m_pAccel = NULL;						// ...
}

//////////////////
// Call this to install the menu manager. Install(NULL) to un-install.
//
void CCoolMenuManager::Install(CFrameWnd* pFrame)
{
	ASSERT_VALID(pFrame);
	m_pFrame = pFrame;
	HookWindow(pFrame);   // install message hook
}

//////////////////
// Load array of toolbar IDs.
//
BOOL CCoolMenuManager::LoadToolbars(const UINT* arID, int n)
{
	ASSERT(arID);
	BOOL bRet = TRUE;
	for (int i=0; i<n; i++)
		bRet |= LoadToolbar(arID[i]);
	return bRet;
}

// structure of RT_TOOLBAR resource
struct TOOLBARDATA {
	WORD wVersion;		// version # should be 1
	WORD wWidth;		// width of one bitmap
	WORD wHeight;		// height of one bitmap
	WORD wItemCount;	// number of items
	WORD items[1];		// array of command IDs, actual size is wItemCount
};

//////////////////
// Load one toolbar. Assumes bg color is gray.
// 
//  * add toolbar bitmap to image list
//	 * add each button ID to button map
//
BOOL CCoolMenuManager::LoadToolbar(UINT nIDToolbar)
{
	// load bitmap
	HBITMAP hbmToolbar = PxLib::LoadSysColorBitmap(nIDToolbar);
	if (!hbmToolbar) {
		TRACE(_T("*** Can't load bitmap for toolbar %d!\n"), nIDToolbar);
		return FALSE;
	}
	CBitmap bmToolbar;
	bmToolbar.Attach(hbmToolbar); // destructor will detach & destroy

	// load toolbar
	LPTSTR lpResName = MAKEINTRESOURCE(nIDToolbar);
	HINSTANCE hInst;
	HRSRC hRsrc;
	TOOLBARDATA* ptbd;
	if ((hInst= AfxFindResourceHandle(lpResName, RT_TOOLBAR)) == NULL ||
		 (hRsrc= FindResource(hInst, lpResName, RT_TOOLBAR))   == NULL ||
		 (ptbd = (TOOLBARDATA*)LoadResource(hInst, hRsrc))     == NULL) {

		TRACE(_T("*** Can't load toolbar %d!\n"), nIDToolbar);
		return FALSE;
	}
	ASSERT(ptbd->wVersion==1);
		
	// OK, I have the bitmap and toolbar. 

	CSize sz(ptbd->wWidth, ptbd->wHeight);
	if (m_szBitmap.cx==0) {
		// First toolbar: initialized bitmap/button sizes and create image list.
		m_szBitmap = sz;
		m_szButton = sz + CSize(CXBUTTONMARGIN<<1, CYBUTTONMARGIN<<1);
		VERIFY(m_ilButtons.Create(sz.cx, sz.cy, ILC_MASK, 0, 10));

	} else if (m_szBitmap != sz) {
		// button sizes different -- oops
		TRACE(_T("*** Toolbar %d button size differs!\n"), nIDToolbar);
		return FALSE;
	}
	
	// I have a good toolbar: now add bitmap to the image list, and each
	// command ID to m_mapIDtoImage array. Note that LoadSysColorBitmap will
	// change gray -> COLOR_3DFACE, so use that for image list background.
	//
	int iNextImage = m_ilButtons.GetImageCount();
	m_ilButtons.Add(&bmToolbar, GetSysColor(COLOR_3DFACE));
	for (int i = 0; i < ptbd->wItemCount; i++) {
		UINT nID = ptbd->items[i];
		if (nID > 0) {
			if (GetButtonIndex(nID) >= 0) {
				TRACE(_T("*** Duplicate button ID %d ignored\n"), nID);
			} else {
				m_mapIDtoImage.SetAt(nID, (void*)iNextImage);
				TRACE("CCoolMenuManager::LoadToolbar(). Added Menu Id %d, Button Number %d\n", nID, iNextImage-1);
			}
			// Andrew Bancroft 13-08-98. Since we've already added the entire toolbar to the imagelist
			// we need to increment nNextImage even if we didn't add this button to
			// m_mapIDtoImage.
			iNextImage++;
		}
	}
	m_arToolbarID.Add(nIDToolbar);  // remember toolbar ID for Refresh
	bmToolbar.Detach();
	return TRUE; // success!
}

//////////////////
// Virtual CSubclassWnd window proc. All messages come here before frame
// window. Isn't it cool? Just like in the old days!
//
LRESULT CCoolMenuManager::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
	switch(msg) {
	case WM_SYSCOLORCHANGE:
	case WM_SETTINGCHANGE:
		Refresh();
		break;

	case WM_MEASUREITEM:
		if (OnMeasureItem((MEASUREITEMSTRUCT*)lp))
			return TRUE; // handled
		break;

	case WM_DRAWITEM:
		if (OnDrawItem((DRAWITEMSTRUCT*)lp))
			return TRUE; // handled
		break;

	case WM_INITMENUPOPUP:
		// Very important: must let frame window handle it first!
		// Because if someone calls CCmdUI::SetText, MFC will change item to
		// MFT_STRING, so I must change back to MFT_OWNERDRAW.
		//
		CSubclassWnd::WindowProc(msg, wp, lp);
		OnInitMenuPopup(CMenu::FromHandle((HMENU)wp),
			(UINT)LOWORD(lp), (BOOL)HIWORD(lp));
		return 0;

	case WM_MENUSELECT:
		OnMenuSelect((UINT)LOWORD(wp), (UINT)HIWORD(wp), (HMENU)lp);
		break;

	case WM_MENUCHAR:
		LRESULT lr = OnMenuChar((TCHAR)LOWORD(wp), (UINT)HIWORD(wp),
			CMenu::FromHandle((HMENU)lp));
		if (lr!=0)
			return lr;
		break;
	}
	return CSubclassWnd::WindowProc(msg, wp, lp);
}

//////////////////
// Refresh all colors, fonts, etc. For WM_SETTINGCHANGE, WM_SYSCOLORCHANGE.
//
void CCoolMenuManager::Refresh()
{
	// first copy list (array) of toolbar IDs now loaded.
	CUIntArray arToolbarID;
	arToolbarID.Copy(m_arToolbarID);

	// destroy everything
	Destroy();

	// re-load toolbars.
	int nToolbars = arToolbarID.GetSize();
	for (int i = 0; i < nToolbars; i++)
		LoadToolbar(arToolbarID[i]);
}

//////////////////
// Get menu font, creating if needed
//
CFont* CCoolMenuManager::GetMenuFont()
{
	if (!(HFONT)m_fontMenu) {
		NONCLIENTMETRICS info;
		info.cbSize = sizeof(info);
		SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0);
		VERIFY(m_fontMenu.CreateFontIndirect(&info.lfMenuFont));
	}
	return &m_fontMenu;
}

//////////////////
// Handle WM_MEASUREITEM on behalf of frame: compute menu item size.
//
BOOL CCoolMenuManager::OnMeasureItem(LPMEASUREITEMSTRUCT lpms)
{
	ASSERT(lpms);
	CMyItemData* pmd = (CMyItemData*)lpms->itemData;
	ASSERT(pmd);
	if (lpms->CtlType != ODT_MENU || !pmd->IsMyItemData())
		return FALSE; // not handled by me

	if (pmd->fType & MFT_SEPARATOR) {
		// separator: use half system height and zero width
		lpms->itemHeight = GetSystemMetrics(SM_CYMENU)>>1;
		lpms->itemWidth  = 0;

	} else {

		// compute size of text: use DrawText with DT_CALCRECT

		CWindowDC dc(NULL);	// screen DC--I won't actually draw on it
		CRect rcText(0,0,0,0);
		CFont* pOldFont = dc.SelectObject(GetMenuFont());
		dc.DrawText(pmd->text, rcText, DT_MYSTANDARD|DT_CALCRECT);
		dc.SelectObject(pOldFont);

		// height of item is just height of a standard menu item
		lpms->itemHeight= max(GetSystemMetrics(SM_CYMENU), rcText.Height());

		// width is width of text plus a bunch of stuff
		int cx = rcText.Width();	// text width 
		cx += CXTEXTMARGIN<<1;		// L/R margin for readability
		cx += CXGAP;					// space between button and menu text
		cx += m_szButton.cx<<1;		// button width (L=button; R=empty margin)

		// whatever value I return in lpms->itemWidth, Windows will add the
		// width of a menu checkmark, so I must subtract to defeat Windows. Argh.
		//
		cx -= GetSystemMetrics(SM_CXMENUCHECK)-1;
		lpms->itemWidth = cx;		// done deal

		CMTRACE(_T("OnMeasureItem for '%s':\tw=%d h=%d\n"), (LPCTSTR)pmd->text,
			lpms->itemWidth, lpms->itemHeight);
	}
	return TRUE; // handled
}

/////////////////
// Handle WM_DRAWITEM on behalf of frame. Note: in all that goes
// below, can't assume rcItem.left=0 because of multi-column menus!
//
BOOL CCoolMenuManager::OnDrawItem(LPDRAWITEMSTRUCT lpds)
{
	ASSERT(lpds);
	
	// Omar L Francisco
	if (lpds->CtlType != ODT_MENU)
		return FALSE;
	
	// Omar L Francisco
	CMyItemData* pmd = (CMyItemData*)lpds->itemData;
	ASSERT(pmd);
	if (!pmd->IsMyItemData())
		return FALSE;
	
	ASSERT(lpds->itemAction != ODA_FOCUS);
	ASSERT(lpds->hDC);
	CDC dc;
	dc.Attach(lpds->hDC);

	const CRect& rcItem = lpds->rcItem;
	if (pmd->fType & MFT_SEPARATOR) {
		// draw separator
		CRect rc = rcItem;								// copy rect
		rc.top += rc.Height()>>1;						// vertical center
		dc.DrawEdge(&rc, EDGE_ETCHED, BF_TOP);		// draw separator line

	} else {													// not a separator

		CMTRACE(_T("OnDrawItem for '%s':\tw=%d h=%d\n"), (LPCTSTR)pmd->text,
			rcItem.Width(), rcItem.Height());

		BOOL bDisabled = lpds->itemState & ODS_GRAYED;
		BOOL bSelected = lpds->itemState & ODS_SELECTED;
		BOOL bChecked  = lpds->itemState & ODS_CHECKED;
		BOOL bHaveButn=FALSE;

		// Paint button, or blank if none
		CRect rcButn(rcItem.TopLeft(), m_szButton);	// button rect
		rcButn += CPoint(0,									// center vertically
			(rcItem.Height() - rcButn.Height())>>1 );

		int iButton = pmd->iButton;
		if (iButton >= 0) {

			// this item has a button!
			bHaveButn = TRUE;

			// compute point to start drawing
			CSize sz = rcButn.Size() - m_szBitmap;
			sz.cx >>= 1;
			sz.cy >>= 1;
			CPoint p(rcButn.TopLeft() + sz);

			// draw disabled or normal
			if (!bDisabled) {
				// normal: fill BG depending on state
				PxLib::FillRect(dc, rcButn, GetSysColor(
					(bChecked && !bSelected) ? COLOR_3DLIGHT : COLOR_MENU));

				// draw pushed-in or popped-out edge
				if (bSelected || bChecked) {
					CRect rc2 = rcButn;
					dc.DrawEdge(rc2, bChecked ? BDR_SUNKENOUTER : BDR_RAISEDINNER,
						BF_RECT);
				}
				// draw the button!
				m_ilButtons.Draw(&dc, iButton, p, ILD_TRANSPARENT);

			} else if (m_bUseDrawState) {
				// use DrawState to draw disabled button: must convert to icon
				HICON hIcon=m_ilButtons.ExtractIcon(iButton);
				ASSERT(hIcon);
				dc.DrawState(p, CSize(0,0), hIcon, DSS_DISABLED, (HBRUSH)NULL);
				DestroyIcon(hIcon);

			} else
				// use DrawEmbossed to draw disabeld button, w/color flag
				PxLib::DrawEmbossed(dc, m_ilButtons, iButton, p,
					m_bDrawDisabledButtonsInColor);

		} else {
			// no button: look for custom checked/unchecked bitmaps
			CMenuItemInfo info;
			info.fMask = MIIM_CHECKMARKS;
			GetMenuItemInfo((HMENU)lpds->hwndItem,
				lpds->itemID, MF_BYCOMMAND, &info);
			if (bChecked || info.hbmpUnchecked) {
				bHaveButn = Draw3DCheckmark(dc, rcButn, bSelected,
					bChecked ? info.hbmpChecked : info.hbmpUnchecked);
			}
		}

		// Done with button, now paint text. First do background if needed.
		int cxButn = m_szButton.cx;				// width of button
		COLORREF colorBG = GetSysColor(bSelected ? COLOR_HIGHLIGHT : COLOR_MENU);
		if (bSelected || lpds->itemAction==ODA_SELECT) {
			// selected or selection state changed: paint text background
			CRect rcBG = rcItem;							// whole rectangle
			if (bHaveButn)									// if there's a button:
				rcBG.left += cxButn + CXGAP;			//  don't paint over it!
			PxLib::FillRect(dc, rcBG, colorBG);	// paint it!
		}

		// compute text rectangle and colors
		CRect rcText = rcItem;				 // start w/whole item
		rcText.left += cxButn + CXGAP + CXTEXTMARGIN; // left margin
		rcText.right -= cxButn;				 // right margin
		dc.SetBkMode(TRANSPARENT);			 // paint transparent text
		COLORREF colorText = GetSysColor(bDisabled ?  COLOR_GRAYTEXT :
			bSelected ? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT);

		// Now paint menu item text.	No need to select font,
		// because windows sets it up before sending WM_DRAWITEM
		//
		if (bDisabled && (!bSelected || colorText == colorBG)) {
			// disabled: draw hilite text shifted southeast 1 pixel for embossed
			// look. Don't do it if item is selected, tho--unless text color same
			// as menu highlight color. Got it?
			//
			DrawMenuText(dc, rcText + CPoint(1,1), pmd->text,
				GetSysColor(COLOR_3DHILIGHT));
		}
		DrawMenuText(dc, rcText, pmd->text, colorText); // finally!
	}
	dc.Detach();

	return TRUE; // handled
}

/////////////////
// Helper function to draw justified menu text. If the text contains a TAB,
// draw everything after the tab right-aligned
//
void CCoolMenuManager::DrawMenuText(CDC& dc, CRect rc, CString text,
	COLORREF color)
{
	CString left = text;
	CString right;
	int iTabPos = left.Find('\t');
	if (iTabPos >= 0) {
		right = left.Right(left.GetLength() - iTabPos - 1);
		left  = left.Left(iTabPos);
	}
	dc.SetTextColor(color);
	dc.DrawText(left, &rc, DT_MYSTANDARD);
	if (iTabPos > 0)
		dc.DrawText(right, &rc, DT_MYSTANDARD|DT_RIGHT);
}

#ifndef OBM_CHECK
#define OBM_CHECK 32760 // from winuser.h
#endif

//////////////////
// Draw 3D checkmark
//
//		dc				device context to draw in
//		rc				rectangle to center bitmap in
//		bSelected	TRUE if button is also selected
//		hbmCheck		Checkmark bitmap to use, or NULL for default
//
BOOL CCoolMenuManager::Draw3DCheckmark(CDC& dc, const CRect& rc,
	BOOL bSelected, HBITMAP hbmCheck)
{
	// get checkmark bitmap if none, use Windows standard
	if (!hbmCheck) {
		CBitmap bm;
		VERIFY(bm.LoadOEMBitmap(OBM_CHECK));
		hbmCheck = (HBITMAP)bm.Detach();
		ASSERT(hbmCheck);
	}
	
	// center bitmap in caller's rectangle
	BITMAP bm;
	::GetObject(hbmCheck, sizeof(bm), &bm);
	int cx = bm.bmWidth;
	int cy = bm.bmHeight;
	CRect rcDest = rc;
	CPoint p(0,0);
	CSize delta(CPoint((rc.Width() - cx)/2, (rc.Height() - cy)/2));
	if (rc.Width() > cx)
		rcDest = CRect(rc.TopLeft() + delta, CSize(cx, cy));
	else
		p -= delta;

	// select checkmark into memory DC
	CDC memdc;
	memdc.CreateCompatibleDC(&dc);
	HBITMAP hOldBM = (HBITMAP)::SelectObject(memdc, hbmCheck);

	// set BG color based on selected state
	COLORREF colorOld =
		dc.SetBkColor(GetSysColor(bSelected ? COLOR_MENU : COLOR_3DLIGHT));
	dc.BitBlt(rcDest.left, rcDest.top, rcDest.Width(), rcDest.Height(),
		&memdc, p.x, p.y, SRCCOPY);
	dc.SetBkColor(colorOld);

	::SelectObject(memdc, hOldBM); // restore

	// draw pushed-in hilight.
	if (rc.Width() > cx)				// if room:
		rcDest.InflateRect(1,1);	// inflate checkmark by one pixel all around
	dc.DrawEdge(&rcDest, BDR_SUNKENOUTER, BF_RECT);

	return TRUE;
}

//////////////////
// Handle WM_INITMENUPOPUP on behalf of frame.
//
void CCoolMenuManager::OnInitMenuPopup(CMenu* pMenu,
	UINT nIndex, BOOL bSysMenu)
{
	if (m_bAutoAccel) {
		// check for new accels. If ASSERT bombs, you forgot 
		//to call Install.
		ASSERT_VALID(m_pFrame);
		HACCEL hAccel = m_pFrame->GetDefaultAccelerator();
		if (hAccel != m_hAccel)
			LoadAccel(hAccel);
	}
	// Wang Jun
	if (!bSysMenu) {   //Check if Click system menu
		ConvertMenu(pMenu, nIndex, bSysMenu, m_bShowButtons);
	}
}

//////////////////
// Set the accelerator table used to generate automatic key
// names in menus. Delete previous table if any.
//
void CCoolMenuManager::LoadAccel(HACCEL hAccel)
{
	DestroyAccel();
	int nAccel;
	if (hAccel && (nAccel = CopyAcceleratorTable(hAccel, NULL, 0)) > 0) {
		m_pAccel = new ACCEL [nAccel];
		ASSERT(m_pAccel);
		CopyAcceleratorTable(hAccel, m_pAccel, nAccel);

		// Now I have the accelerators. Look over list, linking each command
		// ID with its ACCEL structure--i.e., m_mapIDtoAccel[nID] = ACCEL for
		// that ID. If more than one ACCEL for a given command (command has more
		// than one shortcut), fix up so ACCEL.cmd is offset of prev ACCEL
		// 
		for (int i=0; i<nAccel; i++) {
			ACCEL& ac = m_pAccel[i];
			ACCEL* pAccel = GetAccel(ac.cmd);
			m_mapIDtoAccel.SetAt(ac.cmd, &ac);
			ac.cmd = pAccel ? &ac - pAccel : 0; // ac.cmd = offset of prev, or 0
		}
	}
}

//////////////////
// This rather gnarly function is used both to convert the menu from strings to
// owner-draw and vice versa. In either case, it also appends automagic
// accelerator key names to the menu items, if m_bAutoAccel is TRUE.
//
void CCoolMenuManager::ConvertMenu(CMenu* pMenu,
	UINT nIndex, BOOL bSysMenu, BOOL bShowButtons)
{
	ASSERT_VALID(pMenu);

	CString sItemName;

	UINT nItem = pMenu->GetMenuItemCount();
	for (UINT i = 0; i < nItem; i++) {	// loop over each item in menu

		// get menu item info
		char itemname[256];
		CMenuItemInfo info;
		info.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_ID | MIIM_TYPE;
		info.dwTypeData = itemname;
		info.cch = sizeof(itemname);
		::GetMenuItemInfo(*pMenu, i, TRUE, &info);
		CMyItemData* pmd = (CMyItemData*)info.dwItemData;

		if (pmd && !pmd->IsMyItemData()) {
			CMTRACE(_T("CCoolMenuManager: ignoring foreign owner-draw item\n"));
			continue; // owner-draw menu item isn't mine--leave it alone
		}

		if (bSysMenu && info.wID >= 0xF000) {
			CMTRACE(_T("CCoolMenuManager: ignoring sys menu item\n"));
			continue; // don't do for system menu commands
		}
	
		// now that I have the info, I will modify it
		info.fMask = 0;	// assume nothing to change

		if (bShowButtons) {

			// I'm showing buttons: convert to owner-draw

			if (!(info.fType & MFT_OWNERDRAW)) {
				// If not already owner-draw, make it so. NOTE: If app calls
				// pCmdUI->SetText to change the text of a menu item, MFC will
				// turn the item to MFT_STRING. So I must set it back to
				// MFT_OWNERDRAW again. In this case, the menu item data (pmd)
				// will still be there.
				// 
				info.fType |= MFT_OWNERDRAW;
				info.fMask |= MIIM_TYPE;
				if (!pmd) {									// if no item data:
					pmd = new CMyItemData;				//   create one
					ASSERT(pmd);							//   (I hope)
					pmd->fType = info.fType;			//   handy when drawing
					pmd->iButton = GetButtonIndex(info.wID);
					info.dwItemData = (DWORD)pmd;		//   set in menu item data
					info.fMask |= MIIM_DATA;			//   set item data
				}
				pmd->text = info.dwTypeData;			// copy menu item string
			}

			// now add the menu to list of "converted" menus
			HMENU hmenu = pMenu->GetSafeHmenu();
			ASSERT(hmenu);
			if (!m_menuList.Find(hmenu))
				m_menuList.AddHead(hmenu);

			// append accelerators to menu item name
			if (m_pAccel && m_bAutoAccel)
				AppendAccelName(pmd->text, info.wID);

		} else {

			// no buttons -- I'm converting to strings
			
			if (info.fType & MFT_OWNERDRAW) {	// if ownerdraw:
				info.fType &= ~MFT_OWNERDRAW;		//   turn it off
				info.fMask |= MIIM_TYPE;			//   change item type
				ASSERT(pmd);							//   sanity check
				sItemName = pmd->text;				//   save name before deleting pmd
			} else										// otherwise:
				sItemName = info.dwTypeData;		//   use name from MENUITEMINFO

			if (pmd) {
				// NOTE: pmd (item data) could still be left hanging around even
				// if MFT_OWNERDRAW is not set, in case mentioned above where app
				// calls pCmdUI->SetText to set text of item and MFC sets the type
				// to MFT_STRING.
				//
				info.dwItemData = NULL;				// item data is NULL
				info.fMask |= MIIM_DATA;			// change it
				delete pmd;								// and item data too
			}

			// possibly add accelerator name
			if (m_pAccel  && m_bAutoAccel && AppendAccelName(sItemName, info.wID))
				info.fMask |= MIIM_TYPE;			//  change item type (string)
				
			if (info.fMask & MIIM_TYPE) {
				// if setting name, copy name from CString to buffer and set cch
				strncpy(itemname, sItemName, sizeof(itemname));
				info.dwTypeData = itemname;
				info.cch = sItemName.GetLength();
			}
		}

		// if after all the above, there is anything to change, change it
		if (info.fMask) {
			CMTRACE(_T("Converting '%s' to %s\n"), itemname,
				(info.fType & MFT_OWNERDRAW) ? _T("OWNERDRAW") : _T("STRING"));
			SetMenuItemInfo(*pMenu, i, TRUE, &info);
		}
	}
}

//////////////////
// User typed a char into menu. Look for item with & preceeding the char typed.
//
LRESULT CCoolMenuManager::OnMenuChar(UINT nChar, UINT nFlags, CMenu* pMenu)
{
	ASSERT_VALID(pMenu);

	UINT iCurrentItem = (UINT)-1; // guaranteed higher than any command ID
	CUIntArray arItemsMatched;		// items that match the character typed

	UINT nItem = pMenu->GetMenuItemCount();
	for (UINT i=0; i< nItem; i++) {
		// get menu info
		CMenuItemInfo info;
		info.fMask = MIIM_DATA | MIIM_TYPE | MIIM_STATE;
		::GetMenuItemInfo(*pMenu, i, TRUE, &info);

		CMyItemData* pmd = (CMyItemData*)info.dwItemData;
		if ((info.fType & MFT_OWNERDRAW) && pmd && pmd->IsMyItemData()) {
			CString& text = pmd->text;
			int iAmpersand = text.Find('&');
			if (iAmpersand >=0 && toupper(nChar)==toupper(text[iAmpersand+1]))
				arItemsMatched.Add(i);
		}
		if (info.fState & MFS_HILITE)
			iCurrentItem = i; // note index of current item
	}

	// arItemsMatched now contains indexes of items that match the char typed.
	//
	//   * if none: beep
	//   * if one:  execute it
	//   * if more than one: hilite next
	//
	UINT nFound = arItemsMatched.GetSize();
	if (nFound == 0)
		return 0;

	else if (nFound==1)
		return MAKELONG(arItemsMatched[0], MNC_EXECUTE);

	// more than one found--return 1st one past current selected item;
	UINT iSelect = 0;
	for (int i=0; i < (int)nFound; i++) {
		if (arItemsMatched[i] > iCurrentItem) {
			iSelect = i;
			break;
		}
	}
	return MAKELONG(arItemsMatched[iSelect], MNC_SELECT);
}

//////////////////
// Handle WM_MENUSELECT: check for menu closed
//
void CCoolMenuManager::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
{
	if (hSysMenu==NULL && nFlags==0xFFFF) {
		// Windows has closed the menu: restore all menus to original state
		while (!m_menuList.IsEmpty()) {
			ConvertMenu(CMenu::FromHandle((HMENU)m_menuList.RemoveHead()),
				0, FALSE, FALSE);
		}
	}
}

//////////////////
// Append the name of accelerator for given command ID to menu string.
// sItemName is menu item name, which will have the accelerator appended.
// For example, might call with sItemName = "File &Open" and return with
// sItemName = "File &Open\tCtrl-O". Returns BOOL = whether string changed.
//
BOOL CCoolMenuManager::AppendAccelName(CString& sItemName, UINT nID)
{
	int iTabPos = sItemName.Find('\t');
	if (iTabPos > 0)
		sItemName = sItemName.Left(iTabPos);

	BOOL bFound = FALSE;
	for (ACCEL* pa = GetAccel(nID); pa; pa -= pa->cmd) {
		sItemName += bFound ? _T(", ") : _T("\t");
		if (pa->fVirt & FALT)			sItemName += _T("Alt+");
		if (pa->fVirt & FCONTROL)		sItemName += _T("Ctrl+");
		if (pa->fVirt & FSHIFT)			sItemName += _T("Shift+");
		if (pa->fVirt & FVIRTKEY) {
			TCHAR keyname[64];
			UINT vkey = MapVirtualKey(pa->key, 0)<<16;
			GetKeyNameText(vkey, keyname, sizeof(keyname));
			sItemName += keyname;
		} else
			sItemName += (char)pa->key;

		bFound = TRUE;
		if (pa->cmd == 0)
			break;
	}
	return bFound;
}

//////////////////
// This function fixes MFC's diseased dot bitmap used for
// "radio-style" menu items (CCmdUI->SetRadio), which is completely
// wrong if the menu font is large. 
//
void CCoolMenuManager::FixMFCDotBitmap()
{
	HBITMAP hbmDot = GetMFCDotBitmap();
	if (hbmDot) {
		// Draw a centered dot of appropriate size
		BITMAP bm;
		::GetObject(hbmDot, sizeof(bm), &bm);
		CRect rcDot(0,0, bm.bmWidth, bm.bmHeight);
		rcDot.DeflateRect((bm.bmWidth>>1)-2, (bm.bmHeight>>1)-2);

		CWindowDC dcScreen(NULL);
		CDC memdc;
		memdc.CreateCompatibleDC(&dcScreen);
		int nSave = memdc.SaveDC();
		memdc.SelectStockObject(BLACK_PEN);
		memdc.SelectStockObject(BLACK_BRUSH);
		memdc.SelectObject((HGDIOBJ)hbmDot);
		memdc.PatBlt(0, 0, bm.bmWidth, bm.bmHeight, WHITENESS);
		memdc.Ellipse(&rcDot);
		memdc.RestoreDC(nSave);
	}
}

//////////////////
// This function gets MFC's dot bitmap.
//
HBITMAP CCoolMenuManager::GetMFCDotBitmap()
{
	// The bitmap is stored in afxData.hbmMenuDot, but afxData is MFC-private,
	// so the only way to get it is create a menu, set the radio check,
	// and then see what bitmap MFC set in the menu item.
	CMenu menu;
	VERIFY(menu.CreateMenu());
	VERIFY(menu.AppendMenu(MFT_STRING, 0, (LPCTSTR)NULL));
	CCmdUI cui;
	cui.m_pMenu = &menu;
	cui.m_nIndex = 0;
	cui.m_nIndexMax = 1;
	cui.SetRadio(TRUE);
	CMenuItemInfo info;
	info.fMask = MIIM_CHECKMARKS;
	GetMenuItemInfo(menu, 0, MF_BYPOSITION, &info);
	HBITMAP hbmDot = info.hbmpChecked;
	menu.DestroyMenu();
	return hbmDot;
}

// Peter Tewkesbury
BOOL CCoolMenuManager::AddSingleBitmap(UINT nBitmapID, UINT n, UINT *nID)
{ 
	// load bitmap
	HBITMAP hbmBitmap= PxLib::LoadSysColorBitmap(nBitmapID);
	if (!hbmBitmap) 
	{
		TRACE(_T("*** Can't load bitmap %d!\n"), nBitmapID);
		return FALSE;
	}
	
	// Assign Bitmap to CBitmap
	CBitmap bmBitmap;
	bmBitmap.Attach(hbmBitmap); // destructor will detach & destroy
	
	// OK, I have the bitmap - Check that Bitmaps are correct size.
	if (m_szBitmap.cx==0) 
	{
		// First toolbar: initialized bitmap/button sizes and create image list.
		CSize sz(16,15);
		m_szBitmap = sz;
		m_szButton = sz + CSize(CXBUTTONMARGIN<<1, CYBUTTONMARGIN<<1);
		VERIFY(m_ilButtons.Create(sz.cx, sz.cy, ILC_MASK, 0, 10));
		
	} 
	
	// Add Bitmap to ImageList
	int iNextImage = m_ilButtons.GetImageCount();
	m_ilButtons.Add(&bmBitmap, GetSysColor(COLOR_3DFACE)); 
	
	// Add ID to Map.
	for(UINT i=0;i<n;i++)
	{
		if (nID[i] > 0) 
		{
			if (GetButtonIndex(nID[i]) >= 0) 
			{
				TRACE(_T("*** Duplicate button ID %d ignored\n"), nID[i]);
			} 
			else m_mapIDtoImage.SetAt(nID[i], (void*)iNextImage++); 
		}
	}
	
	// All Done.
	return TRUE;
}

////////////////////////////////////////////////////////////////
// Helper functions

//////////////////
// Load a bitmap, converting the standard colors.
// Calls AfxLoadSysColorBitmap to do the work.
//
// RGB(0x00, 0x00, 0x00) (black)      --> COLOR_BTNTEXT
// RGB(0x80, 0x80, 0x80) (dark gray)  --> COLOR_3DSHADOW
// RGB(0xC0, 0xC0, 0xC0) (gray)       --> COLOR_3DFACE
// RGB(0xFF, 0xFF, 0xFF) (white)      --> COLOR_3DHILIGHT
// 
HBITMAP PxLib::LoadSysColorBitmap(LPCTSTR lpResName, BOOL bMono)
{
	HINSTANCE hInst = AfxFindResourceHandle(lpResName, RT_BITMAP);
	HRSRC hRsrc = ::FindResource(hInst, lpResName, RT_BITMAP);
	if (hRsrc == NULL)
		return NULL;
	return AfxLoadSysColorBitmap(hInst, hRsrc, bMono);
}

//////////////////
// Shorthand to fill a rectangle with a solid color.
//
void PxLib::FillRect(CDC& dc, const CRect& rc, COLORREF color)
{
	CBrush brush(color);
	CBrush* pOldBrush = dc.SelectObject(&brush);
	dc.PatBlt(rc.left, rc.top, rc.Width(), rc.Height(), PATCOPY);
	dc.SelectObject(pOldBrush);
}

