Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1072 lines
32 KiB

  1. #include "shellprv.h"
  2. #include "fsmenu.h"
  3. #include "ids.h"
  4. #include <limits.h>
  5. #include "filetbl.h"
  6. #include <oleacc.h> // MSAAMENUINFO stuff
  7. #define CXIMAGEGAP 6
  8. typedef enum
  9. {
  10. FMII_DEFAULT = 0x0000,
  11. FMII_BREAK = 0x0001
  12. } FMIIFLAGS;
  13. #define FMI_MARKER 0x00000001
  14. #define FMI_EXPAND 0x00000004
  15. #define FMI_EMPTY 0x00000008
  16. #define FMI_ON_MENU 0x00000040
  17. // One of these per file menu.
  18. typedef struct
  19. {
  20. HMENU hmenu; // Menu.
  21. HDPA hdpa; // List of items (see below).
  22. const struct _FILEMENUITEM *pfmiLastSel;
  23. UINT idCmd; // Command.
  24. UINT grfFlags; // enum filter
  25. DWORD dwMask; // FMC_ flags
  26. PFNFMCALLBACK pfnCallback; // Callback function.
  27. LPARAM lParam; // Parameter passed for callback handler
  28. int cyMenuSizeSinceLastBreak; // Size of menu (cy)
  29. } FILEMENUHEADER;
  30. // One of these for each file menu item.
  31. //
  32. // !!! Note: the testers have a test utility which grabs
  33. // the first 7 fields of this structure. If you change
  34. // the order or meaning of these fields, make sure they
  35. // are notified so they can update their automated tests.
  36. //
  37. typedef struct _FILEMENUITEM
  38. {
  39. MSAAMENUINFO msaa; // accessibility must be first.
  40. FILEMENUHEADER *pfmh; // The header.
  41. IShellFolder *psf; // Shell Folder.
  42. LPITEMIDLIST pidl; // IDlist for item.
  43. int iImage; // Image index to use.
  44. DWORD dwFlags; // Misc flags above.
  45. DWORD dwAttributes; // GetAttributesOf(), SFGAO_ bits (only some)
  46. LPTSTR psz; // Text when not using pidls.
  47. LPARAM lParam; // Application data
  48. } FILEMENUITEM;
  49. #if defined(DEBUG)
  50. BOOL IsValidPFILEMENUHEADER(FILEMENUHEADER *pfmh)
  51. {
  52. return (IS_VALID_WRITE_PTR(pfmh, FILEMENUHEADER) &&
  53. IS_VALID_HANDLE(pfmh->hmenu, MENU) &&
  54. IS_VALID_HANDLE(pfmh->hdpa, DPA));
  55. }
  56. BOOL IsValidPFILEMENUITEM(FILEMENUITEM *pfmi)
  57. {
  58. return (IS_VALID_WRITE_PTR(pfmi, FILEMENUITEM) &&
  59. IS_VALID_STRUCT_PTR(pfmi->pfmh, FILEMENUHEADER) &&
  60. (NULL == pfmi->pidl || IS_VALID_PIDL(pfmi->pidl)) &&
  61. (NULL == pfmi->psz || IS_VALID_STRING_PTR(pfmi->psz, -1)));
  62. }
  63. #endif
  64. DWORD GetItemTextExtent(HDC hdc, LPCTSTR lpsz)
  65. {
  66. SIZE sz;
  67. GetTextExtentPoint(hdc, lpsz, lstrlen(lpsz), &sz);
  68. // NB This is OK as long as an item's extend doesn't get very big.
  69. return MAKELONG((WORD)sz.cx, (WORD)sz.cy);
  70. }
  71. void FileMenuItem_GetDisplayName(FILEMENUITEM *pfmi, LPTSTR pszName, UINT cchName)
  72. {
  73. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  74. // Is this a special empty item?
  75. if (pfmi->dwFlags & FMI_EMPTY)
  76. {
  77. // Yep, load the string from a resource.
  78. LoadString(HINST_THISDLL, IDS_NONE, pszName, cchName);
  79. }
  80. else
  81. {
  82. *pszName = 0;
  83. // If it's got a pidl use that, else just use the normal menu string.
  84. if (pfmi->psz)
  85. {
  86. lstrcpyn(pszName, pfmi->psz, cchName);
  87. }
  88. else if (pfmi->pidl && pfmi->psf && SUCCEEDED(DisplayNameOf(pfmi->psf, pfmi->pidl, SHGDN_NORMAL, pszName, cchName)))
  89. {
  90. pfmi->psz = StrDup(pszName);
  91. }
  92. }
  93. }
  94. // Create a menu item structure to be stored in the hdpa
  95. BOOL FileMenuItem_Create(FILEMENUHEADER *pfmh, IShellFolder *psf, LPCITEMIDLIST pidl, DWORD dwFlags, FILEMENUITEM **ppfmi)
  96. {
  97. FILEMENUITEM *pfmi = (FILEMENUITEM *)LocalAlloc(LPTR, sizeof(*pfmi));
  98. if (pfmi)
  99. {
  100. pfmi->pfmh = pfmh;
  101. pfmi->iImage = -1;
  102. pfmi->dwFlags = dwFlags;
  103. pfmi->pidl = pidl ? ILClone(pidl) : NULL;
  104. pfmi->psf = psf;
  105. if (pfmi->psf)
  106. pfmi->psf->AddRef();
  107. if (pfmi->psf && pfmi->pidl)
  108. {
  109. pfmi->dwAttributes = SFGAO_FOLDER;
  110. pfmi->psf->GetAttributesOf(1, &pidl, &pfmi->dwAttributes);
  111. }
  112. // fill in msaa stuff
  113. pfmi->msaa.dwMSAASignature = MSAA_MENU_SIG;
  114. // prep the pfmi->psz cached displayname
  115. WCHAR sz[MAX_PATH];
  116. FileMenuItem_GetDisplayName(pfmi, sz, ARRAYSIZE(sz));
  117. // just use the same string ref, so we dont dupe the allocation.
  118. pfmi->msaa.pszWText = pfmi->psz;
  119. pfmi->msaa.cchWText = pfmi->msaa.pszWText ? lstrlenW(pfmi->msaa.pszWText) : 0;
  120. }
  121. *ppfmi = pfmi;
  122. return (NULL != pfmi);
  123. }
  124. BOOL FileMenuItem_Destroy(FILEMENUITEM *pfmi)
  125. {
  126. BOOL fRet = FALSE;
  127. if (pfmi)
  128. {
  129. ILFree(pfmi->pidl);
  130. LocalFree(pfmi->psz);
  131. ATOMICRELEASE(pfmi->psf);
  132. LocalFree(pfmi);
  133. fRet = TRUE;
  134. }
  135. return fRet;
  136. }
  137. // Enumerates the folder and adds the files to the DPA.
  138. // Returns: count of items in the list
  139. int FileList_Build(FILEMENUHEADER *pfmh, IShellFolder *psf, int cItems)
  140. {
  141. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  142. if (pfmh->hdpa)
  143. {
  144. // special case the single empty item, and remove it.
  145. // this is because we expect to get called multiple times in FileList_Build on a single menu.
  146. if ((1 == cItems) && (1 == DPA_GetPtrCount(pfmh->hdpa)))
  147. {
  148. FILEMENUITEM *pfmiEmpty = (FILEMENUITEM*)DPA_GetPtr(pfmh->hdpa, 0);
  149. if (pfmiEmpty->dwFlags & FMI_EMPTY)
  150. {
  151. DeleteMenu(pfmh->hmenu, 0, MF_BYPOSITION);
  152. FileMenuItem_Destroy(pfmiEmpty);
  153. DPA_DeletePtr(pfmh->hdpa, 0);
  154. cItems = 0;
  155. }
  156. }
  157. // We now need to iterate over the children under this guy...
  158. IEnumIDList *penum;
  159. if (S_OK == psf->EnumObjects(NULL, pfmh->grfFlags, &penum))
  160. {
  161. LPITEMIDLIST pidl;
  162. while (S_OK == penum->Next(1, &pidl, NULL))
  163. {
  164. FILEMENUITEM *pfmi;
  165. if (FileMenuItem_Create(pfmh, psf, pidl, 0, &pfmi))
  166. {
  167. int idpa = DPA_AppendPtr(pfmh->hdpa, pfmi);
  168. ASSERTMSG(idpa != -1, "DPA_AppendPtr failed when adding file menu item");
  169. if (idpa != -1)
  170. {
  171. // if the caller returns S_FALSE then we will remove the item from the
  172. // menu, otherwise we behave as before.
  173. if (pfmh->pfnCallback(FMM_ADD, pfmh->lParam, psf, pidl) == S_FALSE)
  174. {
  175. FileMenuItem_Destroy(pfmi);
  176. DPA_DeletePtr(pfmh->hdpa, idpa);
  177. }
  178. else
  179. {
  180. cItems++;
  181. }
  182. }
  183. }
  184. ILFree(pidl);
  185. }
  186. penum->Release();
  187. }
  188. }
  189. // Insert a special Empty item
  190. if (!cItems && pfmh->hdpa)
  191. {
  192. FILEMENUITEM *pfmi;
  193. if (FileMenuItem_Create(pfmh, NULL, NULL, FMI_EMPTY, &pfmi))
  194. {
  195. DPA_SetPtr(pfmh->hdpa, cItems, pfmi);
  196. cItems++;
  197. }
  198. }
  199. return cItems;
  200. }
  201. // Use the text extent of the given item and the size of the image to work
  202. // what the full extent of the item will be.
  203. DWORD GetItemExtent(HDC hdc, FILEMENUITEM *pfmi)
  204. {
  205. TCHAR szName[MAX_PATH];
  206. szName[0] = 0;
  207. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  208. FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
  209. FILEMENUHEADER *pfmh = pfmi->pfmh;
  210. ASSERT(pfmh);
  211. DWORD dwExtent = GetItemTextExtent(hdc, szName);
  212. UINT uHeight = HIWORD(dwExtent);
  213. // If no custom height - calc it.
  214. uHeight = max(uHeight, ((WORD)g_cySmIcon)) + 6;
  215. ASSERT(pfmi->pfmh);
  216. // string, image, gap on either side of image, popup triangle
  217. // and background bitmap if there is one.
  218. // FEATURE: popup triangle size needs to be real
  219. UINT uWidth = LOWORD(dwExtent) + GetSystemMetrics(SM_CXMENUCHECK);
  220. // Space for image if there is one.
  221. // NB We currently always allow room for the image even if there
  222. // isn't one so that imageless items line up properly.
  223. uWidth += g_cxSmIcon + (2 * CXIMAGEGAP);
  224. return MAKELONG(uWidth, uHeight);
  225. }
  226. // Get the FILEMENUITEM *of this menu item
  227. FILEMENUITEM *FileMenu_GetItemData(HMENU hmenu, UINT iItem, BOOL bByPos)
  228. {
  229. MENUITEMINFO mii = {0};
  230. mii.cbSize = sizeof(MENUITEMINFO);
  231. mii.fMask = MIIM_DATA | MIIM_STATE;
  232. return GetMenuItemInfo(hmenu, iItem, bByPos, &mii) ? (FILEMENUITEM *)mii.dwItemData : NULL;
  233. }
  234. FILEMENUHEADER *FileMenu_GetHeader(HMENU hmenu)
  235. {
  236. FILEMENUITEM *pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  237. if (pfmi &&
  238. EVAL(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)) &&
  239. EVAL(IS_VALID_STRUCT_PTR(pfmi->pfmh, FILEMENUHEADER)))
  240. {
  241. return pfmi->pfmh;
  242. }
  243. return NULL;
  244. }
  245. // Create a file menu header. This header is to be associated
  246. // with the given menu handle.
  247. // If the menu handle already has header, simply return the
  248. // existing header.
  249. FILEMENUHEADER *FileMenuHeader_Create(HMENU hmenu, const FMCOMPOSE *pfmc)
  250. {
  251. FILEMENUHEADER *pfmh;
  252. FILEMENUITEM *pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  253. // Does this guy already have a header?
  254. if (pfmi)
  255. {
  256. // Yes; use it
  257. pfmh = pfmi->pfmh;
  258. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  259. }
  260. else
  261. {
  262. // Nope, create one now.
  263. pfmh = (FILEMENUHEADER *)LocalAlloc(LPTR, sizeof(*pfmh));
  264. if (pfmh)
  265. {
  266. pfmh->hdpa = DPA_Create(0);
  267. if (pfmh->hdpa == NULL)
  268. {
  269. LocalFree((HLOCAL)pfmh);
  270. pfmh = NULL;
  271. }
  272. else
  273. {
  274. pfmh->hmenu = hmenu;
  275. }
  276. }
  277. }
  278. if (pfmc && pfmh)
  279. {
  280. pfmh->idCmd = pfmc->idCmd;
  281. pfmh->grfFlags = pfmc->grfFlags;
  282. pfmh->dwMask = pfmc->dwMask;
  283. pfmh->pfnCallback = pfmc->pfnCallback;
  284. pfmh->lParam = pfmc->lParam;
  285. }
  286. return pfmh;
  287. }
  288. BOOL FileMenuHeader_InsertMarkerItem(FILEMENUHEADER *pfmh, IShellFolder *psf);
  289. // This functions adds the given item (index into DPA) into the actual menu.
  290. BOOL FileMenuHeader_InsertItem(FILEMENUHEADER *pfmh, UINT iItem, FMIIFLAGS fFlags)
  291. {
  292. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  293. // Normal item.
  294. FILEMENUITEM *pfmi = (FILEMENUITEM *)DPA_GetPtr(pfmh->hdpa, iItem);
  295. if (!pfmi || (pfmi->dwFlags & FMI_ON_MENU))
  296. return FALSE;
  297. pfmi->dwFlags |= FMI_ON_MENU;
  298. // The normal stuff.
  299. UINT fMenu = MF_BYPOSITION | MF_OWNERDRAW;
  300. // Keep track of where it's going in the menu.
  301. // The special stuff...
  302. if (fFlags & FMII_BREAK)
  303. {
  304. fMenu |= MF_MENUBARBREAK;
  305. }
  306. // Is it a folder (that's not open yet)?
  307. if ((pfmi->dwAttributes & SFGAO_FOLDER) && !(pfmh->dwMask & FMC_NOEXPAND))
  308. {
  309. // Yep. Create a submenu item.
  310. HMENU hmenuSub = CreatePopupMenu();
  311. if (hmenuSub)
  312. {
  313. FMCOMPOSE fmc = {0};
  314. // Set the callback now so it can be called when adding items
  315. fmc.lParam = pfmh->lParam;
  316. fmc.pfnCallback = pfmh->pfnCallback;
  317. fmc.dwMask = pfmh->dwMask;
  318. fmc.idCmd = pfmh->idCmd;
  319. fmc.grfFlags = pfmh->grfFlags;
  320. // Insert it into the parent menu.
  321. InsertMenu(pfmh->hmenu, iItem, fMenu | MF_POPUP, (UINT_PTR)hmenuSub, (LPTSTR)pfmi);
  322. // Set it's ID.
  323. MENUITEMINFO mii = {0};
  324. mii.cbSize = sizeof(mii);
  325. mii.fMask = MIIM_ID;
  326. mii.wID = pfmh->idCmd;
  327. SetMenuItemInfo(pfmh->hmenu, iItem, TRUE, &mii);
  328. IShellFolder *psf;
  329. if (SUCCEEDED(pfmi->psf->BindToObject(pfmi->pidl, NULL, IID_PPV_ARG(IShellFolder, &psf))))
  330. {
  331. FILEMENUHEADER *pfmhSub = FileMenuHeader_Create(hmenuSub, &fmc);
  332. if (pfmhSub)
  333. {
  334. // Build it a bit at a time.
  335. FileMenuHeader_InsertMarkerItem(pfmhSub, psf);
  336. }
  337. psf->Release();
  338. }
  339. }
  340. }
  341. else
  342. {
  343. // Nope.
  344. if (pfmi->dwFlags & FMI_EMPTY)
  345. fMenu |= MF_DISABLED | MF_GRAYED;
  346. InsertMenu(pfmh->hmenu, iItem, fMenu, pfmh->idCmd, (LPTSTR)pfmi);
  347. }
  348. return TRUE;
  349. }
  350. // Give the submenu a marker item so we can check it's a filemenu item
  351. // at initpopupmenu time.
  352. BOOL FileMenuHeader_InsertMarkerItem(FILEMENUHEADER *pfmh, IShellFolder *psf)
  353. {
  354. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  355. FILEMENUITEM *pfmi;
  356. if (FileMenuItem_Create(pfmh, psf, NULL, FMI_MARKER | FMI_EXPAND, &pfmi))
  357. {
  358. DPA_SetPtr(pfmh->hdpa, 0, pfmi);
  359. FileMenuHeader_InsertItem(pfmh, 0, FMII_DEFAULT);
  360. return TRUE;
  361. }
  362. return FALSE;
  363. }
  364. // Enumerates the DPA and adds each item into the
  365. // menu. Inserts vertical breaks if the menu becomes too long.
  366. // Returns: count of items added to menu
  367. int FileList_AddToMenu(FILEMENUHEADER *pfmh)
  368. {
  369. int cItemMac = 0;
  370. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  371. if (pfmh->hdpa)
  372. {
  373. int cyItem = 0;
  374. int cyMenu = pfmh->cyMenuSizeSinceLastBreak;
  375. int cyMenuMax = GetSystemMetrics(SM_CYSCREEN);
  376. // Get the rough height of an item so we can work out when to break the
  377. // menu. User should really do this for us but that would be useful.
  378. HDC hdc = GetDC(NULL);
  379. if (hdc)
  380. {
  381. NONCLIENTMETRICS ncm;
  382. ncm.cbSize = sizeof(ncm);
  383. if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE))
  384. {
  385. HFONT hfont = CreateFontIndirect(&ncm.lfMenuFont);
  386. if (hfont)
  387. {
  388. HFONT hfontOld = SelectFont(hdc, hfont);
  389. cyItem = HIWORD(GetItemExtent(hdc, (FILEMENUITEM *)DPA_GetPtr(pfmh->hdpa, 0)));
  390. SelectObject(hdc, hfontOld);
  391. DeleteObject(hfont);
  392. }
  393. }
  394. ReleaseDC(NULL, hdc);
  395. }
  396. UINT cItems = DPA_GetPtrCount(pfmh->hdpa);
  397. for (UINT i = 0; i < cItems; i++)
  398. {
  399. // Keep a rough count of the height of the menu.
  400. cyMenu += cyItem;
  401. if (cyMenu > cyMenuMax)
  402. {
  403. // Add a vertical break?
  404. FileMenuHeader_InsertItem(pfmh, i, FMII_BREAK);
  405. cyMenu = cyItem;
  406. }
  407. else
  408. {
  409. FileMenuHeader_InsertItem(pfmh, i, FMII_DEFAULT);
  410. cItemMac++;
  411. }
  412. }
  413. // Save the current cy size so we can use this again
  414. // if more items are appended to this menu.
  415. pfmh->cyMenuSizeSinceLastBreak = cyMenu;
  416. }
  417. return cItemMac;
  418. }
  419. BOOL FileList_AddImages(FILEMENUHEADER *pfmh)
  420. {
  421. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  422. int cItems = DPA_GetPtrCount(pfmh->hdpa);
  423. for (int i = 0; i < cItems; i++)
  424. {
  425. FILEMENUITEM *pfmi = (FILEMENUITEM *)DPA_GetPtr(pfmh->hdpa, i);
  426. if (pfmi && pfmi->pidl && (pfmi->iImage == -1))
  427. {
  428. pfmi->iImage = SHMapPIDLToSystemImageListIndex(pfmi->psf, pfmi->pidl, NULL);
  429. }
  430. }
  431. return TRUE;
  432. }
  433. // We create subemnu's with one marker item so we can check it's a file menu
  434. // at init popup time but we need to delete it before adding new items.
  435. BOOL FileMenuHeader_DeleteMarkerItem(FILEMENUHEADER *pfmh, IShellFolder **ppsf)
  436. {
  437. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  438. if (GetMenuItemCount(pfmh->hmenu) == 1)
  439. {
  440. if (GetMenuItemID(pfmh->hmenu, 0) == pfmh->idCmd)
  441. {
  442. FILEMENUITEM *pfmi = FileMenu_GetItemData(pfmh->hmenu, 0, TRUE);
  443. if (pfmi && (pfmi->dwFlags & FMI_MARKER))
  444. {
  445. // Delete it.
  446. ASSERT(pfmh->hdpa);
  447. ASSERT(DPA_GetPtrCount(pfmh->hdpa) == 1);
  448. if (ppsf)
  449. {
  450. *ppsf = pfmi->psf; // transfer the ref
  451. pfmi->psf = NULL;
  452. }
  453. ASSERT(NULL == pfmi->psf);
  454. // NB The marker shouldn't have a pidl.
  455. ASSERT(NULL == pfmi->pidl);
  456. LocalFree((HLOCAL)pfmi);
  457. DPA_DeletePtr(pfmh->hdpa, 0);
  458. DeleteMenu(pfmh->hmenu, 0, MF_BYPOSITION);
  459. // Cleanup OK.
  460. return TRUE;
  461. }
  462. }
  463. }
  464. return FALSE;
  465. }
  466. // Add files to a file menu header. This function goes thru
  467. // the following steps:
  468. // - enumerates the folder and fills the hdpa list with items
  469. // (files and subfolders)
  470. // - sorts the list
  471. // - gets the images for the items in the list
  472. // - adds the items from list into actual menu
  473. // The last step also (optionally) caps the length of the
  474. // menu to the specified height. Ideally, this should
  475. // happen at the enumeration time, except the required sort
  476. // prevents this from happening. So we end up adding a
  477. // bunch of items to the list and then removing them if
  478. // there are too many.
  479. // returns: count of items added
  480. HRESULT FileMenuHeader_AddFiles(FILEMENUHEADER *pfmh, IShellFolder *psf, int iPos, int *pcItems)
  481. {
  482. HRESULT hr;
  483. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  484. int cItems = FileList_Build(pfmh, psf, iPos);
  485. // If the build was aborted cleanup and early out.
  486. *pcItems = cItems;
  487. if (cItems != 0)
  488. {
  489. // Add the images *after* adding to the menu, since the menu
  490. // may be capped to a maximum height, and we can then prevent
  491. // adding images we won't need.
  492. *pcItems = FileList_AddToMenu(pfmh);
  493. FileList_AddImages(pfmh);
  494. }
  495. hr = (*pcItems < cItems) ? S_FALSE : S_OK;
  496. TraceMsg(TF_MENU, "FileMenuHeader_AddFiles: Added %d filemenu items.", cItems);
  497. return hr;
  498. }
  499. // Add files to this menu.
  500. // Returns: number of items added
  501. HRESULT FileMenu_AddFiles(HMENU hmenu, UINT iPos, FMCOMPOSE *pfmc)
  502. {
  503. HRESULT hr = E_OUTOFMEMORY;
  504. BOOL fMarker = FALSE;
  505. // (FileMenuHeader_Create might return an existing header)
  506. FILEMENUHEADER *pfmh = FileMenuHeader_Create(hmenu, pfmc);
  507. if (pfmh)
  508. {
  509. FILEMENUITEM *pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  510. if (pfmi)
  511. {
  512. // Clean up marker item if there is one.
  513. if ((FMI_MARKER | FMI_EXPAND) == (pfmi->dwFlags & (FMI_MARKER | FMI_EXPAND)))
  514. {
  515. // Nope, do it now.
  516. FileMenuHeader_DeleteMarkerItem(pfmh, NULL);
  517. fMarker = TRUE;
  518. if (iPos)
  519. iPos--;
  520. }
  521. }
  522. hr = FileMenuHeader_AddFiles(pfmh, pfmc->psf, iPos, &pfmc->cItems);
  523. if ((0 == pfmc->cItems) && fMarker)
  524. {
  525. // Aborted or no items. Put the marker back (if there used
  526. // to be one).
  527. FileMenuHeader_InsertMarkerItem(pfmh, NULL);
  528. }
  529. }
  530. return hr;
  531. }
  532. // creator of the filemenu has to explicitly call to free
  533. // up FileMenu items because USER doesn't send WM_DELETEITEM for ownerdraw
  534. // menu. Great eh?
  535. // Returns the number of items deleted.
  536. void FileMenu_DeleteAllItems(HMENU hmenu)
  537. {
  538. FILEMENUHEADER *pfmh = FileMenu_GetHeader(hmenu);
  539. if (pfmh)
  540. {
  541. // Clean up the items.
  542. UINT cItems = DPA_GetPtrCount(pfmh->hdpa);
  543. // backwards stop things dont move as we delete
  544. for (int i = cItems - 1; i >= 0; i--)
  545. {
  546. FILEMENUITEM *pfmi = (FILEMENUITEM *)DPA_GetPtr(pfmh->hdpa, i);
  547. if (pfmi)
  548. {
  549. HMENU hmenuSub = GetSubMenu(pfmh->hmenu, i); // cascade item?
  550. if (hmenuSub)
  551. {
  552. // Yep. Get the submenu for this item, Delete all items.
  553. FileMenu_DeleteAllItems(hmenuSub);
  554. }
  555. // Delete the item itself.
  556. DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION);
  557. FileMenuItem_Destroy(pfmi);
  558. DPA_DeletePtr(pfmh->hdpa, i);
  559. }
  560. }
  561. // Clean up the header.
  562. DPA_Destroy(pfmh->hdpa);
  563. LocalFree((HLOCAL)pfmh);
  564. }
  565. }
  566. STDAPI FileMenu_Compose(HMENU hmenu, UINT nMethod, FMCOMPOSE *pfmc)
  567. {
  568. HRESULT hr = E_INVALIDARG;
  569. switch (nMethod)
  570. {
  571. case FMCM_INSERT:
  572. hr = FileMenu_AddFiles(hmenu, 0, pfmc);
  573. break;
  574. case FMCM_APPEND:
  575. hr = FileMenu_AddFiles(hmenu, GetMenuItemCount(hmenu), pfmc);
  576. break;
  577. case FMCM_REPLACE:
  578. FileMenu_DeleteAllItems(hmenu);
  579. hr = FileMenu_AddFiles(hmenu, 0, pfmc);
  580. break;
  581. }
  582. return hr;
  583. }
  584. LPITEMIDLIST FileMenuItem_FullIDList(const FILEMENUITEM *pfmi)
  585. {
  586. LPITEMIDLIST pidlFolder, pidl = NULL;
  587. if (SUCCEEDED(SHGetIDListFromUnk(pfmi->psf, &pidlFolder)))
  588. {
  589. pidl = ILCombine(pidlFolder, pfmi->pidl);
  590. ILFree(pidlFolder);
  591. }
  592. return pidl;
  593. }
  594. void FileMenuItem_SetItem(const FILEMENUITEM *pfmi, BOOL bClear)
  595. {
  596. if (bClear)
  597. {
  598. pfmi->pfmh->pfmiLastSel = NULL;
  599. pfmi->pfmh->pfnCallback(FMM_SETLASTPIDL, pfmi->pfmh->lParam, NULL, NULL);
  600. }
  601. else
  602. {
  603. pfmi->pfmh->pfmiLastSel = pfmi;
  604. LPITEMIDLIST pidl = FileMenuItem_FullIDList(pfmi);
  605. if (pidl)
  606. {
  607. pfmi->pfmh->pfnCallback(FMM_SETLASTPIDL, pfmi->pfmh->lParam, NULL, pidl);
  608. ILFree(pidl);
  609. }
  610. }
  611. }
  612. LRESULT FileMenu_DrawItem(HWND hwnd, DRAWITEMSTRUCT *pdi)
  613. {
  614. BOOL fFlatMenu = FALSE;
  615. BOOL fFrameRect = FALSE;
  616. SystemParametersInfo(SPI_GETFLATMENU, 0, (void *)&fFlatMenu, 0);
  617. if ((pdi->itemAction & ODA_SELECT) || (pdi->itemAction & ODA_DRAWENTIRE))
  618. {
  619. HBRUSH hbrOld = NULL;
  620. FILEMENUITEM *pfmi = (FILEMENUITEM *)pdi->itemData;
  621. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  622. if (!pfmi)
  623. {
  624. TraceMsg(TF_ERROR, "FileMenu_DrawItem: Filemenu is invalid (no item data).");
  625. return FALSE;
  626. }
  627. FILEMENUHEADER *pfmh = pfmi->pfmh;
  628. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  629. // Adjust for large/small icons.
  630. int cxIcon = g_cxSmIcon;
  631. int cyIcon = g_cxSmIcon;
  632. // Is the menu just starting to get drawn?
  633. if (pdi->itemAction & ODA_DRAWENTIRE)
  634. {
  635. if (pfmi == DPA_GetPtr(pfmh->hdpa, 0))
  636. {
  637. // Yes; reset the last selection item
  638. FileMenuItem_SetItem(pfmi, TRUE);
  639. }
  640. }
  641. if (pdi->itemState & ODS_SELECTED)
  642. {
  643. // Determine the selection colors
  644. //
  645. // Normal menu colors apply until we are in edit mode, in which
  646. // case the menu item is drawn unselected and an insertion caret
  647. // is drawn above or below the current item. The exception is
  648. // if the item is a cascaded menu item, then we draw it
  649. // normally, but also show the insertion caret. (We do this
  650. // because Office does this, and also, USER draws the arrow
  651. // in the selected color always, so it looks kind of funny
  652. // if we don't select the menu item.)
  653. //
  654. if (fFlatMenu)
  655. {
  656. SetBkColor(pdi->hDC, GetSysColor(COLOR_MENUHILIGHT));
  657. hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUHILIGHT));
  658. fFrameRect = TRUE;
  659. }
  660. else
  661. {
  662. // No
  663. SetBkColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHT));
  664. SetTextColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
  665. hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_HIGHLIGHTTEXT));
  666. }
  667. // inform callback of last item
  668. FileMenuItem_SetItem(pfmi, FALSE);
  669. }
  670. else
  671. {
  672. // dwRop = SRCAND;
  673. hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUTEXT));
  674. }
  675. // Initial start pos.
  676. int x = pdi->rcItem.left + CXIMAGEGAP;
  677. // Get the name.
  678. TCHAR szName[MAX_PATH];
  679. FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
  680. // NB Keep a plain copy of the name for testing and accessibility.
  681. if (!pfmi->psz)
  682. pfmi->psz = StrDup(szName);
  683. DWORD dwExtent = GetItemTextExtent(pdi->hDC, szName);
  684. int y = (pdi->rcItem.bottom+pdi->rcItem.top - HIWORD(dwExtent)) / 2;
  685. // Shrink the selection rect for small icons a bit.
  686. pdi->rcItem.top += 1;
  687. pdi->rcItem.bottom -= 1;
  688. // Draw the text.
  689. int fDSFlags;
  690. if ((pfmi->dwFlags & FMI_ON_MENU) == 0)
  691. {
  692. // Norton Desktop Navigator 95 replaces the Start->&Run
  693. // menu item with a &Run pidl. Even though the text is
  694. // from a pidl, we still want to format the "&R" correctly.
  695. fDSFlags = DST_PREFIXTEXT;
  696. }
  697. else
  698. {
  699. // All other strings coming from pidls are displayed
  700. // as is to preserve any & in their display name.
  701. fDSFlags = DST_TEXT;
  702. }
  703. if (pfmi->dwFlags & FMI_EMPTY)
  704. {
  705. if (pdi->itemState & ODS_SELECTED)
  706. {
  707. if (GetSysColor(COLOR_GRAYTEXT) == GetSysColor(COLOR_HIGHLIGHTTEXT))
  708. {
  709. fDSFlags |= DSS_UNION;
  710. }
  711. else
  712. {
  713. SetTextColor(pdi->hDC, GetSysColor(COLOR_GRAYTEXT));
  714. }
  715. }
  716. else
  717. {
  718. fDSFlags |= DSS_DISABLED;
  719. }
  720. ExtTextOut(pdi->hDC, 0, 0, ETO_OPAQUE, &pdi->rcItem, NULL, 0, NULL);
  721. DrawState(pdi->hDC, NULL, NULL, (LONG_PTR)szName, lstrlen(szName), x + cxIcon + CXIMAGEGAP, y, 0, 0, fDSFlags);
  722. }
  723. else
  724. {
  725. ExtTextOut(pdi->hDC, x + cxIcon + CXIMAGEGAP, y, ETO_OPAQUE, &pdi->rcItem, NULL, 0, NULL);
  726. DrawState(pdi->hDC, NULL, NULL, (LONG_PTR)szName, lstrlen(szName), x + cxIcon + CXIMAGEGAP, y, 0, 0, fDSFlags);
  727. }
  728. if (fFrameRect)
  729. {
  730. HBRUSH hbrFill = (HBRUSH)GetSysColorBrush(COLOR_HIGHLIGHT);
  731. HBRUSH hbrSave = (HBRUSH)SelectObject(pdi->hDC, hbrFill);
  732. int x = pdi->rcItem.left;
  733. int y = pdi->rcItem.top;
  734. int cx = pdi->rcItem.right - x - 1;
  735. int cy = pdi->rcItem.bottom - y - 1;
  736. PatBlt(pdi->hDC, x, y, 1, cy, PATCOPY);
  737. PatBlt(pdi->hDC, x + 1, y, cx, 1, PATCOPY);
  738. PatBlt(pdi->hDC, x, y + cy, cx, 1, PATCOPY);
  739. PatBlt(pdi->hDC, x + cx, y + 1, 1, cy, PATCOPY);
  740. SelectObject(pdi->hDC, hbrSave);
  741. }
  742. // Get the image if it needs it,
  743. if ((pfmi->iImage == -1) && pfmi->pidl && pfmi->psf)
  744. {
  745. pfmi->iImage = SHMapPIDLToSystemImageListIndex(pfmi->psf, pfmi->pidl, NULL);
  746. }
  747. // Draw the image (if there is one).
  748. if (pfmi->iImage != -1)
  749. {
  750. // Try to center image.
  751. y = (pdi->rcItem.bottom + pdi->rcItem.top - cyIcon) / 2;
  752. HIMAGELIST himl;
  753. Shell_GetImageLists(NULL, &himl);
  754. ImageList_DrawEx(himl, pfmi->iImage, pdi->hDC, x, y, 0, 0,
  755. GetBkColor(pdi->hDC), CLR_NONE, ILD_NORMAL);
  756. }
  757. if (hbrOld)
  758. SelectObject(pdi->hDC, hbrOld);
  759. }
  760. return TRUE;
  761. }
  762. DWORD FileMenuItem_GetExtent(FILEMENUITEM *pfmi)
  763. {
  764. DWORD dwExtent = 0;
  765. if (pfmi)
  766. {
  767. FILEMENUHEADER *pfmh = pfmi->pfmh;
  768. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  769. HDC hdcMem = CreateCompatibleDC(NULL);
  770. if (hdcMem)
  771. {
  772. // Get the rough height of an item so we can work out when to break the
  773. // menu. User should really do this for us but that would be useful.
  774. NONCLIENTMETRICS ncm = {0};
  775. ncm.cbSize = sizeof(ncm);
  776. if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE))
  777. {
  778. HFONT hfont = CreateFontIndirect(&ncm.lfMenuFont);
  779. if (hfont)
  780. {
  781. HFONT hfontOld = SelectFont(hdcMem, hfont);
  782. dwExtent = GetItemExtent(hdcMem, pfmi);
  783. SelectFont(hdcMem, hfontOld);
  784. DeleteObject(hfont);
  785. }
  786. }
  787. DeleteDC(hdcMem);
  788. }
  789. }
  790. return dwExtent;
  791. }
  792. LRESULT FileMenu_MeasureItem(HWND hwnd, MEASUREITEMSTRUCT *pmi)
  793. {
  794. DWORD dwExtent = FileMenuItem_GetExtent((FILEMENUITEM *)pmi->itemData);
  795. pmi->itemHeight = HIWORD(dwExtent);
  796. pmi->itemWidth = LOWORD(dwExtent);
  797. return TRUE;
  798. }
  799. // Fills the given filemenu with contents of the appropriate folder
  800. //
  801. // Returns: S_OK if all the files were added
  802. // error on something bad
  803. STDAPI FileMenu_InitMenuPopup(HMENU hmenu)
  804. {
  805. HRESULT hr = E_FAIL;
  806. FILEMENUITEM *pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  807. if (pfmi)
  808. {
  809. FILEMENUHEADER *pfmh = pfmi->pfmh;
  810. if (pfmh)
  811. {
  812. hr = S_OK;
  813. // Have we already filled this thing out?
  814. if ((FMI_MARKER | FMI_EXPAND) == (pfmi->dwFlags & (FMI_MARKER | FMI_EXPAND)))
  815. {
  816. // No, do it now. Get the previously init'ed header.
  817. IShellFolder *psf;
  818. if (FileMenuHeader_DeleteMarkerItem(pfmh, &psf))
  819. {
  820. // Fill it full of stuff.
  821. int cItems;
  822. hr = FileMenuHeader_AddFiles(pfmh, psf, 0, &cItems);
  823. psf->Release();
  824. }
  825. }
  826. }
  827. }
  828. return hr;
  829. }
  830. int FileMenuHeader_LastSelIndex(FILEMENUHEADER *pfmh)
  831. {
  832. for (int i = GetMenuItemCount(pfmh->hmenu) - 1; i >= 0; i--)
  833. {
  834. FILEMENUITEM *pfmi = FileMenu_GetItemData(pfmh->hmenu, i, TRUE);
  835. if (pfmi && (pfmi == pfmh->pfmiLastSel))
  836. return i;
  837. }
  838. return -1;
  839. }
  840. // If the string contains &ch or begins with ch then return TRUE.
  841. BOOL _MenuCharMatch(LPCTSTR lpsz, TCHAR ch, BOOL fIgnoreAmpersand)
  842. {
  843. // Find the first ampersand.
  844. LPTSTR pchAS = StrChr(lpsz, TEXT('&'));
  845. if (pchAS && !fIgnoreAmpersand)
  846. {
  847. // Yep, is the next char the one we want.
  848. if (CharUpperChar(*CharNext(pchAS)) == CharUpperChar(ch))
  849. {
  850. // Yep.
  851. return TRUE;
  852. }
  853. }
  854. else if (CharUpperChar(*lpsz) == CharUpperChar(ch))
  855. {
  856. return TRUE;
  857. }
  858. return FALSE;
  859. }
  860. STDAPI_(LRESULT) FileMenu_HandleMenuChar(HMENU hmenu, TCHAR ch)
  861. {
  862. FILEMENUITEM *pfmi;
  863. TCHAR szName[MAX_PATH];
  864. int iFoundOne = -1;
  865. UINT iStep = 0;
  866. UINT iItem = 0;
  867. UINT cItems = GetMenuItemCount(hmenu);
  868. // Start from the last place we looked from.
  869. FILEMENUHEADER *pfmh = FileMenu_GetHeader(hmenu);
  870. if (pfmh)
  871. {
  872. iItem = FileMenuHeader_LastSelIndex(pfmh) + 1;
  873. if (iItem >= cItems)
  874. iItem = 0;
  875. }
  876. while (iStep < cItems)
  877. {
  878. pfmi = FileMenu_GetItemData(hmenu, iItem, TRUE);
  879. if (pfmi)
  880. {
  881. FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
  882. if (_MenuCharMatch(szName, ch, pfmi->pidl ? TRUE : FALSE))
  883. {
  884. // Found (another) match.
  885. if (iFoundOne != -1)
  886. {
  887. // More than one, select the first.
  888. return MAKELRESULT(iFoundOne, MNC_SELECT);
  889. }
  890. else
  891. {
  892. // Found at least one.
  893. iFoundOne = iItem;
  894. }
  895. }
  896. }
  897. iItem++;
  898. iStep++;
  899. // Wrap.
  900. if (iItem >= cItems)
  901. iItem = 0;
  902. }
  903. // Did we find one?
  904. if (iFoundOne != -1)
  905. {
  906. // Just in case the user types ahead without the selection being drawn.
  907. pfmi = FileMenu_GetItemData(hmenu, iFoundOne, TRUE);
  908. FileMenuItem_SetItem(pfmi, FALSE);
  909. return MAKELRESULT(iFoundOne, MNC_EXECUTE);
  910. }
  911. else
  912. {
  913. // Didn't find it.
  914. return MAKELRESULT(0, MNC_IGNORE);
  915. }
  916. }