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.

5014 lines
136 KiB

  1. //---------------------------------------------------------------------------
  2. // Helper routines for an owner draw menu showing the contents of a directory.
  3. //---------------------------------------------------------------------------
  4. // FEATURE (scotth): note this file is #included by SHELL32 and SHDOC401.
  5. // We really want the bits in one place, but right now
  6. // SHDOC401 needs some changes which SHELL32 on win95
  7. // does not provide.
  8. //
  9. // The second best solution is to place this code in
  10. // a static lib (stocklib). However, shell32's default
  11. // data segment is still shared, and since this file
  12. // contains some globals, we'd have problems with that.
  13. // If shell32 is fixed, we can add this file to stocklib.
  14. //
  15. // Our third best solution is to #include this file.
  16. // That's better than maintaining two different source
  17. // codes.
  18. //
  19. #include <limits.h>
  20. #ifdef IN_SHDOCVW
  21. extern "C" LPITEMIDLIST IEILCreate(UINT cbSize);
  22. #define _ILCreate IEILCreate
  23. #endif
  24. STDAPI_(LPITEMIDLIST) SafeILClone(LPCITEMIDLIST pidl);
  25. #define ILClone SafeILClone
  26. #define CXIMAGEGAP 6
  27. // #define SRCSTENCIL 0x00B8074AL
  28. typedef enum
  29. {
  30. FMII_DEFAULT = 0x0000,
  31. FMII_BREAK = 0x0001
  32. } FMIIFLAGS;
  33. #define FMI_NULL 0x00000000
  34. #define FMI_MARKER 0x00000001
  35. #define FMI_FOLDER 0x00000002
  36. #define FMI_EXPAND 0x00000004
  37. #define FMI_EMPTY 0x00000008
  38. #define FMI_SEPARATOR 0x00000010
  39. #define FMI_DISABLED 0x00000020 // Enablingly Challenged ???
  40. #define FMI_ON_MENU 0x00000040
  41. #define FMI_IGNORE_PIDL 0x00000080 // Ignore the pidl as the display string
  42. #define FMI_FILESYSTEM 0x00000100
  43. #define FMI_MARGIN 0x00000200
  44. #define FMI_MAXTIPWIDTH 0x00000400
  45. #define FMI_TABSTOP 0x00000800
  46. #define FMI_DRAWFLAGS 0x00001000
  47. #define FMI_ALTITEM 0x00002000 // Item came from alternate pidl
  48. #define FMI_CXMAX 0x00004000
  49. #define FMI_CYMAX 0x00008000
  50. #define FMI_CYSPACING 0x00010000
  51. #define FMI_ASKEDFORTOOLTIP 0x00020000
  52. #define FMI_USESTRING 0x00040000 // Use psz before pidl as display string
  53. // One of these per file menu.
  54. typedef struct
  55. {
  56. IShellFolder * psf; // Shell Folder.
  57. IStream * pstm; // Optional stream
  58. HMENU hmenu; // Menu.
  59. LPITEMIDLIST pidlFolder; // Pidl for the folder.
  60. HDPA hdpa; // List of items (see below).
  61. UINT idCmd; // Command.
  62. DWORD fmf; // Header flags.
  63. UINT fFSFilter; // file system enum filter
  64. HBITMAP hbmp; // Background bitmap.
  65. UINT cxBmp; // Width of bitmap.
  66. UINT cyBmp; // Height of bitmap.
  67. UINT cxBmpGap; // Gap for bitmap.
  68. UINT yBmp; // Cached Y coord.
  69. COLORREF clrBkg; // Background color.
  70. UINT cySel; // Prefered height of selection.
  71. PFNFMCALLBACK pfncb; // Callback function.
  72. LPARAM lParam; // Parameter passed for callback handler
  73. IShellFolder * psfAlt; // Alternate Shell Folder.
  74. LPITEMIDLIST pidlAltFolder; // Pidl for the alternate folder.
  75. HDPA hdpaAlt; // Alternate dpa
  76. int cyMenuSizeSinceLastBreak; // Size of menu (cy)
  77. UINT cyMax; // Max allowable height of entire menu in pixels
  78. UINT cxMax; // Max allowable width in pixels
  79. UINT cySpacing; // Spacing b/t menu items in pixels
  80. LPTSTR pszFilterTypes; // Multi-string list of extensions (e.g., "doc\0xls\0")
  81. } FILEMENUHEADER, *PFILEMENUHEADER;
  82. // One of these for each file menu item.
  83. //
  84. // !!! Note: the testers have a test utility which grabs
  85. // the first 7 fields of this structure. If you change
  86. // the order or meaning of these fields, make sure they
  87. // are notified so they can update their automated tests.
  88. //
  89. typedef struct
  90. {
  91. PFILEMENUHEADER pfmh; // The header.
  92. int iImage; // Image index to use.
  93. DWORD Flags; // Misc flags above.
  94. LPITEMIDLIST pidl; // IDlist for item.
  95. LPTSTR psz; // Text when not using pidls.
  96. UINT cyItem; // Custom height.
  97. LPTSTR pszTooltip; // Item tooltip.
  98. RECT rcMargin; // Margin around tooltip
  99. DWORD dwMaxTipWidth; // Maximum tooltip width
  100. DWORD dwTabstop;
  101. UINT uDrawFlags;
  102. LPARAM lParam; // Application data
  103. int nOrder; // Ordinal indicating user preference
  104. DWORD dwEffect; // Acceptable drop effects
  105. } FILEMENUITEM, *PFILEMENUITEM;
  106. #define X_TIPOFFSET 130 // an arbitrary number of pixels
  107. class CFSMenuAgent
  108. {
  109. private:
  110. DWORD _dwState; // MAS_*
  111. HHOOK _hhookMsg;
  112. PFILEMENUITEM _pfmiCur; // Current item selected
  113. PFILEMENUITEM _pfmiDrag; // Item being dragged
  114. PFILEMENUITEM _pfmiDrop; // Target of drop
  115. DWORD _dwStateSav; // Saved state once menu goes away
  116. HWND _hwndMenu;
  117. HDC _hdc;
  118. RECT _rcCur; // rect of current selection
  119. RECT _rcCurScr; // rect of current selection in screen coords
  120. RECT _rcMenu; // rect of whole menu in screen coords
  121. int _yCenter; // center of item (in screen coords)
  122. HCURSOR _hcurSav;
  123. HBRUSH _hbr;
  124. public:
  125. CFSMenuAgent(void);
  126. void Init(void);
  127. void Reset(void);
  128. void EndMenu(void);
  129. void UpdateInsertionCaret(void);
  130. void SetEditMode(BOOL bEdit, DWORD dwEffect);
  131. void SetCaretPos(LPPOINT ppt);
  132. HCURSOR SetCursor(DWORD dwEffect);
  133. void SetCurrentRect(HDC hdc, LPRECT prcItem);
  134. void SetItem(PFILEMENUITEM pfmi) { _pfmiCur = pfmi; }
  135. void SetDragItem(void) { _pfmiDrag = _pfmiCur; }
  136. void SetDropItem(void);
  137. DWORD GetDragEffect(void);
  138. BOOL ProcessCommand(HWND hwnd, HMENU hmenuBar, UINT idMenu, HMENU hmenu, UINT idCmd);
  139. friend LRESULT CALLBACK CFSMenuAgent_MsgHook(int nCode, WPARAM wParam, LPARAM lParam);
  140. friend LRESULT FileMenu_DrawItem(HWND hwnd, DRAWITEMSTRUCT *pdi);
  141. };
  142. // Menu agent state
  143. #define MAS_EDITMODE 0x00000001
  144. #define MAS_LBUTTONDOWN 0x00000002
  145. #define MAS_LBUTTONUP 0x00000004
  146. #define MAS_INSERTABOVE 0x00000008
  147. // Edit mode states
  148. #define MenuDD_IsButtonDown() (g_fsmenuagent._dwState & MAS_LBUTTONDOWN)
  149. #define MenuDD_InEditMode() (g_fsmenuagent._dwState & MAS_EDITMODE)
  150. #define MenuDD_InsertAbove() (g_fsmenuagent._dwState & MAS_INSERTABOVE)
  151. #define MenuDD_GetBrush() (g_fsmenuagent._hbr)
  152. //---------------------------------------------------------------------------
  153. CFSMenuAgent g_fsmenuagent;
  154. PFILEMENUITEM g_pfmiLastSel = NULL;
  155. PFILEMENUITEM g_pfmiLastSelNonFolder = NULL;
  156. // This saves us creating DC's all the time for the blits.
  157. HDC g_hdcMem = NULL;
  158. HFONT g_hfont = NULL;
  159. BOOL g_fAbortInitMenu = FALSE;
  160. // Tooltip stuff.
  161. HWND g_hwndTip = NULL;
  162. RECT g_rcItem = {0, 0, 0, 0};
  163. HIMAGELIST g_himlIconsSmall = NULL;
  164. HIMAGELIST g_himlIcons = NULL;
  165. //---------------------------------------------------------------------------
  166. // Validation functions
  167. // IEUNIX -- these functions don't appear to be defined anywhere else, as the
  168. // other def'n would imply
  169. #if defined(DEBUG) || defined(UNIX)
  170. BOOL IsValidPFILEMENUHEADER(PFILEMENUHEADER pfmh)
  171. {
  172. return (IS_VALID_WRITE_PTR(pfmh, FILEMENUHEADER) &&
  173. ((NULL == pfmh->psf &&
  174. NULL == pfmh->pidlFolder) ||
  175. (IS_VALID_CODE_PTR(pfmh->psf, IShellFolder) &&
  176. IS_VALID_PIDL(pfmh->pidlFolder))) &&
  177. IS_VALID_HANDLE(pfmh->hmenu, MENU) &&
  178. IS_VALID_HANDLE(pfmh->hdpa, DPA) &&
  179. ((NULL == pfmh->psfAlt &&
  180. NULL == pfmh->pidlAltFolder) ||
  181. (IS_VALID_CODE_PTR(pfmh->psfAlt, IShellFolder) &&
  182. IS_VALID_PIDL(pfmh->pidlAltFolder))) &&
  183. (NULL == pfmh->hdpaAlt || IS_VALID_HANDLE(pfmh->hdpaAlt, DPA)) &&
  184. (NULL == pfmh->pszFilterTypes || IS_VALID_STRING_PTR(pfmh->pszFilterTypes, -1)));
  185. }
  186. BOOL IsValidPFILEMENUITEM(PFILEMENUITEM pfmi)
  187. {
  188. return (IS_VALID_WRITE_PTR(pfmi, FILEMENUITEM) &&
  189. IS_VALID_STRUCT_PTR(pfmi->pfmh, FILEMENUHEADER) &&
  190. (NULL == pfmi->pidl || IS_VALID_PIDL(pfmi->pidl)) &&
  191. (NULL == pfmi->psz || IS_VALID_STRING_PTR(pfmi->psz, -1)) &&
  192. (NULL == pfmi->pszTooltip || IS_VALID_STRING_PTR(pfmi->pszTooltip, -1)));
  193. }
  194. #else
  195. BOOL IsValidPFILEMENUHEADER(PFILEMENUHEADER pfmh);
  196. BOOL IsValidPFILEMENUITEM(PFILEMENUITEM pfmi);
  197. #endif
  198. //---------------------------------------------------------------------------
  199. // Helper functions
  200. void FileList_Reorder(PFILEMENUHEADER pfmh);
  201. BOOL FileMenuHeader_InsertItem(PFILEMENUHEADER pfmh, UINT iItem, FMIIFLAGS fFlags);
  202. BOOL FileMenuItem_Destroy(PFILEMENUITEM pfmi);
  203. BOOL FileMenuItem_Move(HWND hwnd, PFILEMENUITEM pfmiFrom, PFILEMENUHEADER pfmhTo, int iPosTo);
  204. BOOL Tooltip_Create(HWND *phwndTip);
  205. __inline static BOOL LAlloc(UINT cb, PVOID *ppv)
  206. {
  207. ASSERT(ppv);
  208. *ppv = (PVOID*)LocalAlloc(LPTR, cb);
  209. return *ppv ? TRUE : FALSE;
  210. }
  211. __inline static BOOL LFree(PVOID pv)
  212. {
  213. return LocalFree(pv) ? FALSE : TRUE;
  214. }
  215. /*----------------------------------------------------------
  216. Purpose: Allocate a multi-string (double-null terminated)
  217. Returns:
  218. Cond: --
  219. */
  220. BOOL
  221. MultiSz_AllocCopy(
  222. IN LPCTSTR pszSrc,
  223. OUT LPTSTR * ppszDst)
  224. {
  225. BOOL fRet = FALSE;
  226. UINT cch;
  227. UINT cchMac = 0;
  228. LPCTSTR psz;
  229. LPTSTR pszDst;
  230. ASSERT(pszSrc && ppszDst);
  231. psz = pszSrc;
  232. while (*psz)
  233. {
  234. cch = lstrlen(psz) + 1;
  235. cchMac += cch;
  236. psz += cch;
  237. }
  238. cchMac++; // extra null
  239. if (LAlloc(CbFromCch(cchMac), (PVOID *)ppszDst))
  240. {
  241. psz = pszSrc;
  242. pszDst = *ppszDst;
  243. while (*psz)
  244. {
  245. lstrcpy(pszDst, psz);
  246. cch = lstrlen(psz) + 1;
  247. psz += cch;
  248. pszDst += cch;
  249. }
  250. fRet = TRUE;
  251. }
  252. return fRet;
  253. }
  254. /*----------------------------------------------------------
  255. Purpose: Allocate a string
  256. Returns:
  257. Cond: --
  258. */
  259. BOOL
  260. Sz_AllocCopyA(
  261. IN LPCSTR pszSrc,
  262. OUT LPTSTR *ppszDst)
  263. {
  264. BOOL fRet = FALSE;
  265. UINT cch;
  266. ASSERT(pszSrc && ppszDst);
  267. // NB We allocate an extra char in case we need to add an '&'.
  268. cch = lstrlenA(pszSrc) + 2;
  269. if (LAlloc(CbFromCchA(cch), (PVOID *)ppszDst))
  270. {
  271. #ifdef UNICODE
  272. MultiByteToWideChar(CP_ACP, 0, pszSrc, -1, *ppszDst, cch);
  273. #else
  274. lstrcpy(*ppszDst, pszSrc);
  275. #endif
  276. fRet = TRUE;
  277. }
  278. return fRet;
  279. }
  280. /*----------------------------------------------------------
  281. Purpose: Allocate a string
  282. Returns:
  283. Cond: --
  284. */
  285. BOOL
  286. Sz_AllocCopyW(
  287. IN LPCWSTR pszSrc,
  288. OUT LPTSTR *ppszDst)
  289. {
  290. BOOL fRet = FALSE;
  291. UINT cch;
  292. ASSERT(pszSrc && ppszDst);
  293. // NB We allocate an extra char in case we need to add an '&'.
  294. cch = lstrlenW(pszSrc) + 2;
  295. if (LAlloc(CbFromCchW(cch), (PVOID *)ppszDst))
  296. {
  297. #ifdef UNICODE
  298. lstrcpy(*ppszDst, pszSrc);
  299. #else
  300. WideCharToMultiByte(CP_ACP, 0, pszSrc, -1, *ppszDst, CbFromCchW(cch), NULL, NULL);
  301. #endif
  302. fRet = TRUE;
  303. }
  304. return fRet;
  305. }
  306. #ifdef UNICODE
  307. #define Sz_AllocCopy Sz_AllocCopyW
  308. #else
  309. #define Sz_AllocCopy Sz_AllocCopyA
  310. #endif
  311. HCURSOR LoadMenuCursor(UINT idCur)
  312. {
  313. #ifdef IN_SHDOCVW
  314. return LoadCursor(HINST_THISDLL, MAKEINTRESOURCE(idCur));
  315. #else
  316. HINSTANCE hinst = GetModuleHandle(TEXT("shdocvw.dll"));
  317. if (hinst)
  318. return LoadCursor(hinst, MAKEINTRESOURCE(idCur));
  319. return LoadCursor(NULL, IDC_ARROW);
  320. #endif
  321. }
  322. //---------------------------------------------------------------------------
  323. //---------------------------------------------------------------------------
  324. #ifdef DEBUG
  325. static void DumpMsg(MSG * pmsg)
  326. {
  327. ASSERT(pmsg);
  328. switch (pmsg->message)
  329. {
  330. case WM_LBUTTONDOWN:
  331. TraceMsg(TF_ALWAYS, "MsgHook: msg = WM_LBUTTONDOWN hwnd = %#08lx x = %d y = %d",
  332. pmsg->hwnd, pmsg->pt.x, pmsg->pt.y);
  333. TraceMsg(TF_ALWAYS, " keys = %#04lx x = %d y = %d",
  334. pmsg->wParam, GET_X_LPARAM(pmsg->lParam), GET_Y_LPARAM(pmsg->lParam));
  335. break;
  336. case WM_LBUTTONUP:
  337. TraceMsg(TF_ALWAYS, "MsgHook: msg = WM_LBUTTONUP hwnd = %#08lx x = %d y = %d",
  338. pmsg->hwnd, pmsg->pt.x, pmsg->pt.y);
  339. TraceMsg(TF_ALWAYS, " keys = %#04lx x = %d y = %d",
  340. pmsg->wParam, GET_X_LPARAM(pmsg->lParam), GET_Y_LPARAM(pmsg->lParam));
  341. break;
  342. case WM_MOUSEMOVE:
  343. break;
  344. case WM_TIMER:
  345. TraceMsg(TF_ALWAYS, "MsgHook: msg = WM_TIMER hwnd = %#08lx x = %d y = %d",
  346. pmsg->hwnd, pmsg->pt.x, pmsg->pt.y);
  347. TraceMsg(TF_ALWAYS, " id = %#08lx",
  348. pmsg->wParam);
  349. break;
  350. case WM_MENUSELECT:
  351. TraceMsg(TF_ALWAYS, "MsgHook: msg = WM_MENUSELECT hwnd = %#08lx x = %d y = %d",
  352. pmsg->hwnd, pmsg->pt.x, pmsg->pt.y);
  353. TraceMsg(TF_ALWAYS, " uItem = %#04lx flags = %#04lx hmenu = %#08lx",
  354. GET_WM_MENUSELECT_CMD(pmsg->wParam, pmsg->lParam),
  355. GET_WM_MENUSELECT_FLAGS(pmsg->wParam, pmsg->lParam),
  356. GET_WM_MENUSELECT_HMENU(pmsg->wParam, pmsg->lParam));
  357. break;
  358. default:
  359. TraceMsg(TF_ALWAYS, "MsgHook: msg = %#04lx hwnd = %#04lx x = %d y = %d",
  360. pmsg->message, pmsg->hwnd, pmsg->pt.x, pmsg->pt.y);
  361. break;
  362. }
  363. }
  364. #endif
  365. /*----------------------------------------------------------
  366. Purpose: Message hook used to track drag and drop within the menu.
  367. */
  368. LRESULT CALLBACK CFSMenuAgent_MsgHook(int nCode, WPARAM wParam, LPARAM lParam)
  369. {
  370. LRESULT lRet = 0;
  371. MSG * pmsg = (MSG *)lParam;
  372. switch (nCode)
  373. {
  374. case MSGF_MENU:
  375. #ifdef DEBUG
  376. if (IsFlagSet(g_dwDumpFlags, DF_HOOK))
  377. DumpMsg(pmsg);
  378. #endif
  379. switch (pmsg->message)
  380. {
  381. case WM_LBUTTONUP:
  382. // We record the mouse up IFF it happened in the menu
  383. // and we had previously recorded the mouse down.
  384. if (IsFlagSet(g_fsmenuagent._dwState, MAS_EDITMODE | MAS_LBUTTONDOWN))
  385. {
  386. POINT pt;
  387. TraceMsg(TF_MENU, "MenuDD: getting mouse up");
  388. pt.x = GET_X_LPARAM(pmsg->lParam);
  389. pt.y = GET_Y_LPARAM(pmsg->lParam);
  390. if (PtInRect(&g_fsmenuagent._rcMenu, pt))
  391. {
  392. SetFlag(g_fsmenuagent._dwState, MAS_LBUTTONUP);
  393. g_fsmenuagent.EndMenu();
  394. }
  395. }
  396. ClearFlag(g_fsmenuagent._dwState, MAS_LBUTTONDOWN);
  397. break;
  398. case WM_LBUTTONDOWN:
  399. if (g_fsmenuagent._pfmiCur &&
  400. (g_fsmenuagent._pfmiCur->dwEffect & DROPEFFECT_MOVE))
  401. {
  402. TraceMsg(TF_MENU, "MenuDD: getting mouse down");
  403. SetFlag(g_fsmenuagent._dwState, MAS_LBUTTONDOWN);
  404. g_fsmenuagent.SetDragItem();
  405. }
  406. break;
  407. case WM_MOUSEMOVE:
  408. if (g_fsmenuagent._dwState & MAS_EDITMODE)
  409. {
  410. POINT pt;
  411. BOOL bInMenu;
  412. pt.x = GET_X_LPARAM(pmsg->lParam);
  413. pt.y = GET_Y_LPARAM(pmsg->lParam);
  414. g_fsmenuagent.SetCaretPos(&pt);
  415. bInMenu = PtInRect(&g_fsmenuagent._rcMenu, pt);
  416. #if 0
  417. TraceMsg(TF_MENU, "MenuDD: %s (%d,%d) in [%d,%d,%d,%d]",
  418. bInMenu ? TEXT("in menu") : TEXT("not in menu"),
  419. pt.x, pt.y, g_fsmenuagent._rcMenu.left, g_fsmenuagent._rcMenu.top,
  420. g_fsmenuagent._rcMenu.right, g_fsmenuagent._rcMenu.bottom);
  421. #endif
  422. // Determine which cursor to show
  423. if ( !bInMenu )
  424. g_fsmenuagent.SetItem(NULL);
  425. g_fsmenuagent.SetCursor(g_fsmenuagent.GetDragEffect());
  426. }
  427. break;
  428. case WM_MENUSELECT:
  429. BLOCK
  430. {
  431. UINT uItem = GET_WM_MENUSELECT_CMD(pmsg->wParam, pmsg->lParam);
  432. HMENU hmenu = GET_WM_MENUSELECT_HMENU(pmsg->wParam, pmsg->lParam);
  433. // Is the menu going away?
  434. if (0 == uItem && NULL == hmenu)
  435. {
  436. // Yes; release menu drag/drop
  437. TraceMsg(TF_MENU, "MenuDD: menu being cancelled");
  438. // Since we're in the middle of the hook chain, call
  439. // the next hook first, then remove ourselves
  440. lRet = CallNextHookEx(g_fsmenuagent._hhookMsg, nCode, wParam, lParam);
  441. // Was an item dropped?
  442. if (g_fsmenuagent._dwState & MAS_LBUTTONUP)
  443. {
  444. // Yes; remember it
  445. g_fsmenuagent.SetDropItem();
  446. }
  447. g_fsmenuagent.Reset();
  448. return lRet;
  449. }
  450. }
  451. break;
  452. }
  453. break;
  454. default:
  455. if (0 > nCode)
  456. return CallNextHookEx(g_fsmenuagent._hhookMsg, nCode, wParam, lParam);
  457. break;
  458. }
  459. // Pass it on to the next hook in the chain
  460. if (0 == lRet)
  461. lRet = CallNextHookEx(g_fsmenuagent._hhookMsg, nCode, wParam, lParam);
  462. return lRet;
  463. }
  464. CFSMenuAgent::CFSMenuAgent(void)
  465. {
  466. // This object is global, and not allocated. We must explicitly
  467. // initialize the variables.
  468. _dwState = 0;
  469. _hhookMsg = 0;
  470. _dwStateSav = 0;
  471. _pfmiCur = NULL;
  472. _pfmiDrag = NULL;
  473. _pfmiDrop = NULL;
  474. _hwndMenu = NULL;
  475. _hdc = NULL;
  476. _hcurSav = NULL;
  477. _hbr = NULL;
  478. }
  479. /*----------------------------------------------------------
  480. Purpose: Initialize the menu drag/drop structure. This must
  481. anticipate being called when it is already initialized.
  482. Also, this will get called whenever a cascaded menu
  483. is opened. Be sure to maintain state across these
  484. junctures.
  485. */
  486. void CFSMenuAgent::Init(void)
  487. {
  488. TraceMsg(TF_MENU, "Initialize menu drag/drop");
  489. // Do not init _pfmiDrag, since this function is called for every
  490. // cascaded menu, and _pfmiDrag must be remembered across these
  491. // menus.
  492. _pfmiDrop = NULL;
  493. _dwStateSav = 0;
  494. if (NULL == _hhookMsg)
  495. {
  496. _hhookMsg = SetWindowsHookEx(WH_MSGFILTER, CFSMenuAgent_MsgHook, HINST_THISDLL, 0);
  497. }
  498. if (NULL == _hbr)
  499. {
  500. // Don't need to release this
  501. _hbr = GetSysColorBrush(COLOR_3DFACE);
  502. }
  503. }
  504. /*----------------------------------------------------------
  505. Purpose: Make the menu go away
  506. */
  507. void CFSMenuAgent::EndMenu(void)
  508. {
  509. ASSERT(IsWindow(_hwndMenu));
  510. SendMessage(_hwndMenu, WM_CANCELMODE, 0, 0);
  511. }
  512. /*----------------------------------------------------------
  513. Purpose: Decides whether to position the caret above or below the
  514. menu item, based upon the given point (cursor position).
  515. */
  516. void CFSMenuAgent::SetCaretPos(LPPOINT ppt)
  517. {
  518. ASSERT(ppt);
  519. if (ppt->y < _yCenter)
  520. {
  521. // Change the caret position?
  522. if (IsFlagClear(_dwState, MAS_INSERTABOVE))
  523. {
  524. // Yes
  525. SetFlag(_dwState, MAS_INSERTABOVE);
  526. UpdateInsertionCaret();
  527. }
  528. }
  529. else
  530. {
  531. // Change the caret position?
  532. if (IsFlagSet(_dwState, MAS_INSERTABOVE))
  533. {
  534. // Yes
  535. ClearFlag(_dwState, MAS_INSERTABOVE);
  536. UpdateInsertionCaret();
  537. }
  538. }
  539. }
  540. void CFSMenuAgent::UpdateInsertionCaret(void)
  541. {
  542. if (_dwState & MAS_EDITMODE)
  543. {
  544. InvalidateRect(_hwndMenu, &_rcCur, FALSE);
  545. UpdateWindow(_hwndMenu);
  546. }
  547. }
  548. void CFSMenuAgent::SetDropItem(void)
  549. {
  550. // Only set the drop item if the drop effect is supported.
  551. ASSERT(_pfmiDrag);
  552. if (_pfmiCur && (_pfmiCur->dwEffect & _pfmiDrag->dwEffect))
  553. _pfmiDrop = _pfmiCur;
  554. else
  555. _pfmiDrop = NULL;
  556. }
  557. /*----------------------------------------------------------
  558. Purpose: Set the cursor based on the given flags
  559. */
  560. HCURSOR CFSMenuAgent::SetCursor(DWORD dwEffect)
  561. {
  562. HCURSOR hcur = NULL;
  563. ASSERT(_dwState & MAS_EDITMODE);
  564. // Does this item support the requested drop effect?
  565. if (_pfmiCur && (dwEffect & _pfmiCur->dwEffect))
  566. {
  567. // Yes
  568. UINT idCur;
  569. if (dwEffect & DROPEFFECT_MOVE)
  570. idCur = IDC_MENUMOVE;
  571. else if (dwEffect & DROPEFFECT_COPY)
  572. idCur = IDC_MENUCOPY;
  573. else
  574. {
  575. ASSERT_MSG(0, "Unknown drop effect!");
  576. idCur = IDC_MENUDENY;
  577. }
  578. hcur = ::SetCursor(LoadMenuCursor(idCur));
  579. }
  580. else
  581. {
  582. // No
  583. hcur = ::SetCursor(LoadMenuCursor(IDC_MENUDENY));
  584. }
  585. return hcur;
  586. }
  587. DWORD CFSMenuAgent::GetDragEffect(void)
  588. {
  589. if (_pfmiDrag)
  590. return _pfmiDrag->dwEffect;
  591. else
  592. return DROPEFFECT_NONE;
  593. }
  594. void CFSMenuAgent::SetEditMode(BOOL bEdit, DWORD dwEffect)
  595. {
  596. // Only update if the state has changed
  597. if (bEdit && IsFlagClear(_dwState, MAS_EDITMODE))
  598. {
  599. TraceMsg(TF_MENU, "MenuDD: entering edit mode");
  600. SetFlag(_dwState, MAS_EDITMODE);
  601. _hcurSav = SetCursor(dwEffect);
  602. }
  603. else if (!bEdit && IsFlagSet(_dwState, MAS_EDITMODE))
  604. {
  605. TraceMsg(TF_MENU, "MenuDD: leaving edit mode");
  606. ClearFlag(_dwState, MAS_EDITMODE);
  607. ASSERT(_hcurSav);
  608. if (_hcurSav)
  609. {
  610. ::SetCursor(_hcurSav);
  611. _hcurSav = NULL;
  612. }
  613. }
  614. }
  615. void CFSMenuAgent::SetCurrentRect(HDC hdc, LPRECT prcItem)
  616. {
  617. HWND hwnd = WindowFromDC(hdc);
  618. ASSERT(hdc);
  619. ASSERT(prcItem);
  620. _hwndMenu = hwnd;
  621. _hdc = hdc;
  622. _rcCur = *prcItem;
  623. _rcCurScr = *prcItem;
  624. GetWindowRect(hwnd, &_rcMenu);
  625. MapWindowPoints(hwnd, NULL, (LPPOINT)&_rcCurScr, 2);
  626. _yCenter = _rcCurScr.top + (_rcCurScr.bottom - _rcCurScr.top) / 2;
  627. }
  628. /*----------------------------------------------------------
  629. Purpose: Reset the menu agent. This is called when the menu goes
  630. away. The ProcessCommand method still needs some state
  631. information so it knows what action had taken place.
  632. This info is moved to a post-action field.
  633. */
  634. void CFSMenuAgent::Reset(void)
  635. {
  636. TraceMsg(TF_MENU, "MenuDD: releasing edit mode resources");
  637. // Remember the state for FileMenu_ProcessCommand
  638. _dwStateSav = _dwState;
  639. SetEditMode(FALSE, DROPEFFECT_NONE);
  640. TraceMsg(TF_MENU, "MenuDD: Hook removed for menu drag/drop");
  641. if (_hhookMsg)
  642. {
  643. UnhookWindowsHookEx(_hhookMsg);
  644. _hhookMsg = NULL;
  645. }
  646. // Reset
  647. _pfmiCur = NULL;
  648. _hwndMenu = NULL;
  649. _hdc = NULL;
  650. _dwState = 0;
  651. _hbr = NULL;
  652. ASSERT(NULL == _hcurSav);
  653. }
  654. /*----------------------------------------------------------
  655. Purpose: Have a whack at the WM_COMMAND, in case it is the result
  656. of drag and drop within the menu.
  657. Returns: TRUE if this function handled the command
  658. */
  659. BOOL CFSMenuAgent::ProcessCommand(HWND hwnd, HMENU hmenuBar, UINT idMenu, HMENU hmenu, UINT idCmd)
  660. {
  661. BOOL bRet = FALSE;
  662. if (hmenu && _pfmiDrag && (_dwStateSav & MAS_EDITMODE))
  663. {
  664. ASSERT(IS_VALID_STRUCT_PTR(_pfmiDrag, FILEMENUITEM));
  665. // Did the user move an item within the menu?
  666. if (_pfmiDrop)
  667. {
  668. // Yes
  669. ASSERT(IS_VALID_STRUCT_PTR(_pfmiDrop, FILEMENUITEM));
  670. int iPosTo = DPA_GetPtrIndex(_pfmiDrop->pfmh->hdpa, _pfmiDrop);
  671. if (IsFlagClear(_dwStateSav, MAS_INSERTABOVE))
  672. iPosTo++;
  673. IEPlaySound(TEXT("MoveMenuItem"), FALSE);
  674. bRet = FileMenuItem_Move(hwnd, _pfmiDrag, _pfmiDrop->pfmh, iPosTo);
  675. // Re-order the items
  676. FileList_Reorder(_pfmiDrop->pfmh);
  677. }
  678. _pfmiDrag = NULL;
  679. _pfmiDrop = NULL;
  680. #if 0
  681. // Did we successfully handle this?
  682. if (bRet)
  683. {
  684. // Yes; bring the menu back up so the user can continue
  685. // editting.
  686. HiliteMenuItem(hwnd, hmenuBar, idMenu, MF_BYCOMMAND | MF_HILITE);
  687. DrawMenuBar(hwnd);
  688. TrackPopupMenu(hmenu, TPM_LEFTALIGN, _rcMenu.left,
  689. _rcMenu.top, 0, hwnd, NULL);
  690. HiliteMenuItem(hwnd, hmenuBar, idMenu, MF_BYCOMMAND | MF_UNHILITE);
  691. DrawMenuBar(hwnd);
  692. }
  693. #endif
  694. // Always return true because we handled it
  695. bRet = TRUE;
  696. }
  697. return bRet;
  698. }
  699. //---------------------------------------------------------------------------
  700. //---------------------------------------------------------------------------
  701. //---------------------------------------------------------------------------
  702. void DeleteGlobalMemDCAndFont(void)
  703. {
  704. if (g_hdcMem)
  705. {
  706. DeleteDC(g_hdcMem);
  707. g_hdcMem = NULL;
  708. }
  709. if (g_hfont)
  710. {
  711. DeleteObject(g_hfont);
  712. g_hfont = NULL;
  713. }
  714. }
  715. DWORD
  716. GetItemTextExtent(
  717. IN HDC hdc,
  718. IN LPCTSTR lpsz)
  719. {
  720. SIZE sz;
  721. GetTextExtentPoint(hdc, lpsz, lstrlen(lpsz), &sz);
  722. // NB This is OK as long as an item's extend doesn't get very big.
  723. return MAKELONG((WORD)sz.cx, (WORD)sz.cy);
  724. }
  725. /*----------------------------------------------------------
  726. Purpose: Validates pfmitem. This also initializes pfmitemOut
  727. given the mask and flags set in pfmitem. This helper
  728. function is useful for APIs to "cleanse" incoming
  729. FMITEM structures.
  730. Returns: TRUE if pfmitem is a valid structure
  731. Cond: --
  732. */
  733. BOOL
  734. IsValidFMItem(
  735. IN FMITEM const * pfmitem,
  736. OUT PFMITEM pfmitemOut)
  737. {
  738. BOOL bRet = FALSE;
  739. ASSERT(pfmitem);
  740. ASSERT(pfmitemOut);
  741. if (IS_VALID_READ_PTR(pfmitem, FMITEM) &&
  742. SIZEOF(*pfmitem) == pfmitem->cbSize)
  743. {
  744. ZeroInit(pfmitemOut, SIZEOF(*pfmitemOut));
  745. pfmitemOut->cbSize = SIZEOF(*pfmitemOut);
  746. pfmitemOut->dwMask = pfmitem->dwMask;
  747. if (pfmitemOut->dwMask & FMI_TYPE)
  748. pfmitemOut->dwType = pfmitem->dwType;
  749. if (pfmitemOut->dwMask & FMI_ID)
  750. pfmitemOut->uID = pfmitem->uID;
  751. if (pfmitemOut->dwMask & FMI_ITEM)
  752. pfmitemOut->uItem = pfmitem->uItem;
  753. if (pfmitemOut->dwMask & FMI_IMAGE)
  754. pfmitemOut->iImage = pfmitem->iImage;
  755. else
  756. pfmitemOut->iImage = -1;
  757. if (pfmitemOut->dwMask & FMI_DATA)
  758. pfmitemOut->pvData = pfmitem->pvData;
  759. if (pfmitemOut->dwMask & FMI_HMENU)
  760. pfmitemOut->hmenuSub = pfmitem->hmenuSub;
  761. if (pfmitemOut->dwMask & FMI_METRICS)
  762. pfmitemOut->cyItem = pfmitem->cyItem;
  763. if (pfmitemOut->dwMask & FMI_LPARAM)
  764. pfmitemOut->lParam = pfmitem->lParam;
  765. // The FMIT_STRING and FMIT_SEPARATOR are exclusive
  766. if (IsFlagSet(pfmitemOut->dwType, FMIT_STRING) &&
  767. IsFlagSet(pfmitemOut->dwType, FMIT_SEPARATOR))
  768. {
  769. bRet = FALSE;
  770. }
  771. else
  772. bRet = TRUE;
  773. }
  774. return bRet;
  775. }
  776. void
  777. FileMenuItem_GetDisplayName(
  778. IN PFILEMENUITEM pfmi,
  779. IN LPTSTR pszName,
  780. IN UINT cchName)
  781. {
  782. STRRET str;
  783. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  784. ASSERT(IS_VALID_WRITE_BUFFER(pszName, TCHAR, cchName));
  785. // Is this a special empty item?
  786. if (pfmi->Flags & FMI_EMPTY)
  787. {
  788. // Yep, load the string from a resource.
  789. LoadString(HINST_THISDLL, IDS_NONE, pszName, cchName);
  790. }
  791. else
  792. {
  793. // Nope, ask the folder for the name of the item.
  794. PFILEMENUHEADER pfmh = pfmi->pfmh;
  795. LPSHELLFOLDER psfTemp;
  796. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  797. if (pfmi->Flags & FMI_ALTITEM) {
  798. psfTemp = pfmh->psfAlt;
  799. } else {
  800. psfTemp = pfmh->psf;
  801. }
  802. // If it's got a pidl use that, else just use the normal menu string.
  803. if (psfTemp && pfmi->pidl &&
  804. IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL))
  805. {
  806. if (SUCCEEDED(psfTemp->GetDisplayNameOf(pfmi->pidl, SHGDN_NORMAL, &str)))
  807. {
  808. StrRetToBuf(&str, pfmi->pidl, pszName, cchName);
  809. }
  810. else
  811. {
  812. *pszName = TEXT('\0');
  813. }
  814. }
  815. else if (pfmi->psz)
  816. {
  817. lstrcpyn(pszName, pfmi->psz, cchName);
  818. }
  819. else
  820. {
  821. *pszName = TEXT('\0');
  822. }
  823. }
  824. }
  825. #define FileMenuHeader_AllowAbort(pfmh) (!(pfmh->fmf & FMF_NOABORT))
  826. /*----------------------------------------------------------
  827. Purpose: Create a menu item structure to be stored in the hdpa
  828. Returns: TRUE on success
  829. Cond: --
  830. */
  831. BOOL
  832. FileMenuItem_Create(
  833. IN PFILEMENUHEADER pfmh,
  834. IN LPCITEMIDLIST pidl, OPTIONAL
  835. IN int iImage,
  836. IN DWORD dwFlags, // FMI_*
  837. OUT PFILEMENUITEM * ppfmi)
  838. {
  839. PFILEMENUITEM pfmi = (PFILEMENUITEM)LocalAlloc(LPTR, SIZEOF(FILEMENUITEM));
  840. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  841. ASSERT(ppfmi);
  842. ASSERT(NULL == pidl || IS_VALID_PIDL(pidl));
  843. if (pfmi)
  844. {
  845. DWORD dwAttribs = SFGAO_FOLDER | SFGAO_FILESYSTEM;
  846. IShellFolder * psfTemp;
  847. BOOL bUseAlt = IsFlagSet(dwFlags, FMI_ALTITEM);
  848. pfmi->pfmh = pfmh;
  849. pfmi->pidl = (LPITEMIDLIST)pidl;
  850. pfmi->iImage = iImage;
  851. pfmi->Flags = dwFlags;
  852. pfmi->nOrder = INT_MAX; // New items go to the bottom
  853. if (bUseAlt)
  854. psfTemp = pfmh->psfAlt;
  855. else
  856. psfTemp = pfmh->psf;
  857. if (pidl &&
  858. SUCCEEDED(psfTemp->GetAttributesOf(1, &pidl, &dwAttribs)))
  859. {
  860. if (dwAttribs & SFGAO_FOLDER)
  861. pfmi->Flags |= FMI_FOLDER;
  862. if (dwAttribs & SFGAO_FILESYSTEM)
  863. pfmi->Flags |= FMI_FILESYSTEM;
  864. }
  865. }
  866. *ppfmi = pfmi;
  867. return (NULL != pfmi);
  868. }
  869. /*----------------------------------------------------------
  870. Purpose: Move an item within the same menu or across menus
  871. */
  872. BOOL FileMenuItem_Move(
  873. HWND hwnd,
  874. PFILEMENUITEM pfmiFrom,
  875. PFILEMENUHEADER pfmhTo,
  876. int iPosTo)
  877. {
  878. BOOL bRet = FALSE;
  879. TCHAR szFrom[MAX_PATH + 1]; // +1 for double null
  880. ASSERT(IS_VALID_STRUCT_PTR(pfmiFrom, FILEMENUITEM));
  881. ASSERT(IS_VALID_STRUCT_PTR(pfmhTo, FILEMENUHEADER));
  882. PFILEMENUHEADER pfmhFrom = pfmiFrom->pfmh;
  883. HDPA hdpaFrom = pfmhFrom->hdpa;
  884. HDPA hdpaTo = pfmhTo->hdpa;
  885. BOOL bSameMenu = (pfmhFrom == pfmhTo);
  886. ASSERT(IsFlagSet(pfmhFrom->fmf, FMF_CANORDER));
  887. // Is this item being moved within the same menu?
  888. if (bSameMenu)
  889. {
  890. // Yes; simply change the order of the menu below
  891. bRet = TRUE;
  892. }
  893. else
  894. {
  895. // No; need to move the actual file to the menu's associated
  896. // folder. Also note the placement of the item in the menu.
  897. TCHAR szTo[MAX_PATH + 1]; // +1 for double null
  898. IShellFolder * psf = pfmhFrom->psf;
  899. STRRET str;
  900. SHGetPathFromIDList(pfmhTo->pidlFolder, szTo);
  901. szTo[lstrlen(szTo) + 1] = 0; // double null
  902. if (SUCCEEDED(psf->GetDisplayNameOf(pfmiFrom->pidl, SHGDN_FORPARSING, &str)))
  903. {
  904. StrRetToBuf(&str, pfmiFrom->pidl, szFrom, SIZECHARS(szFrom));
  905. szFrom[lstrlen(szFrom) + 1] = 0; // double null
  906. // WARNING: if you change this code to perform rename on
  907. // collision, be sure to update the pfmiFrom contents to
  908. // reflect that name change!
  909. SHFILEOPSTRUCT shop = {hwnd, FO_MOVE, szFrom, szTo, 0, };
  910. bRet = (NO_ERROR == SHFileOperation(&shop));
  911. if (bRet)
  912. {
  913. // Flush the notification so the menu is updated immediately.
  914. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSH, szFrom, NULL);
  915. }
  916. // The move operation will send a notification to the
  917. // window, which will eventually invalidate this menu
  918. // to have it rebuilt. However, before that happens
  919. // we want to record the position of this dragged item
  920. // in the destination menu. So change the order of
  921. // the menu anyway.
  922. }
  923. }
  924. if (bRet)
  925. {
  926. // Change the order of the menu
  927. int iPosFrom = DPA_GetPtrIndex(hdpaFrom, pfmiFrom);
  928. bRet = FALSE;
  929. // Account for the fact we delete before we insert within the
  930. // same menu
  931. if (bSameMenu && iPosTo > iPosFrom)
  932. iPosTo--;
  933. DPA_DeletePtr(hdpaFrom, iPosFrom);
  934. iPosTo = DPA_InsertPtr(hdpaTo, iPosTo, pfmiFrom);
  935. if (-1 != iPosTo)
  936. {
  937. // Update the header of the item
  938. pfmiFrom->pfmh = pfmhTo;
  939. // Move the menu items
  940. MENUITEMINFO mii;
  941. mii.cbSize = SIZEOF(mii);
  942. mii.fMask = MIIM_DATA | MIIM_ID | MIIM_STATE | MIIM_SUBMENU | MIIM_TYPE;
  943. if (GetMenuItemInfo(pfmhFrom->hmenu, iPosFrom, TRUE, &mii))
  944. {
  945. // Remove a submenu first so it doesn't get nuked
  946. if (GetSubMenu(pfmhFrom->hmenu, iPosFrom))
  947. RemoveMenu(pfmhFrom->hmenu, iPosFrom, MF_BYPOSITION);
  948. DeleteMenu(pfmhFrom->hmenu, iPosFrom, MF_BYPOSITION);
  949. if ( !InsertMenuItem(pfmhTo->hmenu, iPosTo, TRUE, &mii) )
  950. {
  951. TraceMsg(TF_ERROR, "Failed to move menu item");
  952. DPA_DeletePtr(hdpaTo, iPosTo);
  953. }
  954. else
  955. {
  956. SetFlag(pfmhFrom->fmf, FMF_DIRTY);
  957. SetFlag(pfmhTo->fmf, FMF_DIRTY);
  958. bRet = TRUE;
  959. }
  960. }
  961. }
  962. else
  963. {
  964. // Punt
  965. TraceMsg(TF_ERROR, "Menu: could not insert moved item in the DPA");
  966. }
  967. }
  968. return bRet;
  969. }
  970. /*----------------------------------------------------------
  971. Purpose: Enumerates the folder and adds the files to the DPA.
  972. Returns: count of items in the list
  973. */
  974. int
  975. FileList_Build(
  976. IN PFILEMENUHEADER pfmh,
  977. IN int cItems,
  978. IN BOOL bUseAlt)
  979. {
  980. HDPA hdpaTemp;
  981. HRESULT hres;
  982. LPITEMIDLIST pidlSkip = NULL;
  983. LPITEMIDLIST pidlProgs = NULL;
  984. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  985. if (FileMenuHeader_AllowAbort(pfmh) && g_fAbortInitMenu)
  986. return -1;
  987. if (bUseAlt) {
  988. hdpaTemp = pfmh->hdpaAlt;
  989. } else {
  990. hdpaTemp = pfmh->hdpa;
  991. }
  992. if (hdpaTemp && pfmh->psf)
  993. {
  994. LPENUMIDLIST penum;
  995. LPSHELLFOLDER psfTemp;
  996. // Take care with Programs folder.
  997. // If this is the parent of the programs folder set pidlSkip to
  998. // the last bit of the programs pidl.
  999. if (pfmh->fmf & FMF_NOPROGRAMS)
  1000. {
  1001. pidlProgs = SHCloneSpecialIDList(NULL,
  1002. (bUseAlt ? CSIDL_COMMON_PROGRAMS : CSIDL_PROGRAMS),
  1003. TRUE);
  1004. if (ILIsParent((bUseAlt ? pfmh->pidlAltFolder : pfmh->pidlFolder),
  1005. pidlProgs, TRUE))
  1006. {
  1007. TraceMsg(TF_MENU, "FileList_Build: Programs parent.");
  1008. pidlSkip = ILFindLastID(pidlProgs);
  1009. }
  1010. }
  1011. // Decide which shell folder to enumerate.
  1012. if (bUseAlt) {
  1013. psfTemp = pfmh->psfAlt;
  1014. } else {
  1015. psfTemp = pfmh->psf;
  1016. }
  1017. // We now need to iterate over the children under this guy...
  1018. hres = psfTemp->EnumObjects(NULL, pfmh->fFSFilter, &penum);
  1019. if (SUCCEEDED(hres))
  1020. {
  1021. ULONG celt;
  1022. LPITEMIDLIST pidl = NULL;
  1023. // The pidl is stored away into the pfmi structure, so
  1024. // don't free it here
  1025. while (penum->Next(1, &pidl, &celt) == S_OK && celt == 1)
  1026. {
  1027. PFILEMENUITEM pfmi;
  1028. // Abort.
  1029. if (FileMenuHeader_AllowAbort(pfmh) && g_fAbortInitMenu)
  1030. break;
  1031. if (pidlSkip && psfTemp->CompareIDs(0, pidlSkip, pidl) == 0)
  1032. {
  1033. ILFree(pidl); // Don't leak this one...
  1034. TraceMsg(DM_TRACE, "FileList_Build: Skipping Programs.");
  1035. continue;
  1036. }
  1037. // Is there a list of extensions on which we need to
  1038. // filter?
  1039. if (pfmh->pszFilterTypes)
  1040. {
  1041. STRRET str;
  1042. DWORD dwAttribs = SFGAO_FOLDER | SFGAO_FILESYSTEM;
  1043. psfTemp->GetAttributesOf(1, (LPCITEMIDLIST*)&pidl, &dwAttribs);
  1044. // only apply the filter to file system objects
  1045. if ((dwAttribs & SFGAO_FILESYSTEM) &&
  1046. SUCCEEDED(psfTemp->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str)))
  1047. {
  1048. TCHAR szFile[MAX_PATH];
  1049. StrRetToBuf(&str, pidl, szFile, SIZECHARS(szFile));
  1050. if (!(dwAttribs & SFGAO_FOLDER))
  1051. {
  1052. LPTSTR psz = pfmh->pszFilterTypes;
  1053. LPTSTR pszExt = PathFindExtension(szFile);
  1054. if (TEXT('.') == *pszExt)
  1055. pszExt++;
  1056. while (*psz)
  1057. {
  1058. // Skip this file?
  1059. if (0 == lstrcmpi(pszExt, psz))
  1060. break; // No
  1061. psz += lstrlen(psz) + 1;
  1062. }
  1063. if ( !*psz )
  1064. {
  1065. ILFree(pidl); // don't leak this
  1066. continue;
  1067. }
  1068. }
  1069. }
  1070. }
  1071. if (FileMenuItem_Create(pfmh, pidl, -1, bUseAlt ? FMI_ALTITEM : 0, &pfmi))
  1072. {
  1073. if (!bUseAlt)
  1074. {
  1075. // Set the allowable drop effects (as a target).
  1076. // We don't allow common user items to be moved.
  1077. pfmi->dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY;
  1078. }
  1079. int idpa = DPA_AppendPtr(hdpaTemp, pfmi);
  1080. ASSERTMSG(idpa != -1, "DPA_AppendPtr failed when adding file menu item");
  1081. if (idpa != -1)
  1082. {
  1083. // NB We only callback for non-folders at the moment
  1084. //
  1085. // HACK don't callback for non file system things
  1086. // this callback is used to set hotkeys, and that tries
  1087. // to load the PIDL passed back as a file, and that doesn't
  1088. // work for non FS pidls
  1089. if (pfmh->pfncb && (pfmi->Flags & FMI_FILESYSTEM))
  1090. {
  1091. FMCBDATA fmcbdata = { 0 };
  1092. fmcbdata.lParam = pfmh->lParam;
  1093. fmcbdata.hmenu = pfmh->hmenu;
  1094. fmcbdata.iPos = idpa;
  1095. // Don't know the id because it hasn't been
  1096. // added to the menu yet
  1097. fmcbdata.idCmd = (UINT)-1;
  1098. if (bUseAlt)
  1099. {
  1100. fmcbdata.pidlFolder = pfmh->pidlAltFolder;
  1101. }
  1102. else
  1103. {
  1104. fmcbdata.pidlFolder = pfmh->pidlFolder;
  1105. }
  1106. fmcbdata.pidl = pidl;
  1107. fmcbdata.psf = psfTemp;
  1108. fmcbdata.pvHeader = pfmh;
  1109. // if the caller returns S_FALSE then we will remove the item from the
  1110. // menu, otherwise we behave as before.
  1111. if (pfmh->pfncb(FMM_ADD, &fmcbdata, 0) == S_FALSE)
  1112. {
  1113. FileMenuItem_Destroy(pfmi);
  1114. DPA_DeletePtr(pfmh->hdpa, idpa);
  1115. }
  1116. else
  1117. {
  1118. cItems++;
  1119. }
  1120. }
  1121. else
  1122. {
  1123. cItems++;
  1124. }
  1125. }
  1126. }
  1127. }
  1128. penum->Release();
  1129. }
  1130. else
  1131. {
  1132. TraceMsg(TF_ERROR, "FileList_Build: Enumeration failed - leaving folder empty.");
  1133. }
  1134. ILFree(pidlProgs);
  1135. }
  1136. // Insert a special Empty item (unless the header flag says
  1137. // not to).
  1138. if (!cItems && hdpaTemp && !(pfmh->fmf & FMF_NOEMPTYITEM) && !bUseAlt)
  1139. {
  1140. PFILEMENUITEM pfmi;
  1141. if (FileMenuItem_Create(pfmh, NULL, -1, FMI_EMPTY, &pfmi))
  1142. {
  1143. DPA_SetPtr(hdpaTemp, cItems, pfmi);
  1144. cItems++;
  1145. }
  1146. }
  1147. return cItems;
  1148. }
  1149. #define FS_SORTBYNAME 0
  1150. #define FS_SORTBYORDINAL 1
  1151. //---------------------------------------------------------------------------
  1152. // Simplified version of the file info comparison function.
  1153. int CALLBACK FileMenuItem_Compare(LPVOID pv1, LPVOID pv2, LPARAM lParam)
  1154. {
  1155. PFILEMENUITEM pfmi1 = (PFILEMENUITEM)pv1;
  1156. PFILEMENUITEM pfmi2 = (PFILEMENUITEM)pv2;
  1157. int nRet;
  1158. TCHAR szName1[MAX_PATH];
  1159. TCHAR szName2[MAX_PATH];
  1160. switch (lParam)
  1161. {
  1162. case FS_SORTBYNAME:
  1163. // Directories come first, then files
  1164. if ((pfmi1->Flags & FMI_FOLDER) > (pfmi2->Flags & FMI_FOLDER))
  1165. return -1;
  1166. else if ((pfmi1->Flags & FMI_FOLDER) < (pfmi2->Flags & FMI_FOLDER))
  1167. return 1;
  1168. FileMenuItem_GetDisplayName(pfmi1, szName1, ARRAYSIZE(szName1));
  1169. FileMenuItem_GetDisplayName(pfmi2, szName2, ARRAYSIZE(szName2));
  1170. nRet = lstrcmpi(szName1, szName2);
  1171. break;
  1172. case FS_SORTBYORDINAL:
  1173. if (pfmi1->nOrder == pfmi2->nOrder)
  1174. nRet = 0;
  1175. else
  1176. nRet = (pfmi1->nOrder < pfmi2->nOrder ? -1 : 1);
  1177. break;
  1178. default:
  1179. ASSERT_MSG(0, "Bad lParam passed to FileMenuItem_Compare");
  1180. nRet = 0;
  1181. break;
  1182. }
  1183. return nRet;
  1184. }
  1185. LPVOID CALLBACK FileMenuItem_Merge(UINT uMsg, LPVOID pvDest, LPVOID pvSrc, LPARAM lParam)
  1186. {
  1187. PFILEMENUITEM pfmiDest = (PFILEMENUITEM)pvDest;
  1188. PFILEMENUITEM pfmiSrc = (PFILEMENUITEM)pvSrc;
  1189. LPVOID pvRet = pfmiDest;
  1190. switch (uMsg)
  1191. {
  1192. case DPAMM_MERGE:
  1193. // We just care about the order field
  1194. pfmiDest->nOrder = pfmiSrc->nOrder;
  1195. break;
  1196. case DPAMM_DELETE:
  1197. case DPAMM_INSERT:
  1198. // Don't need to implement this
  1199. ASSERT(0);
  1200. pvRet = NULL;
  1201. break;
  1202. }
  1203. return pvRet;
  1204. }
  1205. // Header for file menu streams
  1206. typedef struct tagFMSTREAMHEADER
  1207. {
  1208. DWORD cbSize; // Size of header
  1209. DWORD dwVersion; // Version of header
  1210. } FMSTREAMHEADER;
  1211. #define FMSTREAMHEADER_VERSION 1
  1212. typedef struct tagFMSTREAMITEM
  1213. {
  1214. DWORD cbSize; // Size including pidl (not for versioning)
  1215. int nOrder; // User-specified order
  1216. } FMSTREAMITEM;
  1217. #define CB_FMSTREAMITEM (sizeof(FMSTREAMITEM))
  1218. HRESULT
  1219. CALLBACK
  1220. FileMenuItem_SaveStream(DPASTREAMINFO * pinfo, IStream * pstm, LPVOID pvData)
  1221. {
  1222. // We only write menu items with pidls
  1223. PFILEMENUITEM pfmi = (PFILEMENUITEM)pinfo->pvItem;
  1224. HRESULT hres = S_FALSE;
  1225. if (pfmi->pidl)
  1226. {
  1227. FMSTREAMITEM fmsi;
  1228. ULONG cbWrite;
  1229. ULONG cbWritePidl;
  1230. // Size of header, pidl, and ushort for pidl size.
  1231. fmsi.cbSize = CB_FMSTREAMITEM + pfmi->pidl->mkid.cb + sizeof(USHORT);
  1232. fmsi.nOrder = pfmi->nOrder;
  1233. hres = pstm->Write(&fmsi, CB_FMSTREAMITEM, &cbWrite);
  1234. if (SUCCEEDED(hres))
  1235. {
  1236. hres = pstm->Write(pfmi->pidl, pfmi->pidl->mkid.cb + sizeof(USHORT), &cbWritePidl);
  1237. ASSERT(fmsi.cbSize == cbWrite + cbWritePidl);
  1238. }
  1239. }
  1240. return hres;
  1241. }
  1242. HRESULT
  1243. CALLBACK
  1244. FileMenuItem_LoadStream(DPASTREAMINFO * pinfo, IStream * pstm, LPVOID pvData)
  1245. {
  1246. HRESULT hres;
  1247. FMSTREAMITEM fmsi;
  1248. ULONG cbRead;
  1249. PFILEMENUHEADER pfmh = (PFILEMENUHEADER)pvData;
  1250. ASSERT(pfmh);
  1251. hres = pstm->Read(&fmsi, CB_FMSTREAMITEM, &cbRead);
  1252. if (SUCCEEDED(hres))
  1253. {
  1254. if (CB_FMSTREAMITEM != cbRead)
  1255. hres = E_FAIL;
  1256. else
  1257. {
  1258. ASSERT(CB_FMSTREAMITEM < fmsi.cbSize);
  1259. if (CB_FMSTREAMITEM < fmsi.cbSize)
  1260. {
  1261. UINT cb = fmsi.cbSize - CB_FMSTREAMITEM;
  1262. LPITEMIDLIST pidl = _ILCreate(cb);
  1263. if ( !pidl )
  1264. hres = E_OUTOFMEMORY;
  1265. else
  1266. {
  1267. hres = pstm->Read(pidl, cb, &cbRead);
  1268. if (SUCCEEDED(hres) && cb == cbRead &&
  1269. IS_VALID_PIDL(pidl))
  1270. {
  1271. PFILEMENUITEM pfmi;
  1272. if (FileMenuItem_Create(pfmh, pidl, -1, 0, &pfmi))
  1273. {
  1274. pfmi->nOrder = fmsi.nOrder;
  1275. pinfo->pvItem = pfmi;
  1276. hres = S_OK;
  1277. }
  1278. else
  1279. hres = E_OUTOFMEMORY;
  1280. }
  1281. else
  1282. hres = E_FAIL;
  1283. // Cleanup
  1284. if (FAILED(hres))
  1285. ILFree(pidl);
  1286. }
  1287. }
  1288. else
  1289. hres = E_FAIL;
  1290. }
  1291. }
  1292. ASSERT((S_OK == hres && pinfo->pvItem) || FAILED(hres));
  1293. return hres;
  1294. }
  1295. int
  1296. CALLBACK
  1297. FileMenuItem_DestroyCB(LPVOID pv, LPVOID pvData)
  1298. {
  1299. return FileMenuItem_Destroy((PFILEMENUITEM)pv);
  1300. }
  1301. BOOL
  1302. FileList_Load(
  1303. IN PFILEMENUHEADER pfmh,
  1304. OUT HDPA * phdpa,
  1305. IN IStream * pstm)
  1306. {
  1307. HDPA hdpa = NULL;
  1308. FMSTREAMHEADER fmsh;
  1309. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1310. ASSERT(phdpa);
  1311. ASSERT(pstm);
  1312. // Read the header for more info
  1313. if (SUCCEEDED(pstm->Read(&fmsh, sizeof(fmsh), NULL)) &&
  1314. sizeof(fmsh) == fmsh.cbSize &&
  1315. FMSTREAMHEADER_VERSION == fmsh.dwVersion)
  1316. {
  1317. // Load the stream. (Should be ordered by name.)
  1318. DPA_LoadStream(&hdpa, FileMenuItem_LoadStream, pstm, pfmh);
  1319. }
  1320. *phdpa = hdpa;
  1321. return (NULL != hdpa);
  1322. }
  1323. HRESULT
  1324. FileList_Save(
  1325. IN PFILEMENUHEADER pfmh,
  1326. IN IStream * pstm)
  1327. {
  1328. HRESULT hres = E_OUTOFMEMORY;
  1329. FMSTREAMHEADER fmsh;
  1330. HDPA hdpa;
  1331. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1332. ASSERT(pstm);
  1333. // Clone the array and sort by name for the purpose of persisting it
  1334. hdpa = DPA_Clone(pfmh->hdpa, NULL);
  1335. if (hdpa)
  1336. {
  1337. DPA_Sort(hdpa, FileMenuItem_Compare, FS_SORTBYNAME);
  1338. // Save the header
  1339. fmsh.cbSize = sizeof(fmsh);
  1340. fmsh.dwVersion = FMSTREAMHEADER_VERSION;
  1341. hres = pstm->Write(&fmsh, sizeof(fmsh), NULL);
  1342. if (SUCCEEDED(hres))
  1343. {
  1344. hres = DPA_SaveStream(hdpa, FileMenuItem_SaveStream, pstm, pfmh);
  1345. }
  1346. DPA_Destroy(hdpa);
  1347. }
  1348. return hres;
  1349. }
  1350. void FileList_Reorder(PFILEMENUHEADER pfmh)
  1351. {
  1352. int i;
  1353. int cel;
  1354. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1355. BOOL bCantOrder = (NULL == pfmh->pstm);
  1356. // Update the order fields. While we're at it, massage the
  1357. // dwEffect field so it reflects whether something can be
  1358. // ordered based on the stream (no stream means no reorder).
  1359. cel = DPA_GetPtrCount(pfmh->hdpa);
  1360. for (i = 0; i < cel; i++)
  1361. {
  1362. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_FastGetPtr(pfmh->hdpa, i);
  1363. pfmi->nOrder = i;
  1364. if (bCantOrder)
  1365. pfmi->dwEffect = DROPEFFECT_NONE;
  1366. }
  1367. }
  1368. // Caller should release the stream after using it
  1369. BOOL FileList_GetStream(PFILEMENUHEADER pfmh, IStream ** ppstm)
  1370. {
  1371. if (NULL == pfmh->pstm)
  1372. {
  1373. if (pfmh->pfncb)
  1374. {
  1375. FMGETSTREAM fmgs = { 0 };
  1376. FMCBDATA fmcbdata;
  1377. fmcbdata.lParam = pfmh->lParam;
  1378. fmcbdata.hmenu = pfmh->hmenu;
  1379. fmcbdata.idCmd = pfmh->idCmd;
  1380. fmcbdata.iPos = -1;
  1381. fmcbdata.pidlFolder = pfmh->pidlFolder;
  1382. fmcbdata.pidl = NULL;
  1383. fmcbdata.psf = pfmh->psf;
  1384. fmcbdata.pvHeader = pfmh;
  1385. if (S_OK == pfmh->pfncb(FMM_GETSTREAM, &fmcbdata, (LPARAM)&fmgs) &&
  1386. fmgs.pstm)
  1387. {
  1388. // Cache this stream away
  1389. pfmh->pstm = fmgs.pstm;
  1390. }
  1391. }
  1392. }
  1393. else
  1394. {
  1395. // Reset the seek pointer to beginning
  1396. LARGE_INTEGER dlibMove = { 0 };
  1397. pfmh->pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL);
  1398. }
  1399. if (pfmh->pstm)
  1400. pfmh->pstm->AddRef();
  1401. *ppstm = pfmh->pstm;
  1402. return (NULL != *ppstm);
  1403. }
  1404. void
  1405. FileList_Sort(
  1406. PFILEMENUHEADER pfmh)
  1407. {
  1408. IStream * pstm;
  1409. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1410. // First sort by name
  1411. DPA_Sort(pfmh->hdpa, FileMenuItem_Compare, FS_SORTBYNAME);
  1412. // Can this menu be sorted by the user?
  1413. if ((pfmh->fmf & FMF_CANORDER) && FileList_GetStream(pfmh, &pstm))
  1414. {
  1415. // Yes; get the stream and try to load the order info
  1416. HDPA hdpaOrder;
  1417. // Read the order from the stream
  1418. if (FileList_Load(pfmh, &hdpaOrder, pstm))
  1419. {
  1420. // Sort the menu according to this stream's order.
  1421. // The persisted order is by name. This reduces the number of
  1422. // sorts to two at load-time, and 1 at save-time. (Persisting
  1423. // by ordinal number means we sort three times at load-time, and
  1424. // none at save-time. We want to speed up the initial menu
  1425. // creation as much as possible.)
  1426. // (Already sorted by name above)
  1427. DPA_Merge(pfmh->hdpa, hdpaOrder, DPAM_SORTED, FileMenuItem_Compare,
  1428. FileMenuItem_Merge, FS_SORTBYNAME);
  1429. DPA_Sort(pfmh->hdpa, FileMenuItem_Compare, FS_SORTBYORDINAL);
  1430. DPA_DestroyCallback(hdpaOrder, FileMenuItem_DestroyCB, NULL);
  1431. }
  1432. pstm->Release();
  1433. }
  1434. FileList_Reorder(pfmh);
  1435. }
  1436. //---------------------------------------------------------------------------
  1437. // Use the text extent of the given item and the size of the image to work
  1438. // what the full extent of the item will be.
  1439. DWORD GetItemExtent(HDC hdc, PFILEMENUITEM pfmi)
  1440. {
  1441. TCHAR szName[MAX_PATH];
  1442. szName[0] = 0;
  1443. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  1444. FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
  1445. PFILEMENUHEADER pfmh = pfmi->pfmh;
  1446. ASSERT(pfmh);
  1447. // Limit the width of the text?
  1448. if (0 < pfmh->cxMax)
  1449. {
  1450. // Yes
  1451. PathCompactPath(hdc, szName, pfmh->cxMax);
  1452. }
  1453. DWORD dwExtent = GetItemTextExtent(hdc, szName);
  1454. WORD wHeight = HIWORD(dwExtent);
  1455. // If no custom height - calc it.
  1456. if (!pfmi->cyItem)
  1457. {
  1458. if (pfmh->fmf & FMF_LARGEICONS)
  1459. wHeight = max(wHeight, ((WORD)g_cyIcon)) + 2;
  1460. else
  1461. wHeight = max(wHeight, ((WORD)g_cySmIcon)) + pfmh->cySpacing;
  1462. }
  1463. else
  1464. {
  1465. wHeight = max(wHeight, pfmi->cyItem);
  1466. }
  1467. ASSERT(pfmi->pfmh);
  1468. // string, image, gap on either side of image, popup triangle
  1469. // and background bitmap if there is one.
  1470. // FEATURE: popup triangle size needs to be real
  1471. WORD wWidth = LOWORD(dwExtent) + GetSystemMetrics(SM_CXMENUCHECK);
  1472. // Keep track of the width and height of the bitmap.
  1473. if (pfmh->hbmp && !pfmh->cxBmp && !pfmh->cyBmp)
  1474. {
  1475. BITMAP bmp;
  1476. GetObject(pfmh->hbmp, SIZEOF(bmp), &bmp);
  1477. pfmh->cxBmp = bmp.bmWidth;
  1478. pfmh->cyBmp = bmp.bmHeight;
  1479. }
  1480. // Gap for bitmap.
  1481. wWidth += (WORD) pfmh->cxBmpGap;
  1482. // Space for image if there is one.
  1483. // NB We currently always allow room for the image even if there
  1484. // isn't one so that imageless items line up properly.
  1485. if (pfmh->fmf & FMF_LARGEICONS)
  1486. wWidth += g_cxIcon + (2 * CXIMAGEGAP);
  1487. else
  1488. wWidth += g_cxSmIcon + (2 * CXIMAGEGAP);
  1489. return MAKELONG(wWidth, wHeight);
  1490. }
  1491. /*----------------------------------------------------------
  1492. Purpose: Get the PFILEMENUITEM of this menu item
  1493. Returns:
  1494. Cond: --
  1495. */
  1496. PFILEMENUITEM
  1497. FileMenu_GetItemData(
  1498. IN HMENU hmenu,
  1499. IN UINT iItem,
  1500. IN BOOL bByPos)
  1501. {
  1502. MENUITEMINFO mii;
  1503. mii.cbSize = SIZEOF(MENUITEMINFO);
  1504. mii.fMask = MIIM_DATA | MIIM_STATE;
  1505. mii.cch = 0; // just in case
  1506. if (GetMenuItemInfo(hmenu, iItem, bByPos, &mii))
  1507. return (PFILEMENUITEM)mii.dwItemData;
  1508. return NULL;
  1509. }
  1510. PFILEMENUHEADER FileMenu_GetHeader(HMENU hmenu)
  1511. {
  1512. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  1513. if (pfmi &&
  1514. EVAL(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)) &&
  1515. EVAL(IS_VALID_STRUCT_PTR(pfmi->pfmh, FILEMENUHEADER)))
  1516. {
  1517. return pfmi->pfmh;
  1518. }
  1519. return NULL;
  1520. }
  1521. /*----------------------------------------------------------
  1522. Purpose: Create a file menu header. This header is to be associated
  1523. with the given menu handle.
  1524. If the menu handle already has header, simply return the
  1525. existing header.
  1526. Returns: pointer to header
  1527. NULL on failure
  1528. */
  1529. PFILEMENUHEADER
  1530. FileMenuHeader_Create(
  1531. IN HMENU hmenu,
  1532. IN HBITMAP hbmp,
  1533. IN int cxBmpGap,
  1534. IN COLORREF clrBkg,
  1535. IN int cySel,
  1536. IN const FMCOMPOSE * pfmc) OPTIONAL
  1537. {
  1538. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  1539. PFILEMENUHEADER pfmh;
  1540. // Does this guy already have a header?
  1541. if (pfmi)
  1542. {
  1543. // Yes; use it
  1544. pfmh = pfmi->pfmh;
  1545. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1546. }
  1547. else
  1548. {
  1549. // Nope, create one now.
  1550. pfmh = (PFILEMENUHEADER)LocalAlloc(LPTR, SIZEOF(FILEMENUHEADER));
  1551. if (pfmh)
  1552. {
  1553. // Keep track of the header.
  1554. TraceMsg(TF_MENU, "Creating filemenu header for %#08x (%x)", hmenu, pfmh);
  1555. pfmh->hdpa = DPA_Create(0);
  1556. if (pfmh->hdpa == NULL)
  1557. {
  1558. LocalFree((HLOCAL)pfmh);
  1559. pfmh = NULL;
  1560. }
  1561. else
  1562. {
  1563. pfmh->hmenu = hmenu;
  1564. pfmh->hbmp = hbmp;
  1565. pfmh->cxBmpGap = cxBmpGap;
  1566. pfmh->clrBkg = clrBkg;
  1567. pfmh->cySel = cySel;
  1568. pfmh->cySpacing = 6; // default for small icons
  1569. }
  1570. }
  1571. }
  1572. if (pfmc && pfmh)
  1573. {
  1574. // Set additional values
  1575. if (IsFlagSet(pfmc->dwMask, FMC_CALLBACK))
  1576. {
  1577. pfmh->pfncb = pfmc->pfnCallback;
  1578. pfmh->lParam = pfmc->lParam;
  1579. }
  1580. if (IsFlagSet(pfmc->dwMask, FMC_CYMAX))
  1581. pfmh->cyMax = pfmc->cyMax;
  1582. if (IsFlagSet(pfmc->dwMask, FMC_CXMAX))
  1583. pfmh->cxMax = pfmc->cxMax;
  1584. if (IsFlagSet(pfmc->dwMask, FMC_CYSPACING))
  1585. pfmh->cySpacing = pfmc->cySpacing;
  1586. if (IsFlagSet(pfmc->dwMask, FMC_FILTERTYPES))
  1587. {
  1588. // This is a double-null terminated string
  1589. MultiSz_AllocCopy(pfmc->pszFilterTypes, &pfmh->pszFilterTypes);
  1590. }
  1591. }
  1592. return pfmh;
  1593. }
  1594. /*----------------------------------------------------------
  1595. Purpose: Set info specific to a folder.
  1596. Returns:
  1597. Cond: --
  1598. */
  1599. BOOL
  1600. FileMenuHeader_SetFolderInfo(
  1601. IN PFILEMENUHEADER pfmh,
  1602. IN const FMCOMPOSE * pfmc)
  1603. {
  1604. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1605. ASSERT(pfmc);
  1606. // Keep track of the header.
  1607. pfmh->idCmd = pfmc->id;
  1608. if (IsFlagSet(pfmc->dwMask, FMC_FILTER))
  1609. pfmh->fFSFilter = pfmc->dwFSFilter;
  1610. if (IsFlagSet(pfmc->dwMask, FMC_CYMAX))
  1611. pfmh->cyMax = pfmc->cyMax;
  1612. if (IsFlagSet(pfmc->dwMask, FMC_CXMAX))
  1613. pfmh->cxMax = pfmc->cxMax;
  1614. if (IsFlagSet(pfmc->dwMask, FMC_CYSPACING))
  1615. pfmh->cySpacing = pfmc->cySpacing;
  1616. if (IsFlagSet(pfmc->dwMask, FMC_FILTERTYPES))
  1617. MultiSz_AllocCopy(pfmc->pszFilterTypes, &pfmh->pszFilterTypes);
  1618. if (pfmc->pidlFolder)
  1619. {
  1620. pfmh->pidlFolder = ILClone(pfmc->pidlFolder);
  1621. if (pfmh->pidlFolder)
  1622. {
  1623. LPSHELLFOLDER psfDesktop;
  1624. if (SUCCEEDED(SHGetDesktopFolder(&psfDesktop)))
  1625. {
  1626. if (SUCCEEDED(psfDesktop->BindToObject(pfmh->pidlFolder,
  1627. NULL, IID_IShellFolder, (PVOID *)&pfmh->psf)))
  1628. {
  1629. return TRUE;
  1630. }
  1631. }
  1632. ILFree(pfmh->pidlFolder);
  1633. }
  1634. }
  1635. return FALSE;
  1636. }
  1637. /*----------------------------------------------------------
  1638. Purpose: Create the tooltip window
  1639. Returns:
  1640. Cond: --
  1641. */
  1642. BOOL
  1643. FileMenuHeader_CreateTooltipWindow(
  1644. IN PFILEMENUHEADER pfmh)
  1645. {
  1646. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1647. // Check if we need to create the main tooltip window
  1648. if (g_hwndTip)
  1649. {
  1650. if (IsWindow(g_hwndTip))
  1651. {
  1652. TCHAR szClass[MAX_PATH];
  1653. GetClassName(g_hwndTip, szClass, ARRAYSIZE(szClass));
  1654. if (lstrcmpi(szClass, TOOLTIPS_CLASS) != 0)
  1655. g_hwndTip = NULL;
  1656. }
  1657. else
  1658. g_hwndTip = NULL;
  1659. }
  1660. if (!g_hwndTip)
  1661. Tooltip_Create(&g_hwndTip);
  1662. ASSERT(IS_VALID_HANDLE(g_hwndTip, WND));
  1663. return NULL != g_hwndTip;
  1664. }
  1665. //---------------------------------------------------------------------------
  1666. // Give the submenu a marker item so we can check it's a filemenu item
  1667. // at initpopupmenu time.
  1668. BOOL FileMenuHeader_InsertMarkerItem(PFILEMENUHEADER pfmh)
  1669. {
  1670. PFILEMENUITEM pfmi;
  1671. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1672. if (FileMenuItem_Create(pfmh, NULL, -1, FMI_MARKER | FMI_EXPAND, &pfmi))
  1673. {
  1674. DPA_SetPtr(pfmh->hdpa, 0, pfmi);
  1675. FileMenuHeader_InsertItem(pfmh, 0, FMII_DEFAULT);
  1676. return TRUE;
  1677. }
  1678. TraceMsg(TF_ERROR, "FileMenuHeader_InsertMarkerItem: Can't create marker item.");
  1679. return FALSE;
  1680. }
  1681. /*----------------------------------------------------------
  1682. Purpose: This functions adds the given item (index into DPA)
  1683. into the actual menu.
  1684. Returns:
  1685. Cond: --
  1686. */
  1687. BOOL
  1688. FileMenuHeader_InsertItem(
  1689. IN PFILEMENUHEADER pfmh,
  1690. IN UINT iItem,
  1691. IN FMIIFLAGS fFlags)
  1692. {
  1693. PFILEMENUITEM pfmi;
  1694. UINT fMenu;
  1695. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1696. // Normal item.
  1697. pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, iItem);
  1698. if (!pfmi)
  1699. return FALSE;
  1700. if (pfmi->Flags & FMI_ON_MENU)
  1701. return FALSE;
  1702. else
  1703. pfmi->Flags |= FMI_ON_MENU;
  1704. // The normal stuff.
  1705. fMenu = MF_BYPOSITION|MF_OWNERDRAW;
  1706. // Keep track of where it's going in the menu.
  1707. // The special stuff...
  1708. if (fFlags & FMII_BREAK)
  1709. {
  1710. fMenu |= MF_MENUBARBREAK;
  1711. }
  1712. // Is it a folder (that's not open yet)?
  1713. if (pfmi->Flags & FMI_FOLDER)
  1714. {
  1715. // Yep. Create a submenu item.
  1716. HMENU hmenuSub = CreatePopupMenu();
  1717. if (hmenuSub)
  1718. {
  1719. MENUITEMINFO mii;
  1720. LPITEMIDLIST pidlSub;
  1721. PFILEMENUHEADER pfmhSub;
  1722. FMCOMPOSE fmc;
  1723. // Set the callback now so it can be called when adding items
  1724. fmc.cbSize = SIZEOF(fmc);
  1725. fmc.dwMask = FMC_CALLBACK;
  1726. fmc.lParam = pfmh->lParam;
  1727. fmc.pfnCallback = pfmh->pfncb;
  1728. // Insert it into the parent menu.
  1729. fMenu |= MF_POPUP;
  1730. InsertMenu(pfmh->hmenu, iItem, fMenu, (UINT_PTR)hmenuSub, (LPTSTR)pfmi);
  1731. // Set it's ID.
  1732. mii.cbSize = SIZEOF(mii);
  1733. mii.fMask = MIIM_ID;
  1734. mii.wID = pfmh->idCmd;
  1735. SetMenuItemInfo(pfmh->hmenu, iItem, TRUE, &mii);
  1736. pidlSub = ILCombine((pfmi->Flags & FMI_ALTITEM) ? pfmh->pidlAltFolder : pfmh->pidlFolder, pfmi->pidl);
  1737. pfmhSub = FileMenuHeader_Create(hmenuSub, NULL, 0, (COLORREF)-1, 0, &fmc);
  1738. if (pfmhSub)
  1739. {
  1740. // Inherit settings from the parent filemenu
  1741. fmc.dwMask = FMC_PIDL | FMC_FILTER | FMC_CYMAX |
  1742. FMC_CXMAX | FMC_CYSPACING;
  1743. fmc.id = pfmh->idCmd;
  1744. fmc.pidlFolder = pidlSub;
  1745. fmc.dwFSFilter = pfmh->fFSFilter;
  1746. fmc.cyMax = pfmh->cyMax;
  1747. fmc.cxMax = pfmh->cxMax;
  1748. fmc.cySpacing = pfmh->cySpacing;
  1749. if (pfmh->pszFilterTypes)
  1750. {
  1751. fmc.dwMask |= FMC_FILTERTYPES;
  1752. fmc.pszFilterTypes = pfmh->pszFilterTypes;
  1753. }
  1754. FileMenuHeader_SetFolderInfo(pfmhSub, &fmc);
  1755. // Magically inherit certain flags
  1756. // FEATURE: (scotth): can we inherit all the bits?
  1757. pfmhSub->fmf = pfmh->fmf & FMF_INHERITMASK;
  1758. // Build it a bit at a time.
  1759. FileMenuHeader_InsertMarkerItem(pfmhSub);
  1760. }
  1761. ILFree(pidlSub);
  1762. }
  1763. }
  1764. else
  1765. {
  1766. // Nope.
  1767. if (pfmi->Flags & FMI_EMPTY)
  1768. fMenu |= MF_DISABLED | MF_GRAYED;
  1769. InsertMenu(pfmh->hmenu, iItem, fMenu, pfmh->idCmd, (LPTSTR)pfmi);
  1770. }
  1771. return TRUE;
  1772. }
  1773. /*----------------------------------------------------------
  1774. Purpose: Remove the rest of the items from the main list starting
  1775. at the given index.
  1776. Returns: --
  1777. Cond: --
  1778. */
  1779. void
  1780. FileList_StripLeftOvers(
  1781. IN PFILEMENUHEADER pfmh,
  1782. IN int idpaStart,
  1783. IN BOOL bUseAlt)
  1784. {
  1785. int cItems;
  1786. int i;
  1787. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1788. cItems = DPA_GetPtrCount(pfmh->hdpa);
  1789. // Do this backwards to stop things moving around as
  1790. // we delete them.
  1791. for (i = cItems - 1; i >= idpaStart; i--)
  1792. {
  1793. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i);
  1794. if (pfmi)
  1795. {
  1796. // Tell the callback we're removing this
  1797. if (pfmh->pfncb && pfmi->pidl &&
  1798. IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL))
  1799. {
  1800. FMCBDATA fmcbdata;
  1801. fmcbdata.lParam = pfmh->lParam;
  1802. fmcbdata.hmenu = pfmh->hmenu;
  1803. fmcbdata.iPos = i;
  1804. fmcbdata.idCmd = GetMenuItemID(pfmh->hmenu, i);
  1805. if (bUseAlt)
  1806. {
  1807. fmcbdata.pidlFolder = pfmh->pidlAltFolder;
  1808. fmcbdata.psf = pfmh->psfAlt;
  1809. }
  1810. else
  1811. {
  1812. fmcbdata.pidlFolder = pfmh->pidlFolder;
  1813. fmcbdata.psf = pfmh->psf;
  1814. }
  1815. fmcbdata.pidl = pfmi->pidl;
  1816. fmcbdata.pvHeader = pfmh;
  1817. pfmh->pfncb(FMM_REMOVE, &fmcbdata, 0);
  1818. }
  1819. // (We don't need to worry about recursively deleting
  1820. // subfolders because their contents haven't been added yet.)
  1821. // Delete the item itself (note there is no menu item
  1822. // to delete)
  1823. FileMenuItem_Destroy(pfmi);
  1824. DPA_DeletePtr(pfmh->hdpa, i);
  1825. }
  1826. }
  1827. }
  1828. /*----------------------------------------------------------
  1829. Purpose: This function adds a "More Items..." menu item
  1830. at the bottom of the menu. It calls the callback
  1831. to get the string.
  1832. Returns:
  1833. Cond: --
  1834. */
  1835. void
  1836. FileMenuHeader_AddMoreItemsItem(
  1837. IN PFILEMENUHEADER pfmh,
  1838. IN UINT iPos)
  1839. {
  1840. PFILEMENUITEM pfmi;
  1841. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1842. if (NULL == pfmh->pfncb)
  1843. {
  1844. // (scotth): this shouldn't be required, but we don't
  1845. // have a default resource ID for this right now.
  1846. TraceMsg(TF_ERROR, "Need a callback in order to add a More item.");
  1847. ASSERT(0);
  1848. }
  1849. else if (FileMenuItem_Create(pfmh, NULL, -1, 0, &pfmi))
  1850. {
  1851. FMCBDATA fmcbdata;
  1852. FMMORESTRING fmms = {0};
  1853. // Make the pidl be the whole path to the folder
  1854. pfmi->pidl = ILClone(pfmh->pidlFolder);
  1855. pfmi->Flags |= FMI_IGNORE_PIDL;
  1856. fmcbdata.hmenu = pfmh->hmenu;
  1857. fmcbdata.iPos = -1;
  1858. fmcbdata.idCmd = (UINT)-1;
  1859. // (scotth): we don't ask for string for alternate lists
  1860. fmcbdata.pidlFolder = NULL;
  1861. fmcbdata.pidl = pfmi->pidl;
  1862. fmcbdata.psf = pfmh->psf;
  1863. // Was a string set?
  1864. if (S_OK == pfmh->pfncb(FMM_GETMORESTRING, &fmcbdata, (LPARAM)&fmms))
  1865. {
  1866. Sz_AllocCopy(fmms.szMoreString, &(pfmi->psz));
  1867. if (DPA_SetPtr(pfmh->hdpa, iPos, pfmi))
  1868. {
  1869. MENUITEMINFO mii;
  1870. // Set the command ID
  1871. mii.cbSize = SIZEOF(mii);
  1872. mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_DATA;
  1873. mii.wID = fmms.uID;
  1874. mii.fType = MFT_OWNERDRAW;
  1875. mii.dwItemData = (DWORD_PTR)pfmi;
  1876. EVAL(InsertMenuItem(pfmh->hmenu, iPos, TRUE, &mii));
  1877. }
  1878. }
  1879. }
  1880. }
  1881. /*----------------------------------------------------------
  1882. Purpose: Enumerates the DPA and adds each item into the
  1883. menu. Inserts vertical breaks if the menu becomes
  1884. too long.
  1885. Returns: count of items added to menu
  1886. Cond: --
  1887. */
  1888. int
  1889. FileList_AddToMenu(
  1890. IN PFILEMENUHEADER pfmh,
  1891. IN BOOL bUseAlt,
  1892. IN BOOL bAddSeparatorSpace)
  1893. {
  1894. UINT i, cItems;
  1895. int cItemMac = 0;
  1896. PFILEMENUITEM pfmi;
  1897. int cyMenu, cyItem, cyMenuMax;
  1898. HDC hdc;
  1899. HFONT hfont, hfontOld;
  1900. NONCLIENTMETRICS ncm;
  1901. int idpa;
  1902. HDPA hdpaT;
  1903. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  1904. if (bUseAlt)
  1905. hdpaT = pfmh->hdpaAlt;
  1906. else
  1907. hdpaT = pfmh->hdpa;
  1908. if (hdpaT)
  1909. {
  1910. cyItem = 0;
  1911. cyMenu = pfmh->cyMenuSizeSinceLastBreak;
  1912. if (0 < pfmh->cyMax)
  1913. cyMenuMax = pfmh->cyMax;
  1914. else
  1915. cyMenuMax = GetSystemMetrics(SM_CYSCREEN);
  1916. // Get the rough height of an item so we can work out when to break the
  1917. // menu. User should really do this for us but that would be useful.
  1918. hdc = GetDC(NULL);
  1919. if (hdc)
  1920. {
  1921. ncm.cbSize = SIZEOF(NONCLIENTMETRICS);
  1922. if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SIZEOF(ncm), &ncm, FALSE))
  1923. {
  1924. hfont = CreateFontIndirect(&ncm.lfMenuFont);
  1925. if (hfont)
  1926. {
  1927. hfontOld = SelectFont(hdc, hfont);
  1928. cyItem = HIWORD(GetItemExtent(hdc, (PFILEMENUITEM)DPA_GetPtr(hdpaT, 0)));
  1929. SelectObject(hdc, hfontOld);
  1930. DeleteObject(hfont);
  1931. }
  1932. }
  1933. ReleaseDC(NULL, hdc);
  1934. }
  1935. // If we are appending items to a menu, we need to account
  1936. // for the separator.
  1937. if (bAddSeparatorSpace) {
  1938. cyMenu += cyItem;
  1939. }
  1940. cItems = DPA_GetPtrCount(hdpaT);
  1941. for (i = 0; i < cItems; i++)
  1942. {
  1943. if (bUseAlt) {
  1944. // Move the items from the alternate list to the main
  1945. // list and use the new index.
  1946. pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpaAlt, i);
  1947. if (!pfmi)
  1948. continue;
  1949. idpa = DPA_AppendPtr(pfmh->hdpa, pfmi);
  1950. } else {
  1951. idpa = i;
  1952. }
  1953. // Keep a rough count of the height of the menu.
  1954. cyMenu += cyItem;
  1955. if (cyMenu > cyMenuMax)
  1956. {
  1957. // Add a vertical break?
  1958. if ( !(pfmh->fmf & (FMF_NOBREAK | FMF_RESTRICTHEIGHT)) )
  1959. {
  1960. // Yes
  1961. FileMenuHeader_InsertItem(pfmh, idpa, FMII_BREAK);
  1962. cyMenu = cyItem;
  1963. }
  1964. // Restrict height?
  1965. else if (IsFlagSet(pfmh->fmf, FMF_RESTRICTHEIGHT))
  1966. {
  1967. // Yes; remove the remaining items from the list
  1968. FileList_StripLeftOvers(pfmh, idpa, bUseAlt);
  1969. // (so cyMenuSizeSinceLastBreak is accurate)
  1970. cyMenu -= cyItem;
  1971. // Add a "more..." item at the end?
  1972. if (pfmh->fmf & FMF_MOREITEMS)
  1973. {
  1974. // Yes
  1975. FileMenuHeader_AddMoreItemsItem(pfmh, idpa);
  1976. }
  1977. // We won't go any further
  1978. break;
  1979. }
  1980. }
  1981. else
  1982. {
  1983. FileMenuHeader_InsertItem(pfmh, idpa, FMII_DEFAULT);
  1984. cItemMac++;
  1985. }
  1986. }
  1987. // Save the current cy size so we can use this again
  1988. // if more items are appended to this menu.
  1989. pfmh->cyMenuSizeSinceLastBreak = cyMenu;
  1990. }
  1991. return cItemMac;
  1992. }
  1993. BOOL
  1994. FileList_AddImages(
  1995. IN PFILEMENUHEADER pfmh,
  1996. IN BOOL bUseAlt)
  1997. {
  1998. PFILEMENUITEM pfmi;
  1999. int i, cItems;
  2000. HDPA hdpaTemp;
  2001. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2002. if (bUseAlt) {
  2003. hdpaTemp = pfmh->hdpaAlt;
  2004. } else {
  2005. hdpaTemp = pfmh->hdpa;
  2006. }
  2007. cItems = DPA_GetPtrCount(hdpaTemp);
  2008. for (i = 0; i < cItems; i++)
  2009. {
  2010. if (FileMenuHeader_AllowAbort(pfmh) && g_fAbortInitMenu)
  2011. {
  2012. TraceMsg(TF_MENU, "FileList_AddImages: Abort: Defering images till later.");
  2013. break;
  2014. }
  2015. pfmi = (PFILEMENUITEM)DPA_GetPtr(hdpaTemp, i);
  2016. if (pfmi && pfmi->pidl && (pfmi->iImage == -1) &&
  2017. IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL))
  2018. {
  2019. pfmi->iImage = SHMapPIDLToSystemImageListIndex(
  2020. (bUseAlt ? pfmh->psfAlt : pfmh->psf),
  2021. pfmi->pidl, NULL);
  2022. }
  2023. }
  2024. return TRUE;
  2025. }
  2026. //---------------------------------------------------------------------------
  2027. BOOL FileMenuItem_Destroy(PFILEMENUITEM pfmi)
  2028. {
  2029. BOOL fRet = FALSE;
  2030. ASSERT(NULL == pfmi || IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  2031. if (pfmi)
  2032. {
  2033. if (pfmi->pidl)
  2034. ILFree(pfmi->pidl);
  2035. if (pfmi->psz)
  2036. LFree(pfmi->psz);
  2037. if (pfmi->pszTooltip)
  2038. LFree(pfmi->pszTooltip);
  2039. LocalFree(pfmi);
  2040. fRet = TRUE;
  2041. }
  2042. return fRet;
  2043. }
  2044. //---------------------------------------------------------------------------
  2045. // Clean up the items created by FileList_Build;
  2046. void FileList_UnBuild(PFILEMENUHEADER pfmh)
  2047. {
  2048. int cItems;
  2049. int i;
  2050. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2051. cItems = DPA_GetPtrCount(pfmh->hdpa);
  2052. for (i=cItems-1; i>=0; i--)
  2053. {
  2054. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i);
  2055. if (FileMenuItem_Destroy(pfmi))
  2056. DPA_DeletePtr(pfmh->hdpa, i);
  2057. }
  2058. }
  2059. // Flags for FileMenuHeader_AddFiles
  2060. #define FMHAF_USEALT 0x0001
  2061. #define FMHAF_SEPARATOR 0x0002
  2062. //---------------------------------------------------------------------------
  2063. // Add menu items from an IContextMenu handler.
  2064. HRESULT FileMenuHeader_AddFromContextMenu(PFILEMENUHEADER pfmh, HKEY hk)
  2065. {
  2066. // enumerate the key and create each of the context menu handlers, for that
  2067. // we can then add the entries.
  2068. return S_OK;
  2069. }
  2070. /*----------------------------------------------------------
  2071. Purpose: Add files to a file menu header. This function goes thru
  2072. the following steps:
  2073. - enumerates the folder and fills the hdpa list with items
  2074. (files and subfolders)
  2075. - sorts the list
  2076. - gets the images for the items in the list
  2077. - adds the items from list into actual menu
  2078. The last step also (optionally) caps the length of the
  2079. menu to the specified height. Ideally, this should
  2080. happen at the enumeration time, except the required sort
  2081. prevents this from happening. So we end up adding a
  2082. bunch of items to the list and then removing them if
  2083. there are too many.
  2084. Returns: count of items added
  2085. -1 if aborted
  2086. Cond: --
  2087. */
  2088. HRESULT
  2089. FileMenuHeader_AddFiles(
  2090. IN PFILEMENUHEADER pfmh,
  2091. IN int iPos,
  2092. IN UINT uFlags, // FMHAF_*
  2093. OUT int * pcItems)
  2094. {
  2095. HRESULT hres;
  2096. BOOL bUseAlt = IsFlagSet(uFlags, FMHAF_USEALT);
  2097. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2098. int cItems = FileList_Build(pfmh, iPos, bUseAlt);
  2099. // If the build was aborted cleanup and early out.
  2100. if (FileMenuHeader_AllowAbort(pfmh) && g_fAbortInitMenu)
  2101. {
  2102. // Cleanup.
  2103. TraceMsg(TF_MENU, "FileList_Build aborted.");
  2104. FileList_UnBuild(pfmh);
  2105. hres = E_ABORT;
  2106. *pcItems = -1;
  2107. }
  2108. else
  2109. {
  2110. *pcItems = cItems;
  2111. if (cItems > 1)
  2112. FileList_Sort(pfmh);
  2113. if (cItems != 0)
  2114. {
  2115. BOOL bSeparator = IsFlagSet(uFlags, FMHAF_SEPARATOR);
  2116. if (bSeparator)
  2117. {
  2118. // insert a line
  2119. FileMenu_AppendItem(pfmh->hmenu, (LPTSTR)FMAI_SEPARATOR, 0, -1, NULL, 0);
  2120. }
  2121. // Add the images *after* adding to the menu, since the menu
  2122. // may be capped to a maximum height, and we can then prevent
  2123. // adding images we won't need.
  2124. *pcItems = FileList_AddToMenu(pfmh, bUseAlt, bSeparator);
  2125. FileList_AddImages(pfmh, bUseAlt);
  2126. }
  2127. hres = (*pcItems < cItems) ? S_FALSE : S_OK;
  2128. }
  2129. if (g_fAbortInitMenu)
  2130. g_fAbortInitMenu = FALSE;
  2131. TraceMsg(TF_MENU, "FileMenuHeader_AddFiles: Added %d filemenu items.", cItems);
  2132. return hres;
  2133. }
  2134. //----------------------------------------------------------------------------
  2135. // Free up a header (you should delete all the items first).
  2136. void FileMenuHeader_Destroy(PFILEMENUHEADER pfmh)
  2137. {
  2138. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2139. TraceMsg(TF_MENU, "Destroy filemenu for (%x)", pfmh);
  2140. // Clean up the header.
  2141. DPA_Destroy(pfmh->hdpa);
  2142. if (pfmh->pidlFolder)
  2143. {
  2144. ILFree(pfmh->pidlFolder);
  2145. pfmh->pidlFolder = NULL;
  2146. }
  2147. if (pfmh->psf)
  2148. {
  2149. pfmh->psf->Release();
  2150. pfmh->psf = NULL;
  2151. }
  2152. if (pfmh->pstm)
  2153. {
  2154. pfmh->pstm->Release();
  2155. pfmh->pstm = NULL;
  2156. }
  2157. if (pfmh->pidlAltFolder)
  2158. {
  2159. ILFree(pfmh->pidlAltFolder);
  2160. pfmh->pidlAltFolder = NULL;
  2161. }
  2162. if (pfmh->psfAlt)
  2163. {
  2164. pfmh->psfAlt->Release();
  2165. pfmh->psfAlt = NULL;
  2166. }
  2167. if (pfmh->pszFilterTypes)
  2168. {
  2169. LFree(pfmh->pszFilterTypes);
  2170. pfmh->pszFilterTypes = NULL;
  2171. }
  2172. LocalFree((HLOCAL)pfmh); // needed?
  2173. }
  2174. //---------------------------------------------------------------------------
  2175. // We create subemnu's with one marker item so we can check it's a file menu
  2176. // at init popup time but we need to delete it before adding new items.
  2177. BOOL FileMenuHeader_DeleteMarkerItem(PFILEMENUHEADER pfmh)
  2178. {
  2179. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2180. // It should just be the only one in the menu.
  2181. if (GetMenuItemCount(pfmh->hmenu) == 1)
  2182. {
  2183. // It should have the right id.
  2184. if (GetMenuItemID(pfmh->hmenu, 0) == pfmh->idCmd)
  2185. {
  2186. // With item data and the marker flag set.
  2187. PFILEMENUITEM pfmi = FileMenu_GetItemData(pfmh->hmenu, 0, TRUE);
  2188. if (pfmi && (pfmi->Flags & FMI_MARKER))
  2189. {
  2190. // Delete it.
  2191. ASSERT(pfmh->hdpa);
  2192. ASSERT(DPA_GetPtrCount(pfmh->hdpa) == 1);
  2193. // NB The marker shouldn't have a pidl.
  2194. ASSERT(!pfmi->pidl);
  2195. LocalFree((HLOCAL)pfmi);
  2196. DPA_DeletePtr(pfmh->hdpa, 0);
  2197. DeleteMenu(pfmh->hmenu, 0, MF_BYPOSITION);
  2198. // Cleanup OK.
  2199. return TRUE;
  2200. }
  2201. }
  2202. }
  2203. TraceMsg(TF_MENU, "Can't find marker item.");
  2204. return FALSE;
  2205. }
  2206. /*----------------------------------------------------------
  2207. Purpose: Add files to this menu.
  2208. Returns: number of items added
  2209. Cond: --
  2210. */
  2211. HRESULT
  2212. FileMenu_AddFiles(
  2213. IN HMENU hmenu,
  2214. IN UINT iPos,
  2215. IN OUT FMCOMPOSE * pfmc)
  2216. {
  2217. HRESULT hres = E_OUTOFMEMORY;
  2218. BOOL fMarker = FALSE;
  2219. PFILEMENUHEADER pfmh;
  2220. // NOTE: this function takes in FMCOMPOSE, which can be A or W
  2221. // version depending on the platform. Since the function
  2222. // is internal, wrapped by FileMenu_ComposeA/W, it expects
  2223. // the pidl to be valid, and will not use the pszFolder field.
  2224. if (IsFlagClear(pfmc->dwMask, FMC_FILTER))
  2225. pfmc->dwFSFilter = 0;
  2226. if (IsFlagClear(pfmc->dwMask, FMC_FLAGS))
  2227. pfmc->dwFlags = 0;
  2228. // (FileMenuHeader_Create might return an existing header)
  2229. pfmh = FileMenuHeader_Create(hmenu, NULL, 0, (COLORREF)-1, 0, pfmc);
  2230. if (pfmh)
  2231. {
  2232. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  2233. if (pfmi)
  2234. {
  2235. // Clean up marker item if there is one.
  2236. if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND))
  2237. {
  2238. // Nope, do it now.
  2239. TraceMsg(TF_MENU, "Removing marker item.");
  2240. FileMenuHeader_DeleteMarkerItem(pfmh);
  2241. fMarker = TRUE;
  2242. if (iPos)
  2243. iPos--;
  2244. }
  2245. }
  2246. // Add the new stuff
  2247. FileMenuHeader_SetFolderInfo(pfmh, pfmc);
  2248. // Tack on more flags
  2249. pfmh->fmf |= pfmc->dwFlags;
  2250. SetFlag(pfmh->fmf, FMF_NOABORT);
  2251. hres = FileMenuHeader_AddFiles(pfmh, iPos, 0, &pfmc->cItems);
  2252. ClearFlag(pfmh->fmf, FMF_NOABORT);
  2253. if ((E_ABORT == hres || 0 == pfmc->cItems) && fMarker)
  2254. {
  2255. // Aborted or no items. Put the marker back (if there used
  2256. // to be one).
  2257. FileMenuHeader_InsertMarkerItem(pfmh);
  2258. }
  2259. }
  2260. return hres;
  2261. }
  2262. //---------------------------------------------------------------------------
  2263. // Returns the number of items added.
  2264. STDAPI_(UINT)
  2265. FileMenu_AppendFilesForPidl(
  2266. HMENU hmenu,
  2267. LPITEMIDLIST pidl,
  2268. BOOL bInsertSeparator)
  2269. {
  2270. int cItems = 0;
  2271. BOOL fMarker = FALSE;
  2272. PFILEMENUHEADER pfmh;
  2273. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  2274. ASSERT(IS_VALID_HANDLE(hmenu, MENU));
  2275. ASSERT(IS_VALID_PIDL(pidl));
  2276. //
  2277. // Get the filemenu header from the first filemenu item
  2278. //
  2279. if (!pfmi)
  2280. return 0;
  2281. pfmh = pfmi->pfmh;
  2282. if (pfmh)
  2283. {
  2284. // Clean up marker item if there is one.
  2285. if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND))
  2286. {
  2287. // Nope, do it now.
  2288. // TraceMsg(DM_TRACE, "t.fm_ii: Removing marker item.");
  2289. FileMenuHeader_DeleteMarkerItem(pfmh);
  2290. fMarker = TRUE;
  2291. }
  2292. // Add the new stuff.
  2293. if (pidl)
  2294. {
  2295. LPSHELLFOLDER psfDesktop;
  2296. if (SUCCEEDED(SHGetDesktopFolder(&psfDesktop)))
  2297. {
  2298. pfmh->pidlAltFolder = ILClone(pidl);
  2299. if (pfmh->pidlAltFolder) {
  2300. pfmh->hdpaAlt = DPA_Create(0);
  2301. if (pfmh->hdpaAlt) {
  2302. if (SUCCEEDED(psfDesktop->BindToObject(pfmh->pidlAltFolder,
  2303. NULL, IID_IShellFolder, (LPVOID *)&pfmh->psfAlt)))
  2304. {
  2305. UINT uFlags = FMHAF_USEALT;
  2306. if (bInsertSeparator)
  2307. uFlags |= FMHAF_SEPARATOR;
  2308. pfmh->fmf |= FMF_NOABORT;
  2309. FileMenuHeader_AddFiles(pfmh, 0, uFlags, &cItems);
  2310. pfmh->fmf = pfmh->fmf & ~FMF_NOABORT;
  2311. }
  2312. DPA_Destroy (pfmh->hdpaAlt);
  2313. pfmh->hdpaAlt = NULL;
  2314. }
  2315. }
  2316. // we assume this is a static object... which it is.
  2317. // psfDesktop->Release();
  2318. }
  2319. }
  2320. if (cItems <= 0 && fMarker)
  2321. {
  2322. // Aborted or no item s. Put the marker back (if there used
  2323. // to be one).
  2324. FileMenuHeader_InsertMarkerItem(pfmh);
  2325. }
  2326. }
  2327. return cItems;
  2328. }
  2329. //---------------------------------------------------------------------------
  2330. // Delete all the menu items listed in the given header.
  2331. UINT
  2332. FileMenuHeader_DeleteAllItems(
  2333. IN PFILEMENUHEADER pfmh)
  2334. {
  2335. int i;
  2336. int cItems = 0;
  2337. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2338. if (IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER))
  2339. {
  2340. // Notify.
  2341. if (pfmh->pfncb)
  2342. {
  2343. FMCBDATA fmcbdata;
  2344. fmcbdata.lParam = pfmh->lParam;
  2345. fmcbdata.hmenu = pfmh->hmenu;
  2346. fmcbdata.iPos = 0;
  2347. fmcbdata.idCmd = (UINT)-1;
  2348. fmcbdata.pidlFolder = pfmh->pidlFolder;
  2349. fmcbdata.pidl = NULL;
  2350. fmcbdata.psf = pfmh->psf;
  2351. fmcbdata.pvHeader = pfmh;
  2352. pfmh->pfncb(FMM_DELETEALL, &fmcbdata, 0);
  2353. }
  2354. // Clean up the items.
  2355. cItems = DPA_GetPtrCount(pfmh->hdpa);
  2356. // Do this backwards to stop things moving around as
  2357. // we delete them.
  2358. for (i = cItems - 1; i >= 0; i--)
  2359. {
  2360. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i);
  2361. if (pfmi)
  2362. {
  2363. // Does this item have a subfolder?
  2364. if (pfmi->Flags & FMI_FOLDER)
  2365. {
  2366. // Yep.
  2367. // Get the submenu for this item.
  2368. // Delete all it's items.
  2369. FileMenu_DeleteAllItems(GetSubMenu(pfmh->hmenu, i));
  2370. }
  2371. // Delete the item itself.
  2372. DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION);
  2373. FileMenuItem_Destroy(pfmi);
  2374. DPA_DeletePtr(pfmh->hdpa, i);
  2375. }
  2376. }
  2377. }
  2378. return cItems;
  2379. }
  2380. //---------------------------------------------------------------------------
  2381. // NB The creator of the filemenu has to explicitly call FileMenu_DAI to free
  2382. // up FileMenu items because USER doesn't send WM_DELETEITEM for ownerdraw
  2383. // menu. Great eh?
  2384. // Returns the number of items deleted.
  2385. UINT FileMenu_DeleteAllItems(HMENU hmenu)
  2386. {
  2387. PFILEMENUHEADER pfmh;
  2388. if (!IsMenu(hmenu))
  2389. return 0;
  2390. // need to set this guy back to NULL, since it's no longer valid after
  2391. // we delete the menu items.
  2392. g_pfmiLastSelNonFolder = NULL;
  2393. pfmh = FileMenu_GetHeader(hmenu);
  2394. if (pfmh)
  2395. {
  2396. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2397. // Save the order if necessary
  2398. if (IsFlagSet(pfmh->fmf, FMF_DIRTY | FMF_CANORDER))
  2399. {
  2400. FileMenu_SaveOrder(pfmh->hmenu);
  2401. ClearFlag(pfmh->fmf, FMF_DIRTY);
  2402. }
  2403. UINT cItems = FileMenuHeader_DeleteAllItems(pfmh);
  2404. FileMenuHeader_Destroy(pfmh);
  2405. return cItems;
  2406. }
  2407. return 0;
  2408. }
  2409. //---------------------------------------------------------------------------
  2410. STDAPI_(void)
  2411. FileMenu_Destroy(HMENU hmenu)
  2412. {
  2413. TraceMsg(TF_MENU, "Destroying filemenu for %#08x", hmenu);
  2414. FileMenu_DeleteAllItems(hmenu);
  2415. DestroyMenu(hmenu);
  2416. // Reset the menu tracking agent
  2417. g_fsmenuagent.Reset();
  2418. //
  2419. // Delete current global g_hdcMem and g_hfont so they'll be
  2420. // refreshed with current font metrics next time the menu size
  2421. // is calculated. This is needed in case the menu is being destroyed
  2422. // as part of a system metrics change.
  2423. //
  2424. DeleteGlobalMemDCAndFont();
  2425. }
  2426. //---------------------------------------------------------------------------
  2427. // Cause the given filemenu to be rebuilt.
  2428. STDAPI_(void)
  2429. FileMenu_Invalidate(HMENU hmenu)
  2430. {
  2431. ASSERT(IS_VALID_HANDLE(hmenu, MENU));
  2432. // Is this a filemenu?
  2433. // NB First menu item must be a FileMenuItem.
  2434. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  2435. if (pfmi)
  2436. {
  2437. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  2438. // Yep, Is there already a marker here?
  2439. if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND))
  2440. {
  2441. TraceMsg(TF_MENU, "Menu is already invalid.");
  2442. }
  2443. else if (pfmi->pfmh)
  2444. {
  2445. PFILEMENUHEADER pfmhSave = pfmi->pfmh;
  2446. FileMenuHeader_DeleteAllItems(pfmi->pfmh);
  2447. ASSERT(IS_VALID_STRUCT_PTR(pfmhSave, FILEMENUHEADER));
  2448. // above call freed pfmi
  2449. FileMenuHeader_InsertMarkerItem(pfmhSave);
  2450. }
  2451. }
  2452. }
  2453. //---------------------------------------------------------------------------
  2454. // Cause the given filemenu to be marked invalid but don't delete any items
  2455. // yet.
  2456. void FileMenu_DelayedInvalidate(HMENU hmenu)
  2457. {
  2458. // Is this a filemenu?
  2459. // NB First menu item must be a FileMenuItem.
  2460. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  2461. if (pfmi && pfmi->pfmh)
  2462. SetFlag(pfmi->pfmh->fmf, FMF_DELAY_INVALID);
  2463. }
  2464. BOOL FileMenu_IsDelayedInvalid(HMENU hmenu)
  2465. {
  2466. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  2467. return (pfmi && pfmi->pfmh &&
  2468. IsFlagSet(pfmi->pfmh->fmf, FMF_DELAY_INVALID));
  2469. }
  2470. /*----------------------------------------------------------
  2471. Purpose: Compose a file menu.
  2472. Ansi version
  2473. Returns: S_OK if all the files were added
  2474. S_FALSE if some did not get added (reached cyMax)
  2475. error on something bad
  2476. Cond: --
  2477. */
  2478. STDAPI
  2479. FileMenu_ComposeA(
  2480. IN HMENU hmenu,
  2481. IN UINT nMethod,
  2482. IN FMCOMPOSEA * pfmc)
  2483. {
  2484. HRESULT hres = E_INVALIDARG;
  2485. if (IS_VALID_WRITE_PTR(pfmc, FMCOMPOSEA) &&
  2486. SIZEOF(*pfmc) == pfmc->cbSize)
  2487. {
  2488. FMCOMPOSEA fmc;
  2489. fmc = *pfmc;
  2490. if (IsFlagSet(fmc.dwMask, FMC_STRING))
  2491. {
  2492. // Convert string to pidl
  2493. TCHAR szFolder[MAX_PATH];
  2494. #ifdef UNICODE
  2495. MultiByteToWideChar(CP_ACP, 0, fmc.pszFolder, -1, szFolder,
  2496. SIZECHARS(szFolder));
  2497. #else
  2498. lstrcpy(szFolder, fmc.pszFolder);
  2499. #endif
  2500. fmc.pidlFolder = ILCreateFromPath(szFolder);
  2501. if (NULL == fmc.pidlFolder)
  2502. {
  2503. hres = E_OUTOFMEMORY;
  2504. goto Bail;
  2505. }
  2506. }
  2507. else if (IsFlagClear(fmc.dwMask, FMC_PIDL))
  2508. {
  2509. // Either FMC_PIDL or FMC_STRING must be set
  2510. hres = E_INVALIDARG;
  2511. goto Bail;
  2512. }
  2513. switch (nMethod)
  2514. {
  2515. case FMCM_INSERT:
  2516. hres = FileMenu_AddFiles(hmenu, 0, (FMCOMPOSE *)&fmc);
  2517. break;
  2518. case FMCM_APPEND:
  2519. hres = FileMenu_AddFiles(hmenu, GetMenuItemCount(hmenu),
  2520. (FMCOMPOSE *)&fmc);
  2521. break;
  2522. case FMCM_REPLACE:
  2523. FileMenu_DeleteAllItems(hmenu);
  2524. hres = FileMenu_AddFiles(hmenu, 0, (FMCOMPOSE *)&fmc);
  2525. break;
  2526. default:
  2527. ASSERT(0);
  2528. goto Bail;
  2529. }
  2530. pfmc->cItems = fmc.cItems;
  2531. Bail:
  2532. // Cleanup
  2533. if (IsFlagSet(fmc.dwMask, FMC_STRING) && fmc.pidlFolder)
  2534. ILFree(fmc.pidlFolder);
  2535. }
  2536. return hres;
  2537. }
  2538. /*----------------------------------------------------------
  2539. Purpose: Compose a file menu.
  2540. Unicode version
  2541. Returns:
  2542. Cond: --
  2543. */
  2544. STDAPI
  2545. FileMenu_ComposeW(
  2546. IN HMENU hmenu,
  2547. IN UINT nMethod,
  2548. IN FMCOMPOSEW * pfmc)
  2549. {
  2550. HRESULT hres = E_INVALIDARG;
  2551. if (IS_VALID_WRITE_PTR(pfmc, FMCOMPOSEW) &&
  2552. SIZEOF(*pfmc) == pfmc->cbSize)
  2553. {
  2554. FMCOMPOSEW fmc;
  2555. fmc = *pfmc;
  2556. if (IsFlagSet(fmc.dwMask, FMC_STRING))
  2557. {
  2558. // Convert string to pidl
  2559. TCHAR szFolder[MAX_PATH];
  2560. #ifdef UNICODE
  2561. lstrcpy(szFolder, fmc.pszFolder);
  2562. #else
  2563. WideCharToMultiByte(CP_ACP, 0, fmc.pszFolder, -1, szFolder,
  2564. SIZECHARS(szFolder), NULL, NULL);
  2565. #endif
  2566. fmc.pidlFolder = ILCreateFromPath(szFolder);
  2567. if (NULL == fmc.pidlFolder)
  2568. {
  2569. hres = E_OUTOFMEMORY;
  2570. goto Bail;
  2571. }
  2572. }
  2573. else if (IsFlagClear(fmc.dwMask, FMC_PIDL))
  2574. {
  2575. // Either FMC_PIDL or FMC_STRING must be set
  2576. hres = E_INVALIDARG;
  2577. goto Bail;
  2578. }
  2579. switch (nMethod)
  2580. {
  2581. case FMCM_INSERT:
  2582. hres = FileMenu_AddFiles(hmenu, 0, (FMCOMPOSE *)&fmc);
  2583. break;
  2584. case FMCM_APPEND:
  2585. hres = FileMenu_AddFiles(hmenu, GetMenuItemCount(hmenu),
  2586. (FMCOMPOSE *)&fmc);
  2587. break;
  2588. case FMCM_REPLACE:
  2589. FileMenu_DeleteAllItems(hmenu);
  2590. hres = FileMenu_AddFiles(hmenu, 0, (FMCOMPOSE *)&fmc);
  2591. break;
  2592. default:
  2593. ASSERT(0);
  2594. goto Bail;
  2595. }
  2596. pfmc->cItems = fmc.cItems;
  2597. Bail:
  2598. // Cleanup
  2599. if (IsFlagSet(fmc.dwMask, FMC_STRING) && fmc.pidlFolder)
  2600. ILFree(fmc.pidlFolder);
  2601. }
  2602. return hres;
  2603. }
  2604. LRESULT FileMenu_DrawItem(HWND hwnd, DRAWITEMSTRUCT *pdi)
  2605. {
  2606. int y, x;
  2607. TCHAR szName[MAX_PATH];
  2608. DWORD dwExtent;
  2609. int cxIcon, cyIcon;
  2610. RECT rcBkg;
  2611. HBRUSH hbrOld = NULL;
  2612. UINT cyItem, dyItem;
  2613. HIMAGELIST himl;
  2614. RECT rcClip;
  2615. if ((pdi->itemAction & ODA_SELECT) || (pdi->itemAction & ODA_DRAWENTIRE))
  2616. {
  2617. PFILEMENUHEADER pfmh;
  2618. PFILEMENUITEM pfmi = (PFILEMENUITEM)pdi->itemData;
  2619. IShellFolder * psf;
  2620. #ifndef UNIX
  2621. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  2622. #endif
  2623. if (!pfmi)
  2624. {
  2625. TraceMsg(TF_ERROR, "FileMenu_DrawItem: Filemenu is invalid (no item data).");
  2626. return FALSE;
  2627. }
  2628. pfmh = pfmi->pfmh;
  2629. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2630. if (pfmi->Flags & FMI_ALTITEM)
  2631. psf = pfmh->psfAlt;
  2632. else
  2633. psf = pfmh->psf;
  2634. // Adjust for large/small icons.
  2635. if (pfmh->fmf & FMF_LARGEICONS)
  2636. {
  2637. cxIcon = g_cxIcon;
  2638. cyIcon = g_cyIcon;
  2639. }
  2640. else
  2641. {
  2642. cxIcon = g_cxSmIcon;
  2643. cyIcon = g_cxSmIcon;
  2644. }
  2645. // Is the menu just starting to get drawn?
  2646. if (pdi->itemAction & ODA_DRAWENTIRE)
  2647. {
  2648. if (pfmi == DPA_GetPtr(pfmh->hdpa, 0))
  2649. {
  2650. // Yes; reset the last selection item
  2651. g_pfmiLastSelNonFolder = NULL;
  2652. g_pfmiLastSel = NULL;
  2653. // Initialize to handle drag and drop?
  2654. if (pfmh->fmf & FMF_CANORDER)
  2655. {
  2656. // Yes
  2657. g_fsmenuagent.Init();
  2658. }
  2659. }
  2660. }
  2661. if (pdi->itemState & ODS_SELECTED)
  2662. {
  2663. if (pfmh->fmf & FMF_CANORDER)
  2664. {
  2665. // Pass on the current hDC and selection rect so the
  2666. // drag/drop hook can actively draw
  2667. RECT rc = pdi->rcItem;
  2668. hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUTEXT));
  2669. // With no background image, the caret goes all the way
  2670. // across; otherwise it stops in line with the bitmap.
  2671. if (pfmh->hbmp)
  2672. rc.left += pfmh->cxBmpGap;
  2673. g_fsmenuagent.SetCurrentRect(pdi->hDC, &rc);
  2674. g_fsmenuagent.SetItem(pfmi);
  2675. // Are we in edit mode?
  2676. if (MenuDD_IsButtonDown())
  2677. {
  2678. // Yes
  2679. g_fsmenuagent.SetEditMode(TRUE, DROPEFFECT_MOVE);
  2680. }
  2681. }
  2682. // Determine the selection colors
  2683. //
  2684. // Normal menu colors apply until we are in edit mode, in which
  2685. // case the menu item is drawn unselected and an insertion caret
  2686. // is drawn above or below the current item. The exception is
  2687. // if the item is a cascaded menu item, then we draw it
  2688. // normally, but also show the insertion caret. (We do this
  2689. // because Office does this, and also, USER draws the arrow
  2690. // in the selected color always, so it looks kind of funny
  2691. // if we don't select the menu item.)
  2692. //
  2693. // Is the user dragging and dropping and we're not over
  2694. // a cascaded menu item?
  2695. if ((pfmh->fmf & FMF_CANORDER) && MenuDD_InEditMode() &&
  2696. !(pfmi->Flags & FMI_FOLDER))
  2697. {
  2698. // Yes; show the item in the unselected colors
  2699. // (dwRop = SRCAND)
  2700. hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUTEXT));
  2701. }
  2702. else
  2703. {
  2704. // No
  2705. SetBkColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHT));
  2706. SetTextColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
  2707. hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_HIGHLIGHTTEXT));
  2708. }
  2709. // REVIEW HACK NB - keep track of the last selected item.
  2710. // NB The keyboard handler needs to know about all selections
  2711. // but the WM_COMMAND stuff only cares about non-folders.
  2712. g_pfmiLastSel = pfmi;
  2713. if (!(pfmi->Flags & FMI_FOLDER))
  2714. g_pfmiLastSelNonFolder = pfmi;
  2715. // Get the rect of the item in screen coords.
  2716. g_rcItem = pdi->rcItem;
  2717. MapWindowPoints(WindowFromDC(pdi->hDC), NULL, (LPPOINT)&g_rcItem, 2);
  2718. }
  2719. else
  2720. {
  2721. // dwRop = SRCAND;
  2722. hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUTEXT));
  2723. }
  2724. // Initial start pos.
  2725. x = pdi->rcItem.left+CXIMAGEGAP;
  2726. // Draw the background image.
  2727. if (pfmh->hbmp)
  2728. {
  2729. // Draw it the first time the first item paints.
  2730. if (pfmi == DPA_GetPtr(pfmh->hdpa, 0) &&
  2731. (pdi->itemAction & ODA_DRAWENTIRE))
  2732. {
  2733. if (!g_hdcMem)
  2734. {
  2735. g_hdcMem = CreateCompatibleDC(pdi->hDC);
  2736. ASSERT(g_hdcMem);
  2737. }
  2738. if (g_hdcMem)
  2739. {
  2740. HBITMAP hbmOld;
  2741. if (!pfmh->yBmp)
  2742. {
  2743. GetClipBox(pdi->hDC, &rcClip);
  2744. pfmh->yBmp = rcClip.bottom;
  2745. }
  2746. hbmOld = SelectBitmap(g_hdcMem, pfmh->hbmp);
  2747. BitBlt(pdi->hDC, 0, pfmh->yBmp-pfmh->cyBmp, pfmh->cxBmp, pfmh->cyBmp, g_hdcMem, 0, 0, SRCCOPY);
  2748. SelectBitmap(g_hdcMem, hbmOld);
  2749. }
  2750. }
  2751. x += pfmh->cxBmpGap;
  2752. }
  2753. // Background color for when the bitmap runs out.
  2754. if ((pfmh->clrBkg != (COLORREF)-1) &&
  2755. (pfmi == DPA_GetPtr(pfmh->hdpa, 0)) &&
  2756. (pdi->itemAction & ODA_DRAWENTIRE))
  2757. {
  2758. HBRUSH hbr;
  2759. if (!pfmh->yBmp)
  2760. {
  2761. GetClipBox(pdi->hDC, &rcClip);
  2762. pfmh->yBmp = rcClip.bottom;
  2763. }
  2764. rcBkg.top = 0;
  2765. rcBkg.left = 0;
  2766. rcBkg.bottom = pfmh->yBmp - pfmh->cyBmp;
  2767. rcBkg.right = max(pfmh->cxBmp, pfmh->cxBmpGap);
  2768. hbr = CreateSolidBrush(pfmh->clrBkg);
  2769. if (hbr)
  2770. {
  2771. FillRect(pdi->hDC, &rcBkg, hbr);
  2772. DeleteObject(hbr);
  2773. }
  2774. }
  2775. // Special case the separator.
  2776. if (pfmi->Flags & FMI_SEPARATOR)
  2777. {
  2778. // With no background image it goes all the way across otherwise
  2779. // it stops in line with the bitmap.
  2780. if (pfmh->hbmp)
  2781. pdi->rcItem.left += pfmh->cxBmpGap;
  2782. pdi->rcItem.bottom = (pdi->rcItem.top+pdi->rcItem.bottom)/2;
  2783. DrawEdge(pdi->hDC, &pdi->rcItem, EDGE_ETCHED, BF_BOTTOM);
  2784. // Early out.
  2785. goto ExitProc;
  2786. }
  2787. // Have the selection not include the icon to speed up drawing while
  2788. // tracking.
  2789. pdi->rcItem.left += pfmh->cxBmpGap;
  2790. // Get the name.
  2791. FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
  2792. // Limit the width of the text?
  2793. if (0 < pfmh->cxMax)
  2794. {
  2795. // Yes
  2796. PathCompactPath(pdi->hDC, szName, pfmh->cxMax);
  2797. }
  2798. // NB Keep a plain copy of the name for testing and accessibility.
  2799. if (!pfmi->psz)
  2800. Sz_AllocCopy(szName, &(pfmi->psz));
  2801. dwExtent = GetItemTextExtent(pdi->hDC, szName);
  2802. y = (pdi->rcItem.bottom+pdi->rcItem.top-HIWORD(dwExtent))/2;
  2803. // Support custom heights for the selection rectangle.
  2804. if (pfmh->cySel)
  2805. {
  2806. cyItem = pdi->rcItem.bottom-pdi->rcItem.top;
  2807. // Is there room?
  2808. if ((cyItem > pfmh->cySel) && (pfmh->cySel > HIWORD(dwExtent)))
  2809. {
  2810. dyItem = (cyItem-pfmh->cySel)/2;
  2811. pdi->rcItem.top += dyItem ;
  2812. pdi->rcItem.bottom -= dyItem;
  2813. }
  2814. }
  2815. else if(!(pfmh->fmf & FMF_LARGEICONS))
  2816. {
  2817. // Shrink the selection rect for small icons a bit.
  2818. pdi->rcItem.top += 1;
  2819. pdi->rcItem.bottom -= 1;
  2820. }
  2821. // Draw the text.
  2822. int fDSFlags;
  2823. if (pfmi->Flags & FMI_IGNORE_PIDL)
  2824. {
  2825. //
  2826. // If the string is not coming from a pidl,
  2827. // we can format the menu text.
  2828. //
  2829. fDSFlags = DST_PREFIXTEXT;
  2830. }
  2831. else if ((pfmi->Flags & FMI_ON_MENU) == 0)
  2832. {
  2833. //
  2834. // Norton Desktop Navigator 95 replaces the Start->&Run
  2835. // menu item with a &Run pidl. Even though the text is
  2836. // from a pidl, we still want to format the "&R" correctly.
  2837. //
  2838. fDSFlags = DST_PREFIXTEXT;
  2839. }
  2840. else
  2841. {
  2842. //
  2843. // All other strings coming from pidls are displayed
  2844. // as is to preserve any & in their display name.
  2845. //
  2846. fDSFlags = DST_TEXT;
  2847. }
  2848. if ((pfmi->Flags & FMI_EMPTY) || (pfmi->Flags & FMI_DISABLED))
  2849. {
  2850. if (pdi->itemState & ODS_SELECTED)
  2851. {
  2852. if (GetSysColor(COLOR_GRAYTEXT) == GetSysColor(COLOR_HIGHLIGHTTEXT))
  2853. {
  2854. fDSFlags |= DSS_UNION;
  2855. }
  2856. else
  2857. {
  2858. SetTextColor(pdi->hDC, GetSysColor(COLOR_GRAYTEXT));
  2859. }
  2860. }
  2861. else
  2862. {
  2863. fDSFlags |= DSS_DISABLED;
  2864. }
  2865. ExtTextOut(pdi->hDC, 0, 0, ETO_OPAQUE, &pdi->rcItem, NULL, 0, NULL);
  2866. DrawState(pdi->hDC, NULL, NULL, (LONG_PTR)szName, lstrlen(szName), x+cxIcon+CXIMAGEGAP,
  2867. y, 0, 0, fDSFlags);
  2868. }
  2869. else
  2870. {
  2871. ExtTextOut(pdi->hDC, x+cxIcon+CXIMAGEGAP, y, ETO_OPAQUE, &pdi->rcItem, NULL,
  2872. 0, NULL);
  2873. DrawState(pdi->hDC, NULL, NULL, (LONG_PTR)szName, lstrlen(szName), x+cxIcon+CXIMAGEGAP,
  2874. y, 0, 0, fDSFlags);
  2875. }
  2876. // Get the image if it needs it,
  2877. if ((pfmi->iImage == -1) && pfmi->pidl && psf &&
  2878. IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL))
  2879. {
  2880. pfmi->iImage = SHMapPIDLToSystemImageListIndex(psf, pfmi->pidl, NULL);
  2881. }
  2882. // Draw the image (if there is one).
  2883. if (pfmi->iImage != -1)
  2884. {
  2885. int nDC = 0;
  2886. // Try to center image.
  2887. y = (pdi->rcItem.bottom+pdi->rcItem.top-cyIcon)/2;
  2888. if (pfmh->fmf & FMF_LARGEICONS)
  2889. {
  2890. himl = g_himlIcons;
  2891. // Handle minor drawing glitches that can occur with large icons.
  2892. if ((pdi->itemState & ODS_SELECTED) && (y < pdi->rcItem.top))
  2893. {
  2894. nDC = SaveDC(pdi->hDC);
  2895. IntersectClipRect(pdi->hDC, pdi->rcItem.left, pdi->rcItem.top,
  2896. pdi->rcItem.right, pdi->rcItem.bottom);
  2897. }
  2898. }
  2899. else
  2900. {
  2901. himl = g_himlIconsSmall;
  2902. }
  2903. ImageList_DrawEx(himl, pfmi->iImage, pdi->hDC, x, y, 0, 0,
  2904. GetBkColor(pdi->hDC), CLR_NONE, ILD_NORMAL);
  2905. // Restore the clip rect if we were doing custom clipping.
  2906. if (nDC)
  2907. RestoreDC(pdi->hDC, nDC);
  2908. }
  2909. // Is the user dragging and dropping onto an item that accepts
  2910. // a drop?
  2911. if ((pfmh->fmf & FMF_CANORDER) &&
  2912. (pdi->itemState & ODS_SELECTED) &&
  2913. MenuDD_InEditMode() &&
  2914. (pfmi->dwEffect & g_fsmenuagent.GetDragEffect()))
  2915. {
  2916. // Yes; draw the insertion caret
  2917. RECT rc = pdi->rcItem;
  2918. POINT pt;
  2919. // We actively draw the insertion caret on mouse moves.
  2920. // When the cursor moves between menu items, the msg hook
  2921. // does not get a mouse move until after this paint. But
  2922. // we need to update the caret position correctly, so do
  2923. // it here too.
  2924. GetCursorPos(&pt);
  2925. g_fsmenuagent.SetCaretPos(&pt);
  2926. rc.left += 4;
  2927. rc.right -= 8;
  2928. TraceMsg(TF_MENU, "MenuDD: showing caret %s", MenuDD_InsertAbove() ? TEXT("above") : TEXT("below"));
  2929. if (MenuDD_InsertAbove())
  2930. {
  2931. // Hide any existing caret
  2932. HBRUSH hbrSav = SelectBrush(pdi->hDC, MenuDD_GetBrush());
  2933. PatBlt(pdi->hDC, rc.left, pdi->rcItem.bottom - 2, (rc.right - rc.left), 2, PATCOPY);
  2934. SelectBrush(pdi->hDC, hbrSav);
  2935. // Show caret in new position
  2936. PatBlt(pdi->hDC, rc.left, pdi->rcItem.top, (rc.right - rc.left), 2, BLACKNESS);
  2937. }
  2938. else
  2939. {
  2940. // Hide any existing caret
  2941. HBRUSH hbrSav = SelectBrush(pdi->hDC, MenuDD_GetBrush());
  2942. PatBlt(pdi->hDC, rc.left, pdi->rcItem.top, (rc.right - rc.left), 2, PATCOPY);
  2943. SelectBrush(pdi->hDC, hbrSav);
  2944. // Show caret in new position
  2945. PatBlt(pdi->hDC, rc.left, pdi->rcItem.bottom - 2, (rc.right - rc.left), 2, BLACKNESS);
  2946. }
  2947. }
  2948. }
  2949. ExitProc:
  2950. // Cleanup.
  2951. if (hbrOld)
  2952. SelectObject(pdi->hDC, hbrOld);
  2953. return TRUE;
  2954. }
  2955. DWORD FileMenuItem_GetExtent(PFILEMENUITEM pfmi)
  2956. {
  2957. DWORD dwExtent = 0;
  2958. if (pfmi)
  2959. {
  2960. if (pfmi->Flags & FMI_SEPARATOR)
  2961. {
  2962. dwExtent = MAKELONG(0, GetSystemMetrics(SM_CYMENUSIZE)/2);
  2963. }
  2964. else
  2965. {
  2966. PFILEMENUHEADER pfmh = pfmi->pfmh;
  2967. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  2968. if (!g_hdcMem)
  2969. {
  2970. g_hdcMem = CreateCompatibleDC(NULL);
  2971. ASSERT(g_hdcMem);
  2972. }
  2973. if (g_hdcMem)
  2974. {
  2975. // Get the rough height of an item so we can work out when to break the
  2976. // menu. User should really do this for us but that would be useful.
  2977. if (!g_hfont)
  2978. {
  2979. NONCLIENTMETRICS ncm;
  2980. ncm.cbSize = SIZEOF(ncm);
  2981. if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SIZEOF(ncm), &ncm, FALSE))
  2982. {
  2983. g_hfont = CreateFontIndirect(&ncm.lfMenuFont);
  2984. ASSERT(g_hfont);
  2985. }
  2986. }
  2987. if (g_hfont)
  2988. {
  2989. HFONT hfontOld = SelectFont(g_hdcMem, g_hfont);
  2990. dwExtent = GetItemExtent(g_hdcMem, pfmi);
  2991. SelectFont(g_hdcMem, hfontOld);
  2992. // NB We hang on to the font, it'll get stomped by
  2993. // FM_TPME on the way out.
  2994. }
  2995. // NB We hang on to the DC, it'll get stomped by FM_TPME on the way out.
  2996. }
  2997. }
  2998. }
  2999. else
  3000. {
  3001. TraceMsg(TF_ERROR, "FileMenu_GetExtent: Filemenu is invalid.");
  3002. }
  3003. return dwExtent;
  3004. }
  3005. LRESULT FileMenu_MeasureItem(HWND hwnd, MEASUREITEMSTRUCT *lpmi)
  3006. {
  3007. DWORD dwExtent = FileMenuItem_GetExtent((PFILEMENUITEM)lpmi->itemData);
  3008. lpmi->itemHeight = HIWORD(dwExtent);
  3009. lpmi->itemWidth = LOWORD(dwExtent);
  3010. return TRUE;
  3011. }
  3012. STDAPI_(DWORD)
  3013. FileMenu_GetItemExtent(HMENU hmenu, UINT iItem)
  3014. {
  3015. DWORD dwRet = 0;
  3016. PFILEMENUHEADER pfmh = FileMenu_GetHeader(hmenu);
  3017. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  3018. if (pfmh)
  3019. dwRet = FileMenuItem_GetExtent((PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, iItem));
  3020. return dwRet;
  3021. }
  3022. //----------------------------------------------------------------------------
  3023. STDAPI_(HMENU)
  3024. FileMenu_FindSubMenuByPidl(HMENU hmenu, LPITEMIDLIST pidlFS)
  3025. {
  3026. PFILEMENUHEADER pfmh;
  3027. int i;
  3028. if (!pidlFS)
  3029. {
  3030. ASSERT(0);
  3031. return NULL;
  3032. }
  3033. if (ILIsEmpty(pidlFS))
  3034. return hmenu;
  3035. pfmh = FileMenu_GetHeader(hmenu);
  3036. if (pfmh)
  3037. {
  3038. int cItems = DPA_GetPtrCount(pfmh->hdpa);
  3039. for (i = cItems - 1 ; i >= 0; i--)
  3040. {
  3041. // HACK: We directly call this FS function to compare two pidls.
  3042. // For all items, see if it's the one we're looking for.
  3043. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i);
  3044. if (pfmi && pfmi->pidl && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL) &&
  3045. 0 == pfmh->psf->CompareIDs(0, pidlFS, pfmi->pidl))
  3046. {
  3047. HMENU hmenuSub;
  3048. if ((pfmi->Flags & FMI_FOLDER) &&
  3049. (NULL != (hmenuSub = GetSubMenu(hmenu, i))))
  3050. {
  3051. // recurse to find the next sub menu
  3052. return FileMenu_FindSubMenuByPidl(hmenuSub, (LPITEMIDLIST)ILGetNext(pidlFS));
  3053. }
  3054. else
  3055. {
  3056. ASSERT(0); // we're in trouble.
  3057. break;
  3058. }
  3059. }
  3060. }
  3061. }
  3062. return NULL;
  3063. }
  3064. /*----------------------------------------------------------
  3065. Purpose: Fills the given filemenu with contents of the appropriate
  3066. directory.
  3067. Returns: S_OK if all the files were added
  3068. S_FALSE if some did not get added (reached cyMax)
  3069. error on something bad
  3070. Cond: --
  3071. */
  3072. STDAPI
  3073. FileMenu_InitMenuPopupEx(
  3074. IN HMENU hmenu,
  3075. IN OUT PFMDATA pfmdata)
  3076. {
  3077. HRESULT hres = E_INVALIDARG;
  3078. PFILEMENUITEM pfmi;
  3079. PFILEMENUHEADER pfmh;
  3080. ASSERT(IS_VALID_HANDLE(hmenu, MENU));
  3081. if (IS_VALID_WRITE_PTR(pfmdata, FMDATA) &&
  3082. SIZEOF(*pfmdata) == pfmdata->cbSize)
  3083. {
  3084. hres = E_FAIL; // assume error
  3085. g_fAbortInitMenu = FALSE;
  3086. // Is this a filemenu?
  3087. pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  3088. if (pfmi)
  3089. {
  3090. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  3091. pfmh = pfmi->pfmh;
  3092. if (pfmh)
  3093. {
  3094. // Yes
  3095. if (IsFlagSet(pfmh->fmf, FMF_DELAY_INVALID))
  3096. {
  3097. FileMenu_Invalidate(hmenu);
  3098. ClearFlag(pfmh->fmf, FMF_DELAY_INVALID);
  3099. }
  3100. // (scotth): this can return S_OK but not
  3101. // set the cItems field if this menu has already
  3102. // been filled out.
  3103. hres = S_OK;
  3104. // Have we already filled this thing out?
  3105. if (IsFlagSet(pfmi->Flags, FMI_MARKER | FMI_EXPAND))
  3106. {
  3107. // No, do it now. Get the previously init'ed header.
  3108. FileMenuHeader_DeleteMarkerItem(pfmh);
  3109. // Fill it full of stuff.
  3110. hres = FileMenuHeader_AddFiles(pfmh, 0, 0, &pfmdata->cItems);
  3111. if (E_ABORT == hres)
  3112. {
  3113. // Aborted - put the marker back.
  3114. FileMenuHeader_InsertMarkerItem(pfmh);
  3115. }
  3116. else if (pfmh->pidlAltFolder)
  3117. {
  3118. pfmh->hdpaAlt = DPA_Create(0);
  3119. if (pfmh->hdpaAlt)
  3120. {
  3121. int cItems;
  3122. if (E_ABORT == FileMenuHeader_AddFiles(pfmh, 0,
  3123. FMHAF_SEPARATOR | FMHAF_USEALT,
  3124. &cItems))
  3125. {
  3126. // Aborted - put the marker back.
  3127. FileMenuHeader_InsertMarkerItem(pfmh);
  3128. }
  3129. DPA_Destroy (pfmh->hdpaAlt);
  3130. pfmh->hdpaAlt = NULL;
  3131. }
  3132. }
  3133. }
  3134. }
  3135. }
  3136. }
  3137. return hres;
  3138. }
  3139. /*----------------------------------------------------------
  3140. Purpose: Fills the given filemenu with contents of the appropriate
  3141. directory.
  3142. Returns: FALSE if the given menu isn't a filemenu
  3143. Cond: --
  3144. */
  3145. STDAPI_(BOOL)
  3146. FileMenu_InitMenuPopup(
  3147. IN HMENU hmenu)
  3148. {
  3149. FMDATA fmdata = {SIZEOF(fmdata)}; // zero init everything else
  3150. return SUCCEEDED(FileMenu_InitMenuPopupEx(hmenu, &fmdata));
  3151. }
  3152. BOOL FileMenu_IsUnexpanded(HMENU hmenu)
  3153. {
  3154. BOOL fRet = FALSE;
  3155. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  3156. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  3157. if (pfmi)
  3158. {
  3159. if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND))
  3160. {
  3161. fRet = TRUE;
  3162. }
  3163. }
  3164. return fRet;
  3165. }
  3166. //---------------------------------------------------------------------------
  3167. // This sets whether to load all the images while creating the menu or to
  3168. // defer it until the menu is actually being drawn.
  3169. STDAPI_(void)
  3170. FileMenu_AbortInitMenu(void)
  3171. {
  3172. g_fAbortInitMenu = TRUE;
  3173. }
  3174. /*----------------------------------------------------------
  3175. Purpose: Returns a clone of the last selected pidl
  3176. Returns:
  3177. Cond: --
  3178. */
  3179. STDAPI_(BOOL)
  3180. FileMenu_GetLastSelectedItemPidls(
  3181. IN HMENU hmenu,
  3182. OUT LPITEMIDLIST * ppidlFolder, OPTIONAL
  3183. OUT LPITEMIDLIST * ppidlItem) OPTIONAL
  3184. {
  3185. BOOL bRet = FALSE;
  3186. LPITEMIDLIST pidlFolder = NULL;
  3187. LPITEMIDLIST pidlItem = NULL;
  3188. // FEATURE (scotth): this global should be moved into the
  3189. // instance data of the header.
  3190. if (g_pfmiLastSelNonFolder)
  3191. {
  3192. // Get to the header.
  3193. PFILEMENUHEADER pfmh = g_pfmiLastSelNonFolder->pfmh;
  3194. if (pfmh)
  3195. {
  3196. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  3197. bRet = TRUE;
  3198. if (ppidlFolder)
  3199. {
  3200. if (g_pfmiLastSelNonFolder->Flags & FMI_ALTITEM)
  3201. pidlFolder = ILClone(pfmh->pidlAltFolder);
  3202. else
  3203. pidlFolder = ILClone(pfmh->pidlFolder);
  3204. bRet = (NULL != pidlFolder);
  3205. }
  3206. if (bRet && ppidlItem)
  3207. {
  3208. if (g_pfmiLastSelNonFolder->pidl)
  3209. {
  3210. pidlItem = ILClone(g_pfmiLastSelNonFolder->pidl);
  3211. bRet = (NULL != pidlItem);
  3212. }
  3213. else
  3214. bRet = FALSE;
  3215. }
  3216. if (!bRet)
  3217. {
  3218. if (pidlFolder)
  3219. {
  3220. // Failed; free the pidl we just allocated
  3221. ILFree(pidlFolder);
  3222. pidlFolder = NULL;
  3223. }
  3224. }
  3225. }
  3226. }
  3227. // Init because callers get lazy and don't pay attention to the return
  3228. // value.
  3229. if (ppidlFolder)
  3230. *ppidlFolder = pidlFolder;
  3231. if (ppidlItem)
  3232. *ppidlItem = pidlItem;
  3233. if (!bRet)
  3234. TraceMsg(TF_WARNING, "No previously selected item.");
  3235. return bRet;
  3236. }
  3237. /*----------------------------------------------------------
  3238. Purpose: Returns the command ID and hmenu of the last selected
  3239. menu item. The given hmenuRoot is the parent hmenu
  3240. that must be a FileMenu.
  3241. Returns: S_OK
  3242. S_FALSE if there was no last selected item
  3243. Cond: --
  3244. */
  3245. STDAPI
  3246. FileMenu_GetLastSelectedItem(
  3247. IN HMENU hmenu,
  3248. OUT HMENU * phmenu, OPTIONAL
  3249. OUT UINT * puItem) OPTIONAL
  3250. {
  3251. HRESULT hres = S_FALSE;
  3252. if (phmenu)
  3253. *phmenu = NULL;
  3254. if (puItem)
  3255. *puItem = 0;
  3256. if (g_pfmiLastSelNonFolder)
  3257. {
  3258. // Get to the header.
  3259. PFILEMENUHEADER pfmh = g_pfmiLastSelNonFolder->pfmh;
  3260. if (pfmh)
  3261. {
  3262. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  3263. if (phmenu)
  3264. *phmenu = pfmh->hmenu;
  3265. if (puItem)
  3266. {
  3267. // (scotth): this isn't stored right now
  3268. ASSERT(0);
  3269. }
  3270. hres = S_OK;
  3271. }
  3272. }
  3273. return hres;
  3274. }
  3275. int FileMenuHeader_LastSelIndex(PFILEMENUHEADER pfmh)
  3276. {
  3277. int i;
  3278. PFILEMENUITEM pfmi;
  3279. for (i = GetMenuItemCount(pfmh->hmenu)-1;i >= 0; i--)
  3280. {
  3281. pfmi = FileMenu_GetItemData(pfmh->hmenu, i, TRUE);
  3282. if (pfmi && (pfmi == g_pfmiLastSel))
  3283. return i;
  3284. }
  3285. return -1;
  3286. }
  3287. //---------------------------------------------------------------------------
  3288. // If the string contains &ch or begins with ch then return TRUE.
  3289. BOOL _MenuCharMatch(LPCTSTR lpsz, TCHAR ch, BOOL fIgnoreAmpersand)
  3290. {
  3291. LPTSTR pchAS;
  3292. // Find the first ampersand.
  3293. pchAS = StrChr(lpsz, TEXT('&'));
  3294. if (pchAS && !fIgnoreAmpersand)
  3295. {
  3296. // Yep, is the next char the one we want.
  3297. if (CharUpperChar(*CharNext(pchAS)) == CharUpperChar(ch))
  3298. {
  3299. // Yep.
  3300. return TRUE;
  3301. }
  3302. }
  3303. else if (CharUpperChar(*lpsz) == CharUpperChar(ch))
  3304. {
  3305. return TRUE;
  3306. }
  3307. return FALSE;
  3308. }
  3309. STDAPI_(LRESULT)
  3310. FileMenu_HandleMenuChar(HMENU hmenu, TCHAR ch)
  3311. {
  3312. UINT iItem, cItems, iStep;
  3313. PFILEMENUITEM pfmi;
  3314. int iFoundOne;
  3315. TCHAR szName[MAX_PATH];
  3316. PFILEMENUHEADER pfmh;
  3317. iFoundOne = -1;
  3318. iStep = 0;
  3319. iItem = 0;
  3320. cItems = GetMenuItemCount(hmenu);
  3321. // Start from the last place we looked from.
  3322. pfmh = FileMenu_GetHeader(hmenu);
  3323. if (pfmh)
  3324. {
  3325. iItem = FileMenuHeader_LastSelIndex(pfmh) + 1;
  3326. if (iItem >= cItems)
  3327. iItem = 0;
  3328. }
  3329. while (iStep < cItems)
  3330. {
  3331. pfmi = FileMenu_GetItemData(hmenu, iItem, TRUE);
  3332. if (pfmi)
  3333. {
  3334. BOOL bIgnoreAmpersand = (pfmi->pidl && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL));
  3335. FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
  3336. if (_MenuCharMatch(szName, ch, bIgnoreAmpersand))
  3337. {
  3338. // Found (another) match.
  3339. if (iFoundOne != -1)
  3340. {
  3341. // More than one, select the first.
  3342. return MAKELRESULT(iFoundOne, MNC_SELECT);
  3343. }
  3344. else
  3345. {
  3346. // Found at least one.
  3347. iFoundOne = iItem;
  3348. }
  3349. }
  3350. }
  3351. iItem++;
  3352. iStep++;
  3353. // Wrap.
  3354. if (iItem >= cItems)
  3355. iItem = 0;
  3356. }
  3357. // Did we find one?
  3358. if (iFoundOne != -1)
  3359. {
  3360. // Just in case the user types ahead without the selection being drawn.
  3361. pfmi = FileMenu_GetItemData(hmenu, iFoundOne, TRUE);
  3362. if (!(pfmi->Flags & FMI_FOLDER))
  3363. g_pfmiLastSelNonFolder = pfmi;
  3364. return MAKELRESULT(iFoundOne, MNC_EXECUTE);
  3365. }
  3366. else
  3367. {
  3368. // Didn't find it.
  3369. return MAKELRESULT(0, MNC_IGNORE);
  3370. }
  3371. }
  3372. /*----------------------------------------------------------
  3373. Purpose: Create a filemenu from a given normal menu
  3374. Returns:
  3375. Cond: --
  3376. */
  3377. STDAPI_(BOOL)
  3378. FileMenu_CreateFromMenu(
  3379. IN HMENU hmenu,
  3380. IN COLORREF clr,
  3381. IN int cxBmpGap,
  3382. IN HBITMAP hbmp,
  3383. IN int cySel,
  3384. IN DWORD fmf)
  3385. {
  3386. BOOL fRet = FALSE;
  3387. if (hmenu)
  3388. {
  3389. PFILEMENUHEADER pfmh = FileMenuHeader_Create(hmenu, hbmp, cxBmpGap, clr, cySel, NULL);
  3390. if (!g_himlIcons || !g_himlIconsSmall)
  3391. Shell_GetImageLists(&g_himlIcons, &g_himlIconsSmall);
  3392. if (pfmh)
  3393. {
  3394. // Default flags.
  3395. pfmh->fmf = fmf;
  3396. if (FileMenuHeader_InsertMarkerItem(pfmh))
  3397. fRet = TRUE;
  3398. else
  3399. {
  3400. // REARCHITECT: (scotth): FileMenuHeader_Create can return a pointer
  3401. // that is already stored in a filemenu item, in which case this
  3402. // destroy will stomp a data structure.
  3403. TraceMsg(TF_ERROR, "Can't create file menu.");
  3404. FileMenuHeader_Destroy(pfmh);
  3405. }
  3406. }
  3407. }
  3408. else
  3409. {
  3410. TraceMsg(TF_ERROR, "Menu is null.");
  3411. }
  3412. return fRet;
  3413. }
  3414. HMENU FileMenu_Create(COLORREF clr, int cxBmpGap, HBITMAP hbmp, int cySel, DWORD fmf)
  3415. {
  3416. HMENU hmenuRet = NULL;
  3417. HMENU hmenu = CreatePopupMenu();
  3418. if (hmenu)
  3419. {
  3420. if (FileMenu_CreateFromMenu(hmenu, clr, cxBmpGap, hbmp, cySel, fmf))
  3421. hmenuRet = hmenu;
  3422. else
  3423. DestroyMenu(hmenu);
  3424. }
  3425. return hmenuRet;
  3426. }
  3427. /*----------------------------------------------------------
  3428. Purpose: Insert a generic item into a filemenu
  3429. Returns:
  3430. Cond: --
  3431. */
  3432. STDAPI
  3433. FileMenu_InsertItemEx(
  3434. IN HMENU hmenu,
  3435. IN UINT iPos,
  3436. IN FMITEM const * pfmitem)
  3437. {
  3438. HRESULT hres = E_INVALIDARG;
  3439. PFILEMENUITEM pfmi;
  3440. FMITEM fmitem;
  3441. // Is this a filemenu?
  3442. pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  3443. if (IsValidFMItem(pfmitem, &fmitem) && pfmi)
  3444. {
  3445. // Yes
  3446. PFILEMENUHEADER pfmh = pfmi->pfmh;
  3447. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  3448. // Have we cleaned up the marker item?
  3449. if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND))
  3450. {
  3451. // Nope, do it now.
  3452. FileMenuHeader_DeleteMarkerItem(pfmh);
  3453. }
  3454. hres = E_OUTOFMEMORY;
  3455. // Add the new item.
  3456. if (FileMenuItem_Create(pfmh, NULL, fmitem.iImage, 0, &pfmi))
  3457. {
  3458. if (fmitem.pvData && IsFlagSet(fmitem.dwType, FMIT_STRING))
  3459. {
  3460. if (!Sz_AllocCopy((LPTSTR)fmitem.pvData, &(pfmi->psz)))
  3461. TraceMsg(TF_ERROR, "Unable to allocate menu item text.");
  3462. pfmi->Flags |= FMI_IGNORE_PIDL;
  3463. }
  3464. pfmi->cyItem = fmitem.cyItem;
  3465. pfmi->lParam = fmitem.lParam;
  3466. DPA_InsertPtr(pfmh->hdpa, iPos, pfmi);
  3467. if (IsFlagSet(fmitem.dwType, FMIT_SEPARATOR))
  3468. {
  3469. // Override the setting made above, since separator and
  3470. // text are mutually exclusive
  3471. pfmi->Flags = FMI_SEPARATOR;
  3472. InsertMenu(hmenu, iPos, MF_BYPOSITION|MF_OWNERDRAW|MF_DISABLED|MF_SEPARATOR,
  3473. fmitem.uID, (LPTSTR)pfmi);
  3474. }
  3475. else if (fmitem.hmenuSub)
  3476. {
  3477. MENUITEMINFO mii;
  3478. pfmi->Flags |= FMI_FOLDER;
  3479. if ((iPos == 0xffff) || (iPos == 0xffffffff))
  3480. iPos = GetMenuItemCount(pfmh->hmenu);
  3481. InsertMenu(pfmh->hmenu, iPos, MF_BYPOSITION|MF_OWNERDRAW|MF_POPUP,
  3482. (UINT_PTR)fmitem.hmenuSub, (LPTSTR)pfmi);
  3483. // Set it's ID.
  3484. mii.cbSize = SIZEOF(mii);
  3485. mii.fMask = MIIM_ID;
  3486. // mii.wID = pfmh->idCmd;
  3487. mii.wID = fmitem.uID;
  3488. SetMenuItemInfo(pfmh->hmenu, iPos, TRUE, &mii);
  3489. }
  3490. else
  3491. {
  3492. InsertMenu(hmenu, iPos, MF_BYPOSITION|MF_OWNERDRAW,
  3493. fmitem.uID, (LPTSTR)pfmi);
  3494. }
  3495. hres = S_OK;
  3496. }
  3497. }
  3498. return hres;
  3499. }
  3500. /*----------------------------------------------------------
  3501. Purpose: Old function to insert a generic item onto a filemenu
  3502. Returns:
  3503. Cond: --
  3504. */
  3505. STDAPI_(BOOL)
  3506. FileMenu_InsertItem(
  3507. IN HMENU hmenu,
  3508. IN LPTSTR psz,
  3509. IN UINT id,
  3510. IN int iImage,
  3511. IN HMENU hmenuSub,
  3512. IN UINT cyItem,
  3513. IN UINT iPos)
  3514. {
  3515. FMITEM fmitem;
  3516. fmitem.cbSize = SIZEOF(fmitem);
  3517. fmitem.dwMask = FMI_TYPE | FMI_ID | FMI_IMAGE | FMI_HMENU |
  3518. FMI_METRICS;
  3519. if ((LPTSTR)FMAI_SEPARATOR == psz)
  3520. {
  3521. fmitem.dwType = FMIT_SEPARATOR;
  3522. }
  3523. else if (NULL == psz)
  3524. {
  3525. fmitem.dwType = 0;
  3526. }
  3527. else
  3528. {
  3529. fmitem.dwType = FMIT_STRING;
  3530. #ifdef UNICODE
  3531. fmitem.dwType |= FMIT_UNICODE;
  3532. #endif
  3533. fmitem.dwMask |= FMI_DATA;
  3534. fmitem.pvData = psz;
  3535. }
  3536. fmitem.uID = id;
  3537. fmitem.iImage = iImage;
  3538. fmitem.hmenuSub = hmenuSub;
  3539. fmitem.cyItem = cyItem;
  3540. return SUCCEEDED(FileMenu_InsertItemEx(hmenu, iPos, &fmitem));
  3541. }
  3542. /*----------------------------------------------------------
  3543. Purpose: Get info about this filemenu item.
  3544. */
  3545. STDAPI
  3546. FileMenu_GetItemInfo(
  3547. IN HMENU hmenu,
  3548. IN UINT uItem,
  3549. IN BOOL bByPos,
  3550. OUT PFMITEM pfmitem)
  3551. {
  3552. HRESULT hres = E_INVALIDARG;
  3553. if (IS_VALID_WRITE_PTR(pfmitem, FMITEM) &&
  3554. SIZEOF(*pfmitem) == pfmitem->cbSize)
  3555. {
  3556. PFILEMENUITEM pfmi;
  3557. hres = E_FAIL;
  3558. pfmi = FileMenu_GetItemData(hmenu, uItem, bByPos);
  3559. if (pfmi)
  3560. {
  3561. // (scotth): we don't fill in all the fields
  3562. if (IsFlagSet(pfmitem->dwMask, FMI_LPARAM))
  3563. pfmitem->lParam = pfmi->lParam;
  3564. hres = S_OK;
  3565. }
  3566. }
  3567. return hres;
  3568. }
  3569. /*----------------------------------------------------------
  3570. Purpose: Save the order of the menu to the given stream.
  3571. */
  3572. STDAPI
  3573. FileMenu_SaveOrder(HMENU hmenu)
  3574. {
  3575. HRESULT hres = E_FAIL;
  3576. PFILEMENUITEM pfmi;
  3577. IStream * pstm;
  3578. pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
  3579. if (pfmi && FileList_GetStream(pfmi->pfmh, &pstm))
  3580. {
  3581. hres = FileList_Save(pfmi->pfmh, pstm);
  3582. pstm->Release();
  3583. }
  3584. return hres;
  3585. }
  3586. STDAPI_(BOOL)
  3587. FileMenu_AppendItem(HMENU hmenu, LPTSTR psz, UINT id, int iImage,
  3588. HMENU hmenuSub, UINT cyItem)
  3589. {
  3590. return FileMenu_InsertItem(hmenu, psz, id, iImage, hmenuSub, cyItem, 0xffff);
  3591. }
  3592. STDAPI_(BOOL)
  3593. FileMenu_TrackPopupMenuEx(HMENU hmenu, UINT Flags, int x, int y,
  3594. HWND hwndOwner, LPTPMPARAMS lpTpm)
  3595. {
  3596. BOOL fRet = TrackPopupMenuEx(hmenu, Flags, x, y, hwndOwner, lpTpm);
  3597. // Cleanup.
  3598. DeleteGlobalMemDCAndFont();
  3599. return fRet;
  3600. }
  3601. //----------------------------------------------------------------------------
  3602. // Like Users only this works on submenu's too.
  3603. // NB Returns 0 for seperators.
  3604. UINT FileMenu_GetMenuItemID(HMENU hmenu, UINT iItem)
  3605. {
  3606. MENUITEMINFO mii;
  3607. mii.cbSize = SIZEOF(MENUITEMINFO);
  3608. mii.fMask = MIIM_ID;
  3609. mii.cch = 0; // just in case
  3610. if (GetMenuItemInfo(hmenu, iItem, TRUE, &mii))
  3611. return mii.wID;
  3612. return 0;
  3613. }
  3614. PFILEMENUITEM _FindItemByCmd(PFILEMENUHEADER pfmh, UINT id, int *piPos)
  3615. {
  3616. if (pfmh)
  3617. {
  3618. int cItems, i;
  3619. cItems = DPA_GetPtrCount(pfmh->hdpa);
  3620. for (i = 0; i < cItems; i++)
  3621. {
  3622. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i);
  3623. if (pfmi)
  3624. {
  3625. // Is this the right item?
  3626. // NB This ignores menu items.
  3627. if (id == GetMenuItemID(pfmh->hmenu, i))
  3628. {
  3629. // Yep.
  3630. if (piPos)
  3631. *piPos = i;
  3632. return pfmi;
  3633. }
  3634. }
  3635. }
  3636. }
  3637. return NULL;
  3638. }
  3639. PFILEMENUITEM _FindMenuOrItemByCmd(PFILEMENUHEADER pfmh, UINT id, int *piPos)
  3640. {
  3641. if (pfmh)
  3642. {
  3643. int cItems, i;
  3644. cItems = DPA_GetPtrCount(pfmh->hdpa);
  3645. for (i = 0; i < cItems; i++)
  3646. {
  3647. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i);
  3648. if (pfmi)
  3649. {
  3650. // Is this the right item?
  3651. // NB This includes menu items.
  3652. if (id == FileMenu_GetMenuItemID(pfmh->hmenu, i))
  3653. {
  3654. // Yep.
  3655. if (piPos)
  3656. *piPos = i;
  3657. return pfmi;
  3658. }
  3659. }
  3660. }
  3661. }
  3662. return NULL;
  3663. }
  3664. //----------------------------------------------------------------------------
  3665. // NB This deletes regular items or submenus.
  3666. STDAPI_(BOOL)
  3667. FileMenu_DeleteItemByCmd(HMENU hmenu, UINT id)
  3668. {
  3669. PFILEMENUHEADER pfmh;
  3670. if (!IsMenu(hmenu))
  3671. return FALSE;
  3672. if (!id)
  3673. return FALSE;
  3674. pfmh = FileMenu_GetHeader(hmenu);
  3675. if (pfmh)
  3676. {
  3677. int i;
  3678. PFILEMENUITEM pfmi = _FindMenuOrItemByCmd(pfmh, id, &i);
  3679. if (pfmi)
  3680. {
  3681. // If it's a submenu, delete it's items first.
  3682. HMENU hmenuSub = GetSubMenu(pfmh->hmenu, i);
  3683. if (hmenuSub)
  3684. FileMenu_DeleteAllItems(hmenuSub);
  3685. // Delete the item itself.
  3686. DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION);
  3687. FileMenuItem_Destroy(pfmi);
  3688. DPA_DeletePtr(pfmh->hdpa, i);
  3689. return TRUE;
  3690. }
  3691. }
  3692. return FALSE;
  3693. }
  3694. STDAPI_(BOOL)
  3695. FileMenu_DeleteItemByIndex(HMENU hmenu, UINT iItem)
  3696. {
  3697. PFILEMENUHEADER pfmh;
  3698. if (!IsMenu(hmenu))
  3699. return FALSE;
  3700. pfmh = FileMenu_GetHeader(hmenu);
  3701. if (pfmh)
  3702. {
  3703. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, iItem);
  3704. if (pfmi)
  3705. {
  3706. // Delete the item itself.
  3707. DeleteMenu(pfmh->hmenu, iItem, MF_BYPOSITION);
  3708. FileMenuItem_Destroy(pfmi);
  3709. DPA_DeletePtr(pfmh->hdpa, iItem);
  3710. return TRUE;
  3711. }
  3712. }
  3713. return FALSE;
  3714. }
  3715. //---------------------------------------------------------------------------
  3716. // Search for the first sub menu of the given menu, who's first item's ID
  3717. // is id. Returns NULL, if nothing is found.
  3718. HMENU _FindMenuItemByFirstID(HMENU hmenu, UINT id, int *pi)
  3719. {
  3720. int cMax, c;
  3721. MENUITEMINFO mii;
  3722. ASSERT(hmenu);
  3723. // Search all items.
  3724. mii.cbSize = SIZEOF(mii);
  3725. mii.fMask = MIIM_ID;
  3726. mii.cch = 0; // just in case
  3727. cMax = GetMenuItemCount(hmenu);
  3728. for (c=0; c<cMax; c++)
  3729. {
  3730. // Is this item a submenu?
  3731. HMENU hmenuSub = GetSubMenu(hmenu, c);
  3732. if (hmenuSub && GetMenuItemInfo(hmenuSub, 0, TRUE, &mii))
  3733. {
  3734. if (mii.wID == id)
  3735. {
  3736. // Found it!
  3737. if (pi)
  3738. *pi = c;
  3739. return hmenuSub;
  3740. }
  3741. }
  3742. }
  3743. return NULL;
  3744. }
  3745. STDAPI_(BOOL)
  3746. FileMenu_DeleteMenuItemByFirstID(HMENU hmenu, UINT id)
  3747. {
  3748. int i;
  3749. PFILEMENUITEM pfmi;
  3750. PFILEMENUHEADER pfmh;
  3751. HMENU hmenuSub;
  3752. if (!IsMenu(hmenu))
  3753. return FALSE;
  3754. if (!id)
  3755. return FALSE;
  3756. pfmh = FileMenu_GetHeader(hmenu);
  3757. if (pfmh)
  3758. {
  3759. hmenuSub = _FindMenuItemByFirstID(hmenu, id, &i);
  3760. if (hmenuSub && i)
  3761. {
  3762. // Delete the submenu.
  3763. FileMenu_DeleteAllItems(hmenuSub);
  3764. // Delete the item itself.
  3765. pfmi = FileMenu_GetItemData(hmenu, i, TRUE);
  3766. DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION);
  3767. FileMenuItem_Destroy(pfmi);
  3768. DPA_DeletePtr(pfmh->hdpa, i);
  3769. return TRUE;
  3770. }
  3771. }
  3772. return FALSE;
  3773. }
  3774. STDAPI_(BOOL)
  3775. FileMenu_DeleteSeparator(HMENU hmenu)
  3776. {
  3777. int i;
  3778. PFILEMENUHEADER pfmh;
  3779. if (!IsMenu(hmenu))
  3780. return FALSE;
  3781. pfmh = FileMenu_GetHeader(hmenu);
  3782. if (pfmh)
  3783. {
  3784. PFILEMENUITEM pfmi = _FindItemByCmd(pfmh, 0, &i);
  3785. if (pfmi)
  3786. {
  3787. // Yep.
  3788. DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION);
  3789. if (pfmi->pidl)
  3790. ILFree(pfmi->pidl);
  3791. LocalFree((HLOCAL)pfmi);
  3792. DPA_DeletePtr(pfmh->hdpa, i);
  3793. return TRUE;
  3794. }
  3795. }
  3796. return FALSE;
  3797. }
  3798. STDAPI_(BOOL)
  3799. FileMenu_InsertSeparator(HMENU hmenu, UINT iPos)
  3800. {
  3801. return FileMenu_InsertItem(hmenu, (LPTSTR)FMAI_SEPARATOR, 0, -1, NULL, 0, iPos);
  3802. }
  3803. STDAPI_(BOOL)
  3804. FileMenu_IsFileMenu(HMENU hmenu)
  3805. {
  3806. return FileMenu_GetHeader(hmenu) ? TRUE : FALSE;
  3807. }
  3808. STDAPI_(BOOL)
  3809. FileMenu_EnableItemByCmd(HMENU hmenu, UINT id, BOOL fEnable)
  3810. {
  3811. PFILEMENUHEADER pfmh;
  3812. if (!IsMenu(hmenu))
  3813. return FALSE;
  3814. if (!id)
  3815. return FALSE;
  3816. pfmh = FileMenu_GetHeader(hmenu);
  3817. if (pfmh)
  3818. {
  3819. PFILEMENUITEM pfmi = _FindItemByCmd(pfmh, id, NULL);
  3820. if (pfmi)
  3821. {
  3822. if (fEnable)
  3823. {
  3824. pfmi->Flags &= ~FMI_DISABLED;
  3825. EnableMenuItem(pfmh->hmenu, id, MF_BYCOMMAND | MF_ENABLED);
  3826. }
  3827. else
  3828. {
  3829. pfmi->Flags |= FMI_DISABLED;
  3830. EnableMenuItem(pfmh->hmenu, id, MF_BYCOMMAND | MF_GRAYED);
  3831. }
  3832. return TRUE;
  3833. }
  3834. }
  3835. else
  3836. {
  3837. TraceMsg(TF_ERROR, "Menu is not a filemenu.");
  3838. }
  3839. return FALSE;
  3840. }
  3841. STDAPI_(BOOL)
  3842. FileMenu_GetPidl(HMENU hmenu, UINT iPos, LPITEMIDLIST *ppidl)
  3843. {
  3844. BOOL fRet = FALSE;
  3845. PFILEMENUHEADER pfmh = FileMenu_GetHeader(hmenu);
  3846. if (pfmh)
  3847. {
  3848. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, iPos);
  3849. if (pfmi)
  3850. {
  3851. if (pfmh->pidlFolder && pfmi->pidl &&
  3852. IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL))
  3853. {
  3854. *ppidl = ILCombine(pfmh->pidlFolder, pfmi->pidl);
  3855. fRet = TRUE;
  3856. }
  3857. }
  3858. }
  3859. return fRet;
  3860. }
  3861. BOOL Tooltip_Create(HWND *phwndTip)
  3862. {
  3863. BOOL fRet = FALSE;
  3864. *phwndTip = CreateWindow(TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
  3865. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, HINST_THISDLL, NULL);
  3866. if (*phwndTip)
  3867. {
  3868. TOOLINFO ti;
  3869. ti.cbSize = SIZEOF(ti);
  3870. ti.uFlags = TTF_TRACK;
  3871. ti.hwnd = NULL;
  3872. ti.uId = 0;
  3873. ti.lpszText = NULL;
  3874. ti.hinst = HINST_THISDLL;
  3875. SetRectEmpty(&ti.rect);
  3876. SendMessage(*phwndTip, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
  3877. fRet = TRUE;
  3878. }
  3879. return fRet;
  3880. }
  3881. void Tooltip_SetText(HWND hwndTip, LPCTSTR pszText)
  3882. {
  3883. if (hwndTip)
  3884. {
  3885. TOOLINFO ti;
  3886. ti.cbSize = SIZEOF(ti);
  3887. ti.uFlags = 0;
  3888. ti.hwnd = NULL;
  3889. ti.uId = 0;
  3890. ti.lpszText = (LPTSTR)pszText;
  3891. ti.hinst = HINST_THISDLL;
  3892. SendMessage(hwndTip, TTM_UPDATETIPTEXT, 0, (LPARAM)(LPTOOLINFO)&ti);
  3893. }
  3894. }
  3895. void Tooltip_Hide(HWND hwndTip)
  3896. {
  3897. if (hwndTip)
  3898. {
  3899. TOOLINFO ti;
  3900. ti.cbSize = SIZEOF(ti);
  3901. ti.hwnd = NULL;
  3902. ti.uId = 0;
  3903. SendMessage(hwndTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti);
  3904. }
  3905. }
  3906. void Tooltip_Show(HWND hwndTip)
  3907. {
  3908. if (hwndTip)
  3909. {
  3910. TOOLINFO ti;
  3911. ti.cbSize = SIZEOF(ti);
  3912. ti.hwnd = NULL;
  3913. ti.uId = 0;
  3914. SendMessage(hwndTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
  3915. SetWindowPos(hwndTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
  3916. }
  3917. }
  3918. void Tooltip_SetPos(HWND hwndTip, int x, int y)
  3919. {
  3920. ASSERT(IsWindow(hwndTip));
  3921. SendMessage(hwndTip, TTM_TRACKPOSITION, 0, MAKELPARAM(x, y));
  3922. }
  3923. /*----------------------------------------------------------
  3924. Purpose: Ask the callback for a tooltip.
  3925. Returns:
  3926. Cond: --
  3927. */
  3928. void
  3929. FileMenuItem_GetTooltip(
  3930. IN PFILEMENUITEM pfmi)
  3931. {
  3932. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  3933. PFILEMENUHEADER pfmh = pfmi->pfmh;
  3934. if (pfmh->pfncb)
  3935. {
  3936. FMCBDATA fmcbdata;
  3937. FMTOOLTIP fmtt = {0};
  3938. if (pfmi->pszTooltip)
  3939. {
  3940. // Free the previous tooltip
  3941. LocalFree(pfmi->pszTooltip);
  3942. pfmi->pszTooltip = NULL;
  3943. }
  3944. fmcbdata.lParam = pfmh->lParam;
  3945. fmcbdata.hmenu = pfmh->hmenu;
  3946. fmcbdata.iPos = -1;
  3947. fmcbdata.idCmd = (UINT)-1;
  3948. // (scotth): we don't ask for tooltips for alternate lists
  3949. fmcbdata.pidlFolder = pfmh->pidlFolder;
  3950. fmcbdata.pidl = pfmi->pidl;
  3951. fmcbdata.psf = pfmh->psf;
  3952. // Was a tooltip set?
  3953. if (S_OK == pfmh->pfncb(FMM_GETTOOLTIP, &fmcbdata, (LPARAM)&fmtt))
  3954. {
  3955. Sz_AllocCopyW(fmtt.pszTip, &(pfmi->pszTooltip));
  3956. SHFree(fmtt.pszTip);
  3957. if (pfmi->pszTooltip)
  3958. {
  3959. // Set the other settings
  3960. if (IsFlagSet(fmtt.dwMask, FMTT_MARGIN))
  3961. {
  3962. pfmi->rcMargin = fmtt.rcMargin;
  3963. SetFlag(pfmi->Flags, FMI_MARGIN);
  3964. }
  3965. if (IsFlagSet(fmtt.dwMask, FMTT_MAXWIDTH))
  3966. {
  3967. pfmi->dwMaxTipWidth = fmtt.dwMaxWidth;
  3968. SetFlag(pfmi->Flags, FMI_MAXTIPWIDTH);
  3969. }
  3970. if (IsFlagSet(fmtt.dwMask, FMTT_DRAWFLAGS))
  3971. {
  3972. pfmi->uDrawFlags = fmtt.uDrawFlags;
  3973. SetFlag(pfmi->Flags, FMI_DRAWFLAGS);
  3974. }
  3975. if (IsFlagSet(fmtt.dwMask, FMTT_TABSTOP))
  3976. {
  3977. pfmi->dwTabstop = fmtt.dwTabstop;
  3978. SetFlag(pfmi->Flags, FMI_TABSTOP);
  3979. }
  3980. }
  3981. }
  3982. }
  3983. }
  3984. /*----------------------------------------------------------
  3985. Purpose: Called on WM_MENUSELECT
  3986. */
  3987. STDAPI_(BOOL)
  3988. FileMenu_HandleMenuSelect(
  3989. IN HMENU hmenu,
  3990. IN WPARAM wparam,
  3991. IN LPARAM lparam)
  3992. {
  3993. UINT id = LOWORD(wparam);
  3994. BOOL fTip = FALSE;
  3995. BOOL fRet = FALSE;
  3996. PFILEMENUITEM pfmi = g_pfmiLastSelNonFolder;
  3997. if (hmenu && pfmi)
  3998. {
  3999. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  4000. PFILEMENUHEADER pfmh = pfmi->pfmh;
  4001. if (pfmh && IsFlagSet(pfmh->fmf, FMF_TOOLTIPS) &&
  4002. FileMenuHeader_CreateTooltipWindow(pfmh))
  4003. {
  4004. // Have we asked for the tooltip?
  4005. if (IsFlagClear(pfmi->Flags, FMI_ASKEDFORTOOLTIP))
  4006. {
  4007. // No; do it now
  4008. FileMenuItem_GetTooltip(pfmi);
  4009. SetFlag(pfmi->Flags, FMI_ASKEDFORTOOLTIP);
  4010. }
  4011. // Does this have a tooltip?
  4012. if (pfmi->pszTooltip && FileMenu_GetHeader(hmenu) == pfmh)
  4013. {
  4014. // Yes
  4015. Tooltip_Hide(g_hwndTip);
  4016. Tooltip_SetPos(g_hwndTip, g_rcItem.left + X_TIPOFFSET, g_rcItem.bottom);
  4017. Tooltip_SetText(g_hwndTip, pfmi->pszTooltip);
  4018. if (IsFlagSet(pfmi->Flags, FMI_MAXTIPWIDTH))
  4019. SendMessage(g_hwndTip, TTM_SETMAXTIPWIDTH, 0, (LPARAM)pfmi->dwMaxTipWidth);
  4020. if (IsFlagSet(pfmi->Flags, FMI_MARGIN))
  4021. SendMessage(g_hwndTip, TTM_SETMARGIN, 0, (LPARAM)&pfmi->rcMargin);
  4022. Tooltip_Show(g_hwndTip);
  4023. fTip = TRUE;
  4024. }
  4025. }
  4026. fRet = TRUE;
  4027. }
  4028. if (!fTip && IsWindow(g_hwndTip))
  4029. Tooltip_Hide(g_hwndTip);
  4030. return fRet;
  4031. }
  4032. STDAPI_(void)
  4033. FileMenu_EditMode(BOOL bEdit)
  4034. {
  4035. g_fsmenuagent.SetEditMode(bEdit, DROPEFFECT_MOVE);
  4036. }
  4037. /*----------------------------------------------------------
  4038. Purpose:
  4039. */
  4040. STDAPI_(BOOL)
  4041. FileMenu_ProcessCommand(
  4042. IN HWND hwnd,
  4043. IN HMENU hmenuBar,
  4044. IN UINT idMenu,
  4045. IN HMENU hmenu,
  4046. IN UINT idCmd)
  4047. {
  4048. return g_fsmenuagent.ProcessCommand(hwnd, hmenuBar, idMenu, hmenu, idCmd);
  4049. }
  4050. void FileMenuHeader_HandleUpdateImage(PFILEMENUHEADER pfmh, int iImage)
  4051. {
  4052. int i;
  4053. PFILEMENUITEM pfmi;
  4054. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  4055. ASSERT(-1 != iImage);
  4056. // Look for any image indexes that are being changed
  4057. for (i = GetMenuItemCount(pfmh->hmenu) - 1; i >= 0; i--)
  4058. {
  4059. pfmi = FileMenu_GetItemData(pfmh->hmenu, i, TRUE);
  4060. if (pfmi)
  4061. {
  4062. ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
  4063. if (pfmi->iImage == iImage)
  4064. {
  4065. // Invalidate this image. It will be recalculated when
  4066. // the menu item is redrawn.
  4067. pfmi->iImage = -1;
  4068. }
  4069. HMENU hmenuSub = GetSubMenu(pfmh->hmenu, i);
  4070. if (hmenuSub)
  4071. {
  4072. PFILEMENUHEADER pfmhT = FileMenu_GetHeader(hmenuSub);
  4073. if (pfmhT)
  4074. FileMenuHeader_HandleUpdateImage(pfmhT, iImage);
  4075. }
  4076. }
  4077. }
  4078. }
  4079. BOOL FileMenuHeader_HandleNotify(PFILEMENUHEADER pfmh, LPCITEMIDLIST * ppidl, LONG lEvent)
  4080. {
  4081. BOOL bRet;
  4082. int iImage;
  4083. ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
  4084. switch (lEvent)
  4085. {
  4086. case SHCNE_UPDATEIMAGE:
  4087. if (EVAL(ppidl && ppidl[0]))
  4088. {
  4089. iImage = *(int UNALIGNED *)((BYTE *)ppidl[0] + 2);
  4090. if (-1 != iImage)
  4091. FileMenuHeader_HandleUpdateImage(pfmh, iImage);
  4092. }
  4093. bRet = TRUE;
  4094. break;
  4095. default:
  4096. bRet = FALSE;
  4097. break;
  4098. }
  4099. return bRet;
  4100. }
  4101. STDAPI_(BOOL)
  4102. FileMenu_HandleNotify(HMENU hmenu, LPCITEMIDLIST * ppidl, LONG lEvent)
  4103. {
  4104. BOOL bRet = FALSE;
  4105. PFILEMENUHEADER pfmh = FileMenu_GetHeader(hmenu);
  4106. if (hmenu && pfmh)
  4107. {
  4108. bRet = FileMenuHeader_HandleNotify(pfmh, ppidl, lEvent);
  4109. }
  4110. return bRet;
  4111. }