CoolMenu.cpp
上传用户:zhanglf88
上传日期:2013-11-19
资源大小:6036k
文件大小:36k
源码类别:

金融证券系统

开发平台:

Visual C++

  1. //-----------------------------------------------------------------------//
  2. // This is a part of the GuiLib MFC Extention.  //
  3. // Modified by  :  Francisco Campos  //
  4. // (C) 2002 Francisco Campos <www.beyondata.com> All rights reserved     //
  5. // This code is provided "as is", with absolutely no warranty expressed  //
  6. // or implied. Any use is at your own risk.  //
  7. // You must obtain the author's consent before you can include this code //
  8. // in a software library.  //
  9. // If the source code in  this file is used in any application  //
  10. // then acknowledgement must be made to the author of this program  //
  11. // fcampos@tutopia.com  //
  12. //-----------------------------------------------------------------------//
  13. ////////////////////////////////////////////////////////////////
  14. // CoolMenu 1997 Microsoft Systems Journal. 
  15. // If this code works, it was written by Paul DiLascia.
  16. // If not, I don't know who wrote it.
  17. //
  18. // ==========================================================================  
  19. // HISTORY:   
  20. // ==========================================================================
  21. // 1.01 13 Aug 1998 - Andrew Bancroft [ABancroft@lgc.com] - Since we've already 
  22. //   added the entire toolbar to the imagelist we need to 
  23. //   increment nNextImage even if we didn't add this button to 
  24. //   m_mapIDtoImage in the LoadToolbar() method.
  25. // 1.01a 13 Aug 1998 - Peter Tewkesbury - Added AddSingleBitmap(...)
  26. //   method for adding a single bitmap to a pulldown
  27. //   menu item.
  28. // 1.02 13 Aug 1998 - Omar L Francisco - Fixed bug with lpds->CtlType
  29. //   and lpds->itemData item checking.
  30. // 1.03 12 Nov 1998 - Fixes debug assert in system menu. - Wang Jun
  31. // 1.04 17 Nov 1998 - Fixes debug assert when you maximize a view - Wang Jun
  32. //   window, then try to use the system menu for the view.
  33. // 1.05 09 Jan 1998 - Seain B. Conover [sc@tarasoft.com] - Fix for virtual 
  34. //   key names.
  35. // 1.06 24 Feb 1999 - Michael Lange [michael.home@topdogg.com] - Fix for memory 
  36. //   leak in CMyItemData structure, added a destructor that 
  37. //   calls text.Empty().
  38. // - Boris Kartamishev [kbv@omegasoftware.com] - Fix for resource
  39. //   ID bug.
  40. // - Jeremy Horgan [jeremyhorgan@hotmail.com] - During 
  41. //   accelerator key processing OnInitMenuPopup() calls 
  42. //   ConvertMenu() which allocates a new CMyItemData for each 
  43. //       menu item. This is memory is normally freed by a call to 
  44. //       OnMenuSelect(), which is not called when processing 
  45. //   accelerator keys. This results in a memory leak. This was
  46. //   fixed by modifying the ~CCoolMenuManager() destructor.
  47. // 1.07 24 Feb 1999 - Koji MATSUNAMI [kmatsu@inse.co.jp] - Fixed problem with 
  48. //   popup menus being drawn correctly as cool menus.
  49. // ==========================================================================
  50. //
  51. /////////////////////////////////////////////////////////////////////////////
  52. #include "StdAfx.h"
  53. #include "CoolMenu.h"
  54. #include "GuiDrawLayer.h"
  55. #include "GuiDocSpecial.h"
  56. #ifdef _DEBUG
  57. #define new DEBUG_NEW
  58. #undef THIS_FILE
  59. static char THIS_FILE[] = __FILE__;
  60. #endif
  61. #define safe_delete(p){if(p){delete p;p=NULL;}}
  62. // helpers
  63. //void PLFillRect(CDC& dc, const CRect& rc, COLORREF color);
  64. void PLSelectRect(CDC& dc, const CRect& rc);
  65. void PLDrawEmbossed(CDC& dc, CImageList& il, int i,
  66. CPoint p, BOOL bColor=FALSE);
  67. HBITMAP PLLoadSysColorBitmap(LPCTSTR lpResName, BOOL bMono=FALSE);
  68. inline HBITMAP PLLoadSysColorBitmap(UINT nResID, BOOL bMono=FALSE) {
  69. return PLLoadSysColorBitmap(MAKEINTRESOURCE(nResID), bMono);
  70. }
  71. // if you want to see extra TRACE diagnostics, set below to TRUE
  72. BOOL CCoolMenuManager::bTRACE = FALSE;
  73. #ifdef _DEBUG
  74. #define CMTRACEFN
  75. CTraceFn __fooble;
  76. if (bTRACE)
  77. TRACE
  78. #define CMTRACE
  79. if (bTRACE)
  80. TRACE
  81. #else
  82. #define CMTRACEFN TRACE
  83. #define CMTRACE   TRACE
  84. #endif
  85. // constants used for drawing
  86. const CXGAP = 1; // num pixels between button and text
  87. const CXTEXTMARGIN = 2; // num pixels after hilite to start text
  88. const CXBUTTONMARGIN = 2; // num pixels wider button is than bitmap
  89. const CYBUTTONMARGIN = 2; // ditto for height
  90. // DrawText flags
  91. const DT_MYSTANDARD = DT_SINGLELINE|DT_LEFT|DT_VCENTER;
  92. // identifies owner-draw data as mine
  93. const LONG MYITEMID = MAKELONG(MAKEWORD(_T('m'),_T('i')),MAKEWORD(_T('d'),_T('0')));
  94. // private struct: one of these for each owner-draw menu item
  95. struct CMyItemData {
  96. long magicNum; // magic number identifying me
  97. CString text; // item text
  98. UINT fType; // original item type flags
  99. int iButton; // index of button image in image list
  100. CMyItemData() { magicNum = MYITEMID; }
  101. ~CMyItemData() { text.Empty(); }
  102. BOOL IsMyItemData() { return magicNum == MYITEMID; }
  103. };
  104. IMPLEMENT_DYNAMIC(CCoolMenuManager, CSubclassWnd)
  105. CCoolMenuManager::CCoolMenuManager()
  106. {
  107. m_szBitmap = m_szButton = CSize(0,0); // will compute later
  108. m_bShowButtons = TRUE; // show buttons by default
  109. m_bAutoAccel = TRUE; // auto accelerators by default
  110. m_hAccel = NULL; // no accelerators loaded yet
  111. m_pAccel = NULL; // no accelerators loaded yet
  112. m_bUseDrawState = FALSE; // use DrawEmbossed by default
  113. m_bDrawDisabledButtonsInColor = FALSE; // use color for disabled buttons
  114. FixMFCDotBitmap();
  115. }
  116. CCoolMenuManager::~CCoolMenuManager()
  117. {
  118. // Jeremy Horgan [jeremyhorgan@hotmail.com]
  119. while (!m_menuList.IsEmpty()) 
  120. {
  121. ConvertMenu(CMenu::FromHandle((HMENU)m_menuList.RemoveHead()),0,     
  122. FALSE, FALSE);
  123. }
  124. Destroy();
  125. }
  126. //////////////////
  127. // Destroy everything. Called from destructor and Refresh.
  128. //
  129. void CCoolMenuManager::Destroy()
  130. {
  131. m_ilButtons.DeleteImageList();
  132. m_mapIDtoImage.RemoveAll();
  133. m_szBitmap = m_szButton = CSize(0,0);
  134. m_arToolbarID.RemoveAll();
  135. m_fontMenu.DeleteObject();
  136. DestroyAccel();
  137. }
  138. /////////////////
  139. // Destroy accelerators
  140. //
  141. void CCoolMenuManager::DestroyAccel()
  142. {
  143. m_mapIDtoAccel.RemoveAll(); // delete ACCEL entries in map
  144. safe_delete(m_pAccel); // delete current accelerators
  145. }
  146. //////////////////
  147. // Call this to install the menu manager. Install(NULL) to un-install.
  148. //
  149. void CCoolMenuManager::Install(CFrameWnd* pFrame)
  150. {
  151. ASSERT_VALID(pFrame);
  152. m_pFrame = pFrame;
  153. HookWindow(pFrame);   // install message hook
  154. }
  155. //////////////////
  156. // Load array of toolbar IDs.
  157. //
  158. BOOL CCoolMenuManager::LoadToolbars(const UINT* arID, int n)
  159. {
  160. ASSERT(arID);
  161. BOOL bRet = TRUE;
  162. for (int i=0; i<n; i++)
  163. bRet |= LoadToolbar(arID[i]);
  164. return bRet;
  165. }
  166. // structure of RT_TOOLBAR resource
  167. struct TOOLBARDATA {
  168. WORD wVersion; // version # should be 1
  169. WORD wWidth; // width of one bitmap
  170. WORD wHeight; // height of one bitmap
  171. WORD wItemCount; // number of items
  172. WORD items[1]; // array of command IDs, actual size is wItemCount
  173. };
  174. //////////////////
  175. // Load one toolbar. Assumes bg color is gray.
  176. // 
  177. //  * add toolbar bitmap to image list
  178. //  * add each button ID to button map
  179. //
  180. BOOL CCoolMenuManager::LoadToolbar(UINT nIDToolbar)
  181. {
  182. // load bitmap
  183. HBITMAP hbmToolbar = PLLoadSysColorBitmap(nIDToolbar);
  184. if (!hbmToolbar) {
  185. TRACE(_T("*** Can't load bitmap for toolbar %d!n"), nIDToolbar);
  186. return FALSE;
  187. }
  188. CBitmap bmToolbar;
  189. bmToolbar.Attach(hbmToolbar); // destructor will detach & destroy
  190. // load toolbar
  191. LPTSTR lpResName = MAKEINTRESOURCE(nIDToolbar);
  192. HINSTANCE hInst;
  193. HRSRC hRsrc;
  194. TOOLBARDATA* ptbd;
  195. if ((hInst= AfxFindResourceHandle(lpResName, RT_TOOLBAR)) == NULL ||
  196.  (hRsrc= FindResource(hInst, lpResName, RT_TOOLBAR))   == NULL ||
  197.  (ptbd = (TOOLBARDATA*)LoadResource(hInst, hRsrc))     == NULL) {
  198. TRACE(_T("*** Can't load toolbar %d!n"), nIDToolbar);
  199. return FALSE;
  200. }
  201. ASSERT(ptbd->wVersion==1);
  202. // OK, I have the bitmap and toolbar. 
  203. CSize sz(ptbd->wWidth, ptbd->wHeight);
  204. if (m_szBitmap.cx==0) {
  205. // First toolbar: initialized bitmap/button sizes and create image list.
  206. m_szBitmap = sz;
  207. m_szButton = sz + CSize(CXBUTTONMARGIN<<1, CYBUTTONMARGIN<<1);
  208. VERIFY(m_ilButtons.Create(sz.cx, sz.cy, ILC_MASK, 0, 10));
  209. } else if (m_szBitmap != sz) {
  210. // button sizes different -- oops
  211. TRACE(_T("*** Toolbar %d button size differs!n"), nIDToolbar);
  212. return FALSE;
  213. }
  214. // I have a good toolbar: now add bitmap to the image list, and each
  215. // command ID to m_mapIDtoImage array. Note that LoadSysColorBitmap will
  216. // change gray -> COLOR_3DFACE, so use that for image list background.
  217. //
  218. int iNextImage = m_ilButtons.GetImageCount();
  219. m_ilButtons.Add(&bmToolbar, GetSysColor(COLOR_3DFACE));
  220. for (int i = 0; i < ptbd->wItemCount; i++) {
  221. UINT nID = ptbd->items[i];
  222. if (nID > 0) {
  223. if (GetButtonIndex(nID) >= 0) {
  224. TRACE(_T("*** Duplicate button ID %d ignoredn"), nID);
  225. } else {
  226. m_mapIDtoImage.SetAt(nID, (void*)iNextImage);
  227. TRACE(_T("CCoolMenuManager::LoadToolbar(). Added Menu Id %d, Button Number %dn"), nID, iNextImage-1);
  228. }
  229. // AB. 13-08-98. Since we've already added the entire toolbar to the imagelist
  230. // we need to increment nNextImage even if we didn't add this button to
  231. // m_mapIDtoImage.
  232. iNextImage++;
  233. }
  234. }
  235. m_arToolbarID.Add(nIDToolbar);  // remember toolbar ID for Refresh
  236. bmToolbar.Detach();
  237. return TRUE; // success!
  238. }
  239. //////////////////
  240. // Virtual CSubclassWnd window proc. All messages come here before frame
  241. // window. Isn't it cool? Just like in the old days!
  242. //
  243. LRESULT CCoolMenuManager::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
  244. {
  245. switch(msg) {
  246. case WM_SYSCOLORCHANGE:
  247. case WM_SETTINGCHANGE:
  248. Refresh();
  249. break;
  250. case WM_MEASUREITEM:
  251. if (OnMeasureItem((MEASUREITEMSTRUCT*)lp))
  252. return TRUE; // handled
  253. break;
  254. case WM_DRAWITEM:
  255. if (OnDrawItem((DRAWITEMSTRUCT*)lp))
  256. return TRUE; // handled
  257. break;
  258. case WM_INITMENUPOPUP:
  259. // Very important: must let frame window handle it first!
  260. // Because if someone calls CCmdUI::SetText, MFC will change item to
  261. // MFT_STRING, so I must change back to MFT_OWNERDRAW.
  262. //
  263. CSubclassWnd::WindowProc(msg, wp, lp);
  264. OnInitMenuPopup(CMenu::FromHandle((HMENU)wp),
  265. (UINT)LOWORD(lp), (BOOL)HIWORD(lp));
  266. return 0;
  267. case WM_MENUSELECT:
  268. OnMenuSelect((UINT)LOWORD(wp), (UINT)HIWORD(wp), (HMENU)lp);
  269. break;
  270. case WM_MENUCHAR:
  271. LRESULT lr = OnMenuChar((TCHAR)LOWORD(wp), (UINT)HIWORD(wp),
  272. CMenu::FromHandle((HMENU)lp));
  273. if (lr!=0)
  274. return lr;
  275. break;
  276. }
  277. return CSubclassWnd::WindowProc(msg, wp, lp);
  278. }
  279. //////////////////
  280. // Refresh all colors, fonts, etc. For WM_SETTINGCHANGE, WM_SYSCOLORCHANGE.
  281. //
  282. void CCoolMenuManager::Refresh()
  283. {
  284. // first copy list (array) of toolbar IDs now loaded.
  285. CUIntArray arToolbarID;
  286. arToolbarID.Copy(m_arToolbarID);
  287. // destroy everything
  288. Destroy();
  289. // re-load toolbars.
  290. int nToolbars = arToolbarID.GetSize();
  291. for (int i = 0; i < nToolbars; i++)
  292. LoadToolbar(arToolbarID[i]);
  293. }
  294. //////////////////
  295. // Get menu font, creating if needed
  296. //
  297. CFont* CCoolMenuManager::GetMenuFont()
  298. {
  299. if (!(HFONT)m_fontMenu) {
  300. NONCLIENTMETRICS info;
  301. info.cbSize = sizeof(info);
  302. SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0);
  303. VERIFY(m_fontMenu.CreateFontIndirect(&info.lfMenuFont));
  304. }
  305. return &m_fontMenu;
  306. }
  307. //////////////////
  308. // Handle WM_MEASUREITEM on behalf of frame: compute menu item size.
  309. //
  310. BOOL CCoolMenuManager::OnMeasureItem(LPMEASUREITEMSTRUCT lpms)
  311. {
  312. ASSERT(lpms);
  313. CMyItemData* pmd = (CMyItemData*)lpms->itemData;
  314. ASSERT(pmd);
  315. if (lpms->CtlType != ODT_MENU || !pmd->IsMyItemData())
  316. return FALSE; // not handled by me
  317. if (pmd->fType & MFT_SEPARATOR) {
  318. // separator: use half system height and zero width
  319. lpms->itemHeight = GetSystemMetrics(SM_CYMENU)>>1;
  320. lpms->itemWidth  = 0;
  321. } else {
  322. // compute size of text: use DrawText with DT_CALCRECT
  323. CWindowDC dc(NULL); // screen DC--I won't actually draw on it
  324. CRect rcText(0,0,0,0);
  325. CFont* pOldFont = dc.SelectObject(GetMenuFont());
  326. dc.DrawText(pmd->text, rcText, DT_MYSTANDARD|DT_CALCRECT);
  327. dc.SelectObject(pOldFont);
  328. // height of item is just height of a standard menu item
  329. lpms->itemHeight= max(GetSystemMetrics(SM_CYMENU), rcText.Height())+4;
  330. // width is width of text plus a bunch of stuff
  331. int cx = rcText.Width()+20; // text width 
  332. cx += CXTEXTMARGIN<<1; // L/R margin for readability
  333. cx += CXGAP; // space between button and menu text
  334. cx += m_szButton.cx<<1; // button width (L=button; R=empty margin)
  335. // whatever value I return in lpms->itemWidth, Windows will add the
  336. // width of a menu checkmark, so I must subtract to defeat Windows. Argh.
  337. //
  338. cx -= GetSystemMetrics(SM_CXMENUCHECK)-1;
  339. lpms->itemWidth = cx; // done deal
  340. CMTRACE(_T("OnMeasureItem for '%s':tw=%d h=%dn"), (LPCTSTR)pmd->text,
  341. lpms->itemWidth, lpms->itemHeight);
  342. }
  343. return TRUE; // handled
  344. }
  345. void CCoolMenuManager::DrawSeparator(CRect& rc, CDC& dc)
  346. {
  347. CBrush brush;
  348. CRect m_rc=rc;
  349. brush.CreateSolidBrush(GuiDrawLayer::GetRGBColorXP());
  350. m_rc.right=m_rc.left+m_szButton.cx;
  351. dc.FillRect(m_rc,&brush);
  352. brush.DeleteObject( );
  353. brush.CreateSolidBrush(RGB(255,255,255));
  354. rc.left+=m_szButton.cx;
  355. dc.FillRect(rc,&brush);
  356. rc.top += rc.Height()>>1;
  357. rc.left+= 5;
  358. rc.right-=5;
  359. dc.DrawEdge(rc, EDGE_ETCHED, BF_TOP);
  360. }
  361. /////////////////
  362. // Handle WM_DRAWITEM on behalf of frame. Note: in all that goes
  363. // below, can't assume rcItem.left=0 because of multi-column menus!
  364. //
  365. BOOL CCoolMenuManager::OnDrawItem(LPDRAWITEMSTRUCT lpds)
  366. {
  367. CRect rc2;
  368. CPoint p;
  369. CRect rcCheck;
  370. CMenuItemInfo info;
  371. CRect rcText;
  372. CSize sz; 
  373. CSize siImg;
  374. CRect rcima;
  375. IMAGEINFO pImageInfo;
  376. ASSERT(lpds);
  377. // Omar L Francisco
  378. if (lpds->CtlType != ODT_MENU)
  379. return FALSE;
  380. // Omar L Francisco
  381. CMyItemData* pmd = (CMyItemData*)lpds->itemData;
  382. ASSERT(pmd);
  383. if (!pmd->IsMyItemData())
  384. return FALSE;
  385. ASSERT(lpds->itemAction != ODA_FOCUS);
  386. ASSERT(lpds->hDC);
  387. CDC dc;
  388. dc.Attach(lpds->hDC);
  389. const CRect& rcItem = lpds->rcItem;
  390. if (pmd->fType & MFT_SEPARATOR) {
  391. // draw separator
  392. CRect rc = rcItem;
  393. DrawSeparator(rc,dc);
  394. } else
  395. { // not a separator
  396. BOOL bDisabled = lpds->itemState & ODS_GRAYED;
  397. BOOL bSelected = lpds->itemState & ODS_SELECTED;
  398. BOOL bChecked  = lpds->itemState & ODS_CHECKED;
  399. BOOL bHaveButn=FALSE;
  400. int cxButn = m_szButton.cx; // width of button
  401. COLORREF colorBG = bSelected ? GuiDrawLayer::GetRGBFondoXP(): GetSysColor(WHITE_PEN) ;
  402. CRect rcBG = rcItem;
  403. if (bSelected || lpds->itemAction==ODA_SELECT)
  404. {
  405. if (bSelected && !bDisabled)
  406. PLSelectRect(dc, rcBG);
  407. else
  408. PLNormalRect(dc, rcBG);
  409. }
  410. else
  411. PLNormalRect(dc, rcBG);
  412. CMTRACE(_T("OnDrawItem for '%s':tw=%d h=%dn"), (LPCTSTR)pmd->text,
  413. rcItem.Width(), rcItem.Height());
  414. // Paint button, or blank if none
  415. CRect rcButn(rcItem.TopLeft(), m_szButton); // button rect
  416. rcButn += CPoint(0, // center vertically
  417. (rcItem.Height() - rcButn.Height())>>1 );
  418. int iButton = pmd->iButton;
  419. if (true) {
  420. // this item has a button!
  421. bHaveButn = TRUE;
  422. // compute point to start drawing
  423. sz = rcButn.Size() - m_szBitmap;
  424. sz.cx >>= 1;
  425. sz.cy >>= 1;
  426. p=CPoint(rcButn.TopLeft() + sz);
  427. // draw disabled or normal
  428. if (!bDisabled) {
  429. // normal: fill BG depending on state
  430. if (bChecked )
  431. {
  432. rcCheck=rcButn;
  433. rcCheck.left+=1;
  434. rcCheck.top+=1;
  435. //rcCheck.DeflateRect(1,1);
  436. PLSelectRect(dc, rcCheck);
  437. }
  438. // draw pushed-in or popped-out edge
  439. if (bSelected || bChecked) {
  440. rc2 = rcButn;
  441. if (bChecked)
  442. {
  443. rc2.DeflateRect(5,5);
  444. GuiDrawLayer::DrawCheck(&dc,rc2);
  445. }
  446. //dc.DrawEdge(rc2, bChecked ? GuiDrawLayer::GetRGBCaptionXP() : GuiDrawLayer::GetRGBCaptionXP(),
  447. // BF_RECT);
  448. }
  449. // draw the button!
  450. if (bSelected)
  451. {
  452. m_ilButtons.GetImageInfo(iButton,&pImageInfo);
  453. rcima =pImageInfo.rcImage;
  454. siImg=CSize(rcima.Width(),rcima.Height());
  455. p.x+=1;
  456. p.y+=1;
  457. dc.DrawState(p,siImg,m_ilButtons.ExtractIcon(iButton),DSS_MONO,CBrush (GuiDrawLayer::GetRGBColorShadow()));
  458. p.x-=2; p.y-=2;
  459. m_ilButtons.Draw(&dc, iButton, p, ILD_TRANSPARENT);
  460. p.x+=1;
  461. p.y+=1;
  462. }
  463. else
  464. m_ilButtons.Draw(&dc, iButton, p, ILD_TRANSPARENT);
  465. } else if (m_bUseDrawState) {
  466. // use DrawState to draw disabled button: must convert to icon
  467. HICON hIcon=m_ilButtons.ExtractIcon(iButton);
  468. ASSERT(hIcon);
  469. dc.DrawState(p, CSize(0,0), hIcon, DSS_DISABLED, (HBRUSH)NULL);
  470. DestroyIcon(hIcon);
  471. } else
  472. // use DrawEmbossed to draw disabeld button, w/color flag
  473. PLDrawEmbossed(dc, m_ilButtons, iButton, p,
  474. m_bDrawDisabledButtonsInColor);
  475. } else {
  476. // no button: look for custom checked/unchecked bitmaps
  477. info.fMask = MIIM_CHECKMARKS;
  478. GetMenuItemInfo((HMENU)lpds->hwndItem,
  479. lpds->itemID, MF_BYCOMMAND, &info);
  480. if (bChecked || info.hbmpUnchecked) {
  481. bHaveButn = Draw3DCheckmark(dc, rcButn, bSelected,
  482. bChecked ? info.hbmpChecked : info.hbmpUnchecked);
  483. }
  484. }
  485. // Done with button, now paint text. First do background if needed.
  486. // compute text rectangle and colors
  487. rcText = rcItem;  // start w/whole item
  488. rcText.left += cxButn + CXGAP + CXTEXTMARGIN; // left margin
  489. rcText.right -= cxButn;  // right margin
  490. dc.SetBkMode(TRANSPARENT);  // paint transparent text
  491. COLORREF colorText = GetSysColor(bDisabled ?  COLOR_GRAYTEXT :
  492. bSelected ? COLOR_MENUTEXT : COLOR_MENUTEXT);
  493. // Now paint menu item text. No need to select font,
  494. // because windows sets it up before sending WM_DRAWITEM
  495. //
  496. if (bDisabled && (!bSelected || colorText == colorBG)) {
  497. // disabled: draw hilite text shifted southeast 1 pixel for embossed
  498. // look. Don't do it if item is selected, tho--unless text color same
  499. // as menu highlight color. Got it?
  500. //
  501. DrawMenuText(dc, rcText + CPoint(1,1), pmd->text,
  502. GetSysColor(COLOR_3DHILIGHT));
  503. }
  504. DrawMenuText(dc, rcText, pmd->text, colorText); // finally!
  505. }
  506. dc.Detach();
  507. return TRUE; // handled
  508. }
  509. /////////////////
  510. // Helper function to draw justified menu text. If the text contains a TAB,
  511. // draw everything after the tab right-aligned
  512. //
  513. void CCoolMenuManager::DrawMenuText(CDC& dc, CRect rc, CString text,
  514. COLORREF color)
  515. {
  516. CString left = text;
  517. CString right;
  518. int iTabPos = left.Find(_T('t'));
  519. if (iTabPos >= 0) {
  520. right = left.Right(left.GetLength() - iTabPos - 1);
  521. left  = left.Left(iTabPos);
  522. }
  523. dc.SetTextColor(color);
  524. dc.DrawText(left, &rc, DT_MYSTANDARD);
  525. if (iTabPos > 0)
  526. dc.DrawText(right, &rc, DT_MYSTANDARD|DT_RIGHT);
  527. }
  528. #ifndef OBM_CHECK
  529. #define OBM_CHECK 32760 // from winuser.h
  530. #endif
  531. //////////////////
  532. // Draw 3D checkmark
  533. //
  534. // dc device context to draw in
  535. // rc rectangle to center bitmap in
  536. // bSelected TRUE if button is also selected
  537. // hbmCheck Checkmark bitmap to use, or NULL for default
  538. //
  539. BOOL CCoolMenuManager::Draw3DCheckmark(CDC& dc, const CRect& rc,
  540. BOOL bSelected, HBITMAP hbmCheck)
  541. {
  542. // get checkmark bitmap if none, use Windows standard
  543. if (!hbmCheck) {
  544. CBitmap bm;
  545. VERIFY(bm.LoadOEMBitmap(OBM_CHECK));
  546. hbmCheck = (HBITMAP)bm.Detach();
  547. ASSERT(hbmCheck);
  548. }
  549. // center bitmap in caller's rectangle
  550. BITMAP bm;
  551. ::GetObject(hbmCheck, sizeof(bm), &bm);
  552. int cx = bm.bmWidth;
  553. int cy = bm.bmHeight;
  554. CRect rcDest = rc;
  555. CPoint p(0,0);
  556. CSize delta(CPoint((rc.Width() - cx)/2, (rc.Height() - cy)/2));
  557. if (rc.Width() > cx)
  558. rcDest = CRect(rc.TopLeft() + delta, CSize(cx, cy));
  559. else
  560. p -= delta;
  561. // select checkmark into memory DC
  562. CDC memdc;
  563. memdc.CreateCompatibleDC(&dc);
  564. HBITMAP hOldBM = (HBITMAP)::SelectObject(memdc, hbmCheck);
  565. // set BG color based on selected state
  566. COLORREF colorOld =
  567. dc.SetBkColor(GetSysColor(bSelected ? COLOR_MENU : COLOR_3DLIGHT));
  568. dc.BitBlt(rcDest.left, rcDest.top, rcDest.Width(), rcDest.Height(),
  569. &memdc, p.x, p.y, SRCCOPY);
  570. dc.SetBkColor(colorOld);
  571. ::SelectObject(memdc, hOldBM); // restore
  572. // draw pushed-in hilight.
  573. if (rc.Width() > cx) // if room:
  574. rcDest.InflateRect(1,1); // inflate checkmark by one pixel all around
  575. dc.DrawEdge(&rcDest, BDR_SUNKENOUTER, BF_RECT);
  576. return TRUE;
  577. }
  578. //////////////////
  579. // Handle WM_INITMENUPOPUP on behalf of frame.
  580. //
  581. void CCoolMenuManager::OnInitMenuPopup(CMenu* pMenu,
  582. UINT nIndex, BOOL bSysMenu)
  583. {
  584. if (m_bAutoAccel)
  585. {
  586. // check for new accels. If ASSERT bombs,
  587. // you forgot to call Install.
  588. ASSERT_VALID(m_pFrame);
  589. HACCEL hAccel = m_pFrame->GetDefaultAccelerator();
  590. if (hAccel != m_hAccel)
  591. LoadAccel(hAccel);
  592. // 12 Nov 1998 - Wang Jun - Fixes debug assert in system menu.
  593. // Check if click system menu.
  594. if (!bSysMenu) {
  595. ConvertMenu(pMenu, nIndex, bSysMenu, m_bShowButtons);
  596. }
  597. }
  598. ConvertMenu(pMenu, nIndex, bSysMenu, m_bShowButtons);
  599. }
  600. //////////////////
  601. // Set the accelerator table used to generate automatic key
  602. // names in menus. Delete previous table if any.
  603. //
  604. void CCoolMenuManager::LoadAccel(HACCEL hAccel)
  605. {
  606. DestroyAccel();
  607. int nAccel;
  608. if (hAccel && (nAccel = CopyAcceleratorTable(hAccel, NULL, 0)) > 0) {
  609. m_pAccel = new ACCEL [nAccel];
  610. ASSERT(m_pAccel);
  611. CopyAcceleratorTable(hAccel, m_pAccel, nAccel);
  612. // Now I have the accelerators. Look over list, linking each command
  613. // ID with its ACCEL structure--i.e., m_mapIDtoAccel[nID] = ACCEL for
  614. // that ID. If more than one ACCEL for a given command (command has more
  615. // than one shortcut), fix up so ACCEL.cmd is offset of prev ACCEL
  616. // 
  617. for (int i=0; i<nAccel; i++) {
  618. ACCEL& ac = m_pAccel[i];
  619. ACCEL* pAccel = GetAccel(ac.cmd);
  620. m_mapIDtoAccel.SetAt(ac.cmd, &ac);
  621. ac.cmd = pAccel ? &ac - pAccel : 0; // ac.cmd = offset of prev, or 0
  622. }
  623. }
  624. }
  625. //////////////////
  626. // This rather gnarly function is used both to convert the menu from strings to
  627. // owner-draw and vice versa. In either case, it also appends automagic
  628. // accelerator key names to the menu items, if m_bAutoAccel is TRUE.
  629. //
  630. void CCoolMenuManager::ConvertMenu(CMenu* pMenu,
  631. UINT nIndex, BOOL bSysMenu, BOOL bShowButtons)
  632. {
  633. ASSERT_VALID(pMenu);
  634. CString sItemName;
  635. UINT nItem = pMenu->GetMenuItemCount();
  636. for (UINT i = 0; i < nItem; i++) {
  637. // loop over each item in menu
  638. // get menu item info
  639. char itemname[256];
  640. CMenuItemInfo info;
  641. info.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_ID
  642. | MIIM_TYPE;
  643. info.dwTypeData = itemname;
  644. info.cch = sizeof(itemname);
  645. ::GetMenuItemInfo(*pMenu, i, TRUE, &info);
  646. CMyItemData* pmd = (CMyItemData*)info.dwItemData;
  647. if (pmd && !pmd->IsMyItemData()) {
  648. CMTRACE(_T("CCoolMenuManager: ignoring foreign owner-draw itemn"));
  649. continue; 
  650. // owner-draw menu item isn't mine--leave it alone
  651. }
  652. // Koji MATSUNAMI 1999.2.23
  653. //
  654. if (bSysMenu && info.wID >= 0xF000) {
  655. CMTRACE(_T("CCoolMenuManager: ignoring sys menu itemn"));
  656. continue; // don't do for system menu commands
  657. }
  658. // now that I have the info, I will modify it
  659. info.fMask = 0; // assume nothing to change
  660. if (bShowButtons) {
  661. // I'm showing buttons: convert to owner-draw
  662. if (!(info.fType & MFT_OWNERDRAW)) {
  663. // If not already owner-draw, make it so. NOTE: If app calls
  664. // pCmdUI->SetText to change the text of a menu item, MFC will
  665. // turn the item to MFT_STRING. So I must set it back to
  666. // MFT_OWNERDRAW again. In this case, the menu item data (pmd)
  667. // will still be there.
  668. // 
  669. info.fType |= MFT_OWNERDRAW;
  670. info.fMask |= MIIM_TYPE;
  671. if (!pmd) { // if no item data:
  672. pmd = new CMyItemData; //   create one
  673. ASSERT(pmd); //   (I hope)
  674. pmd->fType = info.fType; //   handy when drawing
  675. pmd->iButton = GetButtonIndex(info.wID);
  676. info.dwItemData = (DWORD)pmd; //   set in menu item data
  677. info.fMask |= MIIM_DATA; //   set item data
  678. }
  679. pmd->text = info.dwTypeData; // copy menu item string
  680. }
  681. // now add the menu to list of "converted" menus
  682. HMENU hmenu = pMenu->GetSafeHmenu();
  683. ASSERT(hmenu);
  684. if (!m_menuList.Find(hmenu))
  685. m_menuList.AddHead(hmenu);
  686. // append accelerators to menu item name
  687. if (m_pAccel && m_bAutoAccel)
  688. AppendAccelName(pmd->text, info.wID);
  689. } else {
  690. // no buttons -- I'm converting to strings
  691. if (info.fType & MFT_OWNERDRAW) { // if ownerdraw:
  692. info.fType &= ~MFT_OWNERDRAW; //   turn it off
  693. info.fMask |= MIIM_TYPE; //   change item type
  694. ASSERT(pmd); //   sanity check
  695. sItemName = pmd->text; //   save name before deleting pmd
  696. } else // otherwise:
  697. sItemName = info.dwTypeData; //   use name from MENUITEMINFO
  698. if (pmd) {
  699. // NOTE: pmd (item data) could still be left hanging around even
  700. // if MFT_OWNERDRAW is not set, in case mentioned above where app
  701. // calls pCmdUI->SetText to set text of item and MFC sets the type
  702. // to MFT_STRING.
  703. //
  704. info.dwItemData = NULL; // item data is NULL
  705. info.fMask |= MIIM_DATA; // change it
  706. safe_delete(pmd); // and item data too
  707. }
  708. // possibly add accelerator name
  709. if (m_pAccel  && m_bAutoAccel && AppendAccelName(sItemName, info.wID))
  710. info.fMask |= MIIM_TYPE; //  change item type (string)
  711. if (info.fMask & MIIM_TYPE) {
  712. // if setting name, copy name from CString to buffer and set cch
  713. strncpy(itemname, sItemName, sizeof(itemname));
  714. info.dwTypeData = itemname;
  715. info.cch = sItemName.GetLength();
  716. }
  717. }
  718. // if after all the above, there is anything to change, change it
  719. if (info.fMask) {
  720. CMTRACE(_T("Converting '%s' to %sn"), itemname,
  721. (info.fType & MFT_OWNERDRAW) ? _T("OWNERDRAW") : _T("STRING"));
  722. SetMenuItemInfo(*pMenu, i, TRUE, &info);
  723. }
  724. }
  725. }
  726. //////////////////
  727. // User typed a char into menu. Look for item with & preceeding the char typed.
  728. //
  729. LRESULT CCoolMenuManager::OnMenuChar(UINT nChar, UINT nFlags, CMenu* pMenu)
  730. {
  731. ASSERT_VALID(pMenu);
  732. UINT iCurrentItem = (UINT)-1; // guaranteed higher than any command ID
  733. CUIntArray arItemsMatched; // items that match the character typed
  734. UINT nItem = pMenu->GetMenuItemCount();
  735. for (UINT i=0; i< nItem; i++) {
  736. // get menu info
  737. CMenuItemInfo info;
  738. info.fMask = MIIM_DATA | MIIM_TYPE | MIIM_STATE;
  739. ::GetMenuItemInfo(*pMenu, i, TRUE, &info);
  740. CMyItemData* pmd = (CMyItemData*)info.dwItemData;
  741. if ((info.fType & MFT_OWNERDRAW) && pmd && pmd->IsMyItemData()) {
  742. CString& text = pmd->text;
  743. int iAmpersand = text.Find(_T('&'));
  744. if (iAmpersand >=0 && toupper(nChar)==toupper(text[iAmpersand+1]))
  745. arItemsMatched.Add(i);
  746. }
  747. if (info.fState & MFS_HILITE)
  748. iCurrentItem = i; // note index of current item
  749. }
  750. // arItemsMatched now contains indexes of items that match the char typed.
  751. //
  752. //   * if none: beep
  753. //   * if one:  execute it
  754. //   * if more than one: hilite next
  755. //
  756. UINT nFound = arItemsMatched.GetSize();
  757. if (nFound == 0)
  758. return 0;
  759. else if (nFound==1)
  760. return MAKELONG(arItemsMatched[0], MNC_EXECUTE);
  761. // more than one found--return 1st one past current selected item;
  762. UINT iSelect = 0;
  763. for (i=0; i < nFound; i++) {
  764. if (arItemsMatched[i] > iCurrentItem) {
  765. iSelect = i;
  766. break;
  767. }
  768. }
  769. return MAKELONG(arItemsMatched[iSelect], MNC_SELECT);
  770. }
  771. //////////////////
  772. // Handle WM_MENUSELECT: check for menu closed
  773. //
  774. void CCoolMenuManager::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
  775. {
  776. if (hSysMenu==NULL && nFlags==0xFFFF) {
  777. // Windows has closed the menu: restore all menus to original state
  778. while (!m_menuList.IsEmpty()) {
  779. ConvertMenu(CMenu::FromHandle((HMENU)m_menuList.RemoveHead()),
  780. 0, FALSE, FALSE);
  781. }
  782. }
  783. }
  784. // fix for virtual key names - Seain Conover (1999/01/09) 
  785. CString CCoolMenuManager::GetVirtualKeyName( const CString strVirtKey ) const
  786. {
  787. CString strResult;
  788. if ( strVirtKey == _T("Num 7")) { strResult = _T("Home"); }
  789. else if ( strVirtKey == _T("Num 1")) { strResult = _T("End");  }
  790. else if ( strVirtKey == _T("Num 9")) { strResult = _T("PgUp"); }
  791. else if ( strVirtKey == _T("Num 3")) { strResult = _T("PgDn"); }
  792. else if ( strVirtKey == _T("Num 0")) { strResult = _T("Ins");  }
  793. else if ( strVirtKey == _T("Num Del")) { strResult = _T("Del");  }
  794. else
  795. {
  796. strResult = strVirtKey;
  797. }
  798. return strResult;
  799. }
  800. //////////////////
  801. // Append the name of accelerator for given command ID to menu string.
  802. // sItemName is menu item name, which will have the accelerator appended.
  803. // For example, might call with sItemName = "File &Open" and return with
  804. // sItemName = "File &OpentCtrl-O". Returns BOOL = whether string changed.
  805. //
  806. BOOL CCoolMenuManager::AppendAccelName(CString& sItemName, UINT nID)
  807. {
  808. int iTabPos = sItemName.Find(_T('t'));
  809. if (iTabPos > 0)
  810. sItemName = sItemName.Left(iTabPos);
  811. BOOL bFound = FALSE;
  812. for (ACCEL* pa = GetAccel(nID); pa; pa -= pa->cmd) {
  813. sItemName += bFound ? _T(", ") : _T("t");
  814. if (pa->fVirt & FALT) sItemName += _T("Alt+");
  815. if (pa->fVirt & FCONTROL) sItemName += _T("Ctrl+");
  816. if (pa->fVirt & FSHIFT) sItemName += _T("Shift+");
  817. if (pa->fVirt & FVIRTKEY) {
  818. TCHAR keyname[64];
  819. UINT vkey = MapVirtualKey(pa->key, 0)<<16;
  820. GetKeyNameText(vkey, keyname, sizeof(keyname));
  821. // Seain Conover (1999/01/09) 
  822. sItemName += GetVirtualKeyName( keyname );
  823. } else
  824. sItemName += (char)pa->key;
  825. bFound = TRUE;
  826. if (pa->cmd == 0)
  827. break;
  828. }
  829. return bFound;
  830. }
  831. //////////////////
  832. // This function fixes MFC's diseased dot bitmap used for
  833. // "radio-style" menu items (CCmdUI->SetRadio), which is completely
  834. // wrong if the menu font is large. 
  835. //
  836. void CCoolMenuManager::FixMFCDotBitmap()
  837. {
  838. HBITMAP hbmDot = GetMFCDotBitmap();
  839. if (hbmDot) {
  840. // Draw a centered dot of appropriate size
  841. BITMAP bm;
  842. ::GetObject(hbmDot, sizeof(bm), &bm);
  843. CRect rcDot(0,0, bm.bmWidth, bm.bmHeight);
  844. rcDot.DeflateRect((bm.bmWidth>>1)-2, (bm.bmHeight>>1)-2);
  845. CWindowDC dcScreen(NULL);
  846. CDC memdc;
  847. memdc.CreateCompatibleDC(&dcScreen);
  848. int nSave = memdc.SaveDC();
  849. memdc.SelectStockObject(BLACK_PEN);
  850. memdc.SelectStockObject(BLACK_BRUSH);
  851. HBITMAP hbmOld = (HBITMAP)memdc.SelectObject((HGDIOBJ)hbmDot);
  852. memdc.PatBlt(0, 0, bm.bmWidth, bm.bmHeight, WHITENESS);
  853. memdc.Ellipse(&rcDot);
  854. memdc.RestoreDC(nSave);
  855. // Modified By SunZhenyu
  856. memdc.SelectObject(hbmOld);
  857. }
  858. }
  859. //////////////////
  860. // This function gets MFC's dot bitmap.
  861. //
  862. HBITMAP CCoolMenuManager::GetMFCDotBitmap()
  863. {
  864. // The bitmap is stored in afxData.hbmMenuDot, but afxData is MFC-private,
  865. // so the only way to get it is create a menu, set the radio check,
  866. // and then see what bitmap MFC set in the menu item.
  867. CMenu menu;
  868. VERIFY(menu.CreateMenu());
  869. VERIFY(menu.AppendMenu(MFT_STRING, 0, (LPCTSTR)NULL));
  870. CCmdUI cui;
  871. cui.m_pMenu = &menu;
  872. cui.m_nIndex = 0;
  873. cui.m_nIndexMax = 1;
  874. cui.SetRadio(TRUE);
  875. CMenuItemInfo info;
  876. info.fMask = MIIM_CHECKMARKS;
  877. GetMenuItemInfo(menu, 0, MF_BYPOSITION, &info);
  878. HBITMAP hbmDot = info.hbmpChecked;
  879. menu.DestroyMenu();
  880. return hbmDot;
  881. }
  882. // Peter Tewkesbury
  883. BOOL CCoolMenuManager::AddSingleBitmap(UINT nBitmapID, UINT n, UINT *nID)
  884. {
  885. // load bitmap
  886. HBITMAP hbmBitmap = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),
  887. MAKEINTRESOURCE(nBitmapID), IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS);
  888. ASSERT(hbmBitmap);
  889. if (!hbmBitmap) {
  890. TRACE(_T("*** Can't load bitmap %d!n"), nBitmapID);
  891. return FALSE;
  892. }
  893. // Assign Bitmap to CBitmap
  894. CBitmap bmBitmap;
  895. bmBitmap.Attach(hbmBitmap); // destructor will detach & destroy
  896. // OK, I have the bitmap - Check that Bitmaps are correct size.
  897. if (m_szBitmap.cx==0) 
  898. {
  899. // First toolbar: initialized bitmap/button sizes and create image list.
  900. CSize sz(16,15);
  901. m_szBitmap = sz;
  902. m_szButton = sz + CSize(CXBUTTONMARGIN<<1, CYBUTTONMARGIN<<1);
  903. VERIFY(m_ilButtons.Create(sz.cx, sz.cy, ILC_MASK, 0, 10));
  904. // Add Bitmap to ImageList
  905. int iNextImage = m_ilButtons.GetImageCount();
  906. m_ilButtons.Add(&bmBitmap, GetSysColor(COLOR_3DFACE)); 
  907. // Add ID to Map.
  908. for(UINT i=0;i<n;i++)
  909. {
  910. if (nID[i] > 0) 
  911. {
  912. if (GetButtonIndex(nID[i]) >= 0) 
  913. {
  914. TRACE(_T("*** Duplicate button ID %d ignoredn"), nID[i]);
  915. else m_mapIDtoImage.SetAt(nID[i], (void*)iNextImage++); 
  916. }
  917. }
  918. // All Done.
  919. return TRUE;
  920. }
  921. ////////////////////////////////////////////////////////////////
  922. // Helper functions
  923. //////////////////
  924. // Load a bitmap, converting the standard colors.
  925. // Calls AfxLoadSysColorBitmap to do the work.
  926. //
  927. // RGB(0x00, 0x00, 0x00) (black)      --> COLOR_BTNTEXT
  928. // RGB(0x80, 0x80, 0x80) (dark gray)  --> COLOR_3DSHADOW
  929. // RGB(0xC0, 0xC0, 0xC0) (gray)       --> COLOR_3DFACE
  930. // RGB(0xFF, 0xFF, 0xFF) (white)      --> COLOR_3DHILIGHT
  931. // 
  932. HBITMAP PLLoadSysColorBitmap(LPCTSTR lpResName, BOOL bMono)
  933. {
  934. HINSTANCE hInst = AfxFindResourceHandle(lpResName, RT_BITMAP);
  935. HRSRC hRsrc = ::FindResource(hInst, lpResName, RT_BITMAP);
  936. if (hRsrc == NULL)
  937. return NULL;
  938. return AfxLoadSysColorBitmap(hInst, hRsrc, bMono);
  939. }
  940. //////////////////
  941. // Shorthand to fill a rectangle with a solid color.
  942. //
  943. void PLSelectRect(CDC& dc, const CRect& rc)
  944. {
  945. CBrush brush;
  946. brush.CreateSolidBrush(GuiDrawLayer::GetRGBFondoXP());
  947. dc.FillRect(rc,&brush);
  948. dc.Draw3dRect(rc,GuiDrawLayer::GetRGBCaptionXP(),GuiDrawLayer::GetRGBCaptionXP()); 
  949. }
  950. void CCoolMenuManager::PLNormalRect(CDC& dc, const CRect& rc)
  951. {
  952. CRect m_rc=rc;
  953. CBrush brush;
  954. brush.CreateSolidBrush(GuiDrawLayer::GetRGBColorXP());
  955. m_rc.right=m_rc.left+m_szButton.cx;
  956. dc.FillRect(m_rc,&brush);
  957. brush.DeleteObject( );
  958. m_rc=rc;
  959. m_rc.left+=m_szButton.cx;
  960. brush.CreateSolidBrush(RGB(255,255,255));
  961. dc.FillRect(m_rc,&brush);
  962. }
  963. // This is the magic ROP code used to generate the embossed look for
  964. // a disabled button. It's listed in Appendix F of the Win32 Programmer's
  965. // Reference as PSDPxax (!) which is a cryptic reverse-polish notation for
  966. //
  967. // ((Destination XOR Pattern) AND Source) XOR Pattern
  968. //
  969. // which I leave to you to figure out. In the case where I apply it,
  970. // Source is a monochrome bitmap which I want to draw in such a way that
  971. // the black pixels get transformed to the brush color and the white pixels
  972. // draw transparently--i.e. leave the Destination alone.
  973. //
  974. // black ==> Pattern (brush)
  975. // white ==> Destintation (ie, transparent)
  976. //
  977. // 0xb8074a is the ROP code that does this. For more info, see Charles
  978. // Petzold, _Programming Windows_, 2nd Edition, p 622-624.
  979. //
  980. #define TRANSPARENTROP 0xb8074a
  981. //////////////////
  982. // Draw an image with the embossed (disabled) look.
  983. //
  984. // dc device context to draw in
  985. // il image list containing image
  986. // i index of image to draw
  987. // p point in dc to draw image at
  988. //    bColor do color embossing. Default is B/W.
  989. //
  990. void PLDrawEmbossed(CDC& dc, CImageList& il, int i,
  991. CPoint p, BOOL bColor)
  992. {
  993. IMAGEINFO info;
  994. VERIFY(il.GetImageInfo(0, &info));
  995. CRect rc = info.rcImage;
  996. int cx = rc.Width();
  997. int cy = rc.Height();
  998. // create memory dc
  999. CDC memdc;
  1000. memdc.CreateCompatibleDC(&dc);
  1001. // create mono or color bitmap
  1002. CBitmap bm;
  1003. if (bColor)
  1004. bm.CreateCompatibleBitmap(&dc, cx, cy);
  1005. else
  1006. bm.CreateBitmap(cx, cy, 1, 1, NULL);
  1007. // draw image into memory DC--fill BG white first
  1008. CBitmap* pOldBitmap = memdc.SelectObject(&bm);
  1009. memdc.PatBlt(0, 0, cx, cy, WHITENESS);
  1010. il.Draw(&memdc, i, CPoint(0,0), ILD_TRANSPARENT);
  1011. // This seems to be required. Why, I don't know. ???
  1012. COLORREF colorOldBG = dc.SetBkColor(RGB(255,255,255)); // white
  1013. // Draw using hilite offset by (1,1), then shadow
  1014. CBrush brShadow(GetSysColor(COLOR_3DSHADOW));
  1015. CBrush brHilite(GetSysColor(COLOR_3DHIGHLIGHT));
  1016. CBrush* pOldBrush = dc.SelectObject(&brHilite);
  1017. dc.BitBlt(p.x+1, p.y+1, cx, cy, &memdc, 0, 0, TRANSPARENTROP);
  1018. dc.SelectObject(&brShadow);
  1019. dc.BitBlt(p.x, p.y, cx, cy, &memdc, 0, 0, TRANSPARENTROP);
  1020. dc.SelectObject(pOldBrush);
  1021. dc.SetBkColor(colorOldBG);  // restore
  1022. memdc.SelectObject(pOldBitmap);  // ...
  1023. }