//--------------------------------------------------------------------------- // Helper routines for an owner draw menu showing the contents of a directory. //--------------------------------------------------------------------------- // FEATURE (scotth): note this file is #included by SHELL32 and SHDOC401. // We really want the bits in one place, but right now // SHDOC401 needs some changes which SHELL32 on win95 // does not provide. // // The second best solution is to place this code in // a static lib (stocklib). However, shell32's default // data segment is still shared, and since this file // contains some globals, we'd have problems with that. // If shell32 is fixed, we can add this file to stocklib. // // Our third best solution is to #include this file. // That's better than maintaining two different source // codes. // #include #ifdef IN_SHDOCVW extern "C" LPITEMIDLIST IEILCreate(UINT cbSize); #define _ILCreate IEILCreate #endif STDAPI_(LPITEMIDLIST) SafeILClone(LPCITEMIDLIST pidl); #define ILClone SafeILClone #define CXIMAGEGAP 6 // #define SRCSTENCIL 0x00B8074AL typedef enum { FMII_DEFAULT = 0x0000, FMII_BREAK = 0x0001 } FMIIFLAGS; #define FMI_NULL 0x00000000 #define FMI_MARKER 0x00000001 #define FMI_FOLDER 0x00000002 #define FMI_EXPAND 0x00000004 #define FMI_EMPTY 0x00000008 #define FMI_SEPARATOR 0x00000010 #define FMI_DISABLED 0x00000020 // Enablingly Challenged ??? #define FMI_ON_MENU 0x00000040 #define FMI_IGNORE_PIDL 0x00000080 // Ignore the pidl as the display string #define FMI_FILESYSTEM 0x00000100 #define FMI_MARGIN 0x00000200 #define FMI_MAXTIPWIDTH 0x00000400 #define FMI_TABSTOP 0x00000800 #define FMI_DRAWFLAGS 0x00001000 #define FMI_ALTITEM 0x00002000 // Item came from alternate pidl #define FMI_CXMAX 0x00004000 #define FMI_CYMAX 0x00008000 #define FMI_CYSPACING 0x00010000 #define FMI_ASKEDFORTOOLTIP 0x00020000 #define FMI_USESTRING 0x00040000 // Use psz before pidl as display string // One of these per file menu. typedef struct { IShellFolder * psf; // Shell Folder. IStream * pstm; // Optional stream HMENU hmenu; // Menu. LPITEMIDLIST pidlFolder; // Pidl for the folder. HDPA hdpa; // List of items (see below). UINT idCmd; // Command. DWORD fmf; // Header flags. UINT fFSFilter; // file system enum filter HBITMAP hbmp; // Background bitmap. UINT cxBmp; // Width of bitmap. UINT cyBmp; // Height of bitmap. UINT cxBmpGap; // Gap for bitmap. UINT yBmp; // Cached Y coord. COLORREF clrBkg; // Background color. UINT cySel; // Prefered height of selection. PFNFMCALLBACK pfncb; // Callback function. LPARAM lParam; // Parameter passed for callback handler IShellFolder * psfAlt; // Alternate Shell Folder. LPITEMIDLIST pidlAltFolder; // Pidl for the alternate folder. HDPA hdpaAlt; // Alternate dpa int cyMenuSizeSinceLastBreak; // Size of menu (cy) UINT cyMax; // Max allowable height of entire menu in pixels UINT cxMax; // Max allowable width in pixels UINT cySpacing; // Spacing b/t menu items in pixels LPTSTR pszFilterTypes; // Multi-string list of extensions (e.g., "doc\0xls\0") } FILEMENUHEADER, *PFILEMENUHEADER; // One of these for each file menu item. // // !!! Note: the testers have a test utility which grabs // the first 7 fields of this structure. If you change // the order or meaning of these fields, make sure they // are notified so they can update their automated tests. // typedef struct { PFILEMENUHEADER pfmh; // The header. int iImage; // Image index to use. DWORD Flags; // Misc flags above. LPITEMIDLIST pidl; // IDlist for item. LPTSTR psz; // Text when not using pidls. UINT cyItem; // Custom height. LPTSTR pszTooltip; // Item tooltip. RECT rcMargin; // Margin around tooltip DWORD dwMaxTipWidth; // Maximum tooltip width DWORD dwTabstop; UINT uDrawFlags; LPARAM lParam; // Application data int nOrder; // Ordinal indicating user preference DWORD dwEffect; // Acceptable drop effects } FILEMENUITEM, *PFILEMENUITEM; #define X_TIPOFFSET 130 // an arbitrary number of pixels class CFSMenuAgent { private: DWORD _dwState; // MAS_* HHOOK _hhookMsg; PFILEMENUITEM _pfmiCur; // Current item selected PFILEMENUITEM _pfmiDrag; // Item being dragged PFILEMENUITEM _pfmiDrop; // Target of drop DWORD _dwStateSav; // Saved state once menu goes away HWND _hwndMenu; HDC _hdc; RECT _rcCur; // rect of current selection RECT _rcCurScr; // rect of current selection in screen coords RECT _rcMenu; // rect of whole menu in screen coords int _yCenter; // center of item (in screen coords) HCURSOR _hcurSav; HBRUSH _hbr; public: CFSMenuAgent(void); void Init(void); void Reset(void); void EndMenu(void); void UpdateInsertionCaret(void); void SetEditMode(BOOL bEdit, DWORD dwEffect); void SetCaretPos(LPPOINT ppt); HCURSOR SetCursor(DWORD dwEffect); void SetCurrentRect(HDC hdc, LPRECT prcItem); void SetItem(PFILEMENUITEM pfmi) { _pfmiCur = pfmi; } void SetDragItem(void) { _pfmiDrag = _pfmiCur; } void SetDropItem(void); DWORD GetDragEffect(void); BOOL ProcessCommand(HWND hwnd, HMENU hmenuBar, UINT idMenu, HMENU hmenu, UINT idCmd); friend LRESULT CALLBACK CFSMenuAgent_MsgHook(int nCode, WPARAM wParam, LPARAM lParam); friend LRESULT FileMenu_DrawItem(HWND hwnd, DRAWITEMSTRUCT *pdi); }; // Menu agent state #define MAS_EDITMODE 0x00000001 #define MAS_LBUTTONDOWN 0x00000002 #define MAS_LBUTTONUP 0x00000004 #define MAS_INSERTABOVE 0x00000008 // Edit mode states #define MenuDD_IsButtonDown() (g_fsmenuagent._dwState & MAS_LBUTTONDOWN) #define MenuDD_InEditMode() (g_fsmenuagent._dwState & MAS_EDITMODE) #define MenuDD_InsertAbove() (g_fsmenuagent._dwState & MAS_INSERTABOVE) #define MenuDD_GetBrush() (g_fsmenuagent._hbr) //--------------------------------------------------------------------------- CFSMenuAgent g_fsmenuagent; PFILEMENUITEM g_pfmiLastSel = NULL; PFILEMENUITEM g_pfmiLastSelNonFolder = NULL; // This saves us creating DC's all the time for the blits. HDC g_hdcMem = NULL; HFONT g_hfont = NULL; BOOL g_fAbortInitMenu = FALSE; // Tooltip stuff. HWND g_hwndTip = NULL; RECT g_rcItem = {0, 0, 0, 0}; HIMAGELIST g_himlIconsSmall = NULL; HIMAGELIST g_himlIcons = NULL; //--------------------------------------------------------------------------- // Validation functions // IEUNIX -- these functions don't appear to be defined anywhere else, as the // other def'n would imply #if defined(DEBUG) || defined(UNIX) BOOL IsValidPFILEMENUHEADER(PFILEMENUHEADER pfmh) { return (IS_VALID_WRITE_PTR(pfmh, FILEMENUHEADER) && ((NULL == pfmh->psf && NULL == pfmh->pidlFolder) || (IS_VALID_CODE_PTR(pfmh->psf, IShellFolder) && IS_VALID_PIDL(pfmh->pidlFolder))) && IS_VALID_HANDLE(pfmh->hmenu, MENU) && IS_VALID_HANDLE(pfmh->hdpa, DPA) && ((NULL == pfmh->psfAlt && NULL == pfmh->pidlAltFolder) || (IS_VALID_CODE_PTR(pfmh->psfAlt, IShellFolder) && IS_VALID_PIDL(pfmh->pidlAltFolder))) && (NULL == pfmh->hdpaAlt || IS_VALID_HANDLE(pfmh->hdpaAlt, DPA)) && (NULL == pfmh->pszFilterTypes || IS_VALID_STRING_PTR(pfmh->pszFilterTypes, -1))); } BOOL IsValidPFILEMENUITEM(PFILEMENUITEM pfmi) { return (IS_VALID_WRITE_PTR(pfmi, FILEMENUITEM) && IS_VALID_STRUCT_PTR(pfmi->pfmh, FILEMENUHEADER) && (NULL == pfmi->pidl || IS_VALID_PIDL(pfmi->pidl)) && (NULL == pfmi->psz || IS_VALID_STRING_PTR(pfmi->psz, -1)) && (NULL == pfmi->pszTooltip || IS_VALID_STRING_PTR(pfmi->pszTooltip, -1))); } #else BOOL IsValidPFILEMENUHEADER(PFILEMENUHEADER pfmh); BOOL IsValidPFILEMENUITEM(PFILEMENUITEM pfmi); #endif //--------------------------------------------------------------------------- // Helper functions void FileList_Reorder(PFILEMENUHEADER pfmh); BOOL FileMenuHeader_InsertItem(PFILEMENUHEADER pfmh, UINT iItem, FMIIFLAGS fFlags); BOOL FileMenuItem_Destroy(PFILEMENUITEM pfmi); BOOL FileMenuItem_Move(HWND hwnd, PFILEMENUITEM pfmiFrom, PFILEMENUHEADER pfmhTo, int iPosTo); BOOL Tooltip_Create(HWND *phwndTip); __inline static BOOL LAlloc(UINT cb, PVOID *ppv) { ASSERT(ppv); *ppv = (PVOID*)LocalAlloc(LPTR, cb); return *ppv ? TRUE : FALSE; } __inline static BOOL LFree(PVOID pv) { return LocalFree(pv) ? FALSE : TRUE; } /*---------------------------------------------------------- Purpose: Allocate a multi-string (double-null terminated) Returns: Cond: -- */ BOOL MultiSz_AllocCopy( IN LPCTSTR pszSrc, OUT LPTSTR * ppszDst) { BOOL fRet = FALSE; UINT cch; UINT cchMac = 0; LPCTSTR psz; LPTSTR pszDst; ASSERT(pszSrc && ppszDst); psz = pszSrc; while (*psz) { cch = lstrlen(psz) + 1; cchMac += cch; psz += cch; } cchMac++; // extra null if (LAlloc(CbFromCch(cchMac), (PVOID *)ppszDst)) { psz = pszSrc; pszDst = *ppszDst; while (*psz) { lstrcpy(pszDst, psz); cch = lstrlen(psz) + 1; psz += cch; pszDst += cch; } fRet = TRUE; } return fRet; } /*---------------------------------------------------------- Purpose: Allocate a string Returns: Cond: -- */ BOOL Sz_AllocCopyA( IN LPCSTR pszSrc, OUT LPTSTR *ppszDst) { BOOL fRet = FALSE; UINT cch; ASSERT(pszSrc && ppszDst); // NB We allocate an extra char in case we need to add an '&'. cch = lstrlenA(pszSrc) + 2; if (LAlloc(CbFromCchA(cch), (PVOID *)ppszDst)) { #ifdef UNICODE MultiByteToWideChar(CP_ACP, 0, pszSrc, -1, *ppszDst, cch); #else lstrcpy(*ppszDst, pszSrc); #endif fRet = TRUE; } return fRet; } /*---------------------------------------------------------- Purpose: Allocate a string Returns: Cond: -- */ BOOL Sz_AllocCopyW( IN LPCWSTR pszSrc, OUT LPTSTR *ppszDst) { BOOL fRet = FALSE; UINT cch; ASSERT(pszSrc && ppszDst); // NB We allocate an extra char in case we need to add an '&'. cch = lstrlenW(pszSrc) + 2; if (LAlloc(CbFromCchW(cch), (PVOID *)ppszDst)) { #ifdef UNICODE lstrcpy(*ppszDst, pszSrc); #else WideCharToMultiByte(CP_ACP, 0, pszSrc, -1, *ppszDst, CbFromCchW(cch), NULL, NULL); #endif fRet = TRUE; } return fRet; } #ifdef UNICODE #define Sz_AllocCopy Sz_AllocCopyW #else #define Sz_AllocCopy Sz_AllocCopyA #endif HCURSOR LoadMenuCursor(UINT idCur) { #ifdef IN_SHDOCVW return LoadCursor(HINST_THISDLL, MAKEINTRESOURCE(idCur)); #else HINSTANCE hinst = GetModuleHandle(TEXT("shdocvw.dll")); if (hinst) return LoadCursor(hinst, MAKEINTRESOURCE(idCur)); return LoadCursor(NULL, IDC_ARROW); #endif } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #ifdef DEBUG static void DumpMsg(MSG * pmsg) { ASSERT(pmsg); switch (pmsg->message) { case WM_LBUTTONDOWN: TraceMsg(TF_ALWAYS, "MsgHook: msg = WM_LBUTTONDOWN hwnd = %#08lx x = %d y = %d", pmsg->hwnd, pmsg->pt.x, pmsg->pt.y); TraceMsg(TF_ALWAYS, " keys = %#04lx x = %d y = %d", pmsg->wParam, GET_X_LPARAM(pmsg->lParam), GET_Y_LPARAM(pmsg->lParam)); break; case WM_LBUTTONUP: TraceMsg(TF_ALWAYS, "MsgHook: msg = WM_LBUTTONUP hwnd = %#08lx x = %d y = %d", pmsg->hwnd, pmsg->pt.x, pmsg->pt.y); TraceMsg(TF_ALWAYS, " keys = %#04lx x = %d y = %d", pmsg->wParam, GET_X_LPARAM(pmsg->lParam), GET_Y_LPARAM(pmsg->lParam)); break; case WM_MOUSEMOVE: break; case WM_TIMER: TraceMsg(TF_ALWAYS, "MsgHook: msg = WM_TIMER hwnd = %#08lx x = %d y = %d", pmsg->hwnd, pmsg->pt.x, pmsg->pt.y); TraceMsg(TF_ALWAYS, " id = %#08lx", pmsg->wParam); break; case WM_MENUSELECT: TraceMsg(TF_ALWAYS, "MsgHook: msg = WM_MENUSELECT hwnd = %#08lx x = %d y = %d", pmsg->hwnd, pmsg->pt.x, pmsg->pt.y); TraceMsg(TF_ALWAYS, " uItem = %#04lx flags = %#04lx hmenu = %#08lx", GET_WM_MENUSELECT_CMD(pmsg->wParam, pmsg->lParam), GET_WM_MENUSELECT_FLAGS(pmsg->wParam, pmsg->lParam), GET_WM_MENUSELECT_HMENU(pmsg->wParam, pmsg->lParam)); break; default: TraceMsg(TF_ALWAYS, "MsgHook: msg = %#04lx hwnd = %#04lx x = %d y = %d", pmsg->message, pmsg->hwnd, pmsg->pt.x, pmsg->pt.y); break; } } #endif /*---------------------------------------------------------- Purpose: Message hook used to track drag and drop within the menu. */ LRESULT CALLBACK CFSMenuAgent_MsgHook(int nCode, WPARAM wParam, LPARAM lParam) { LRESULT lRet = 0; MSG * pmsg = (MSG *)lParam; switch (nCode) { case MSGF_MENU: #ifdef DEBUG if (IsFlagSet(g_dwDumpFlags, DF_HOOK)) DumpMsg(pmsg); #endif switch (pmsg->message) { case WM_LBUTTONUP: // We record the mouse up IFF it happened in the menu // and we had previously recorded the mouse down. if (IsFlagSet(g_fsmenuagent._dwState, MAS_EDITMODE | MAS_LBUTTONDOWN)) { POINT pt; TraceMsg(TF_MENU, "MenuDD: getting mouse up"); pt.x = GET_X_LPARAM(pmsg->lParam); pt.y = GET_Y_LPARAM(pmsg->lParam); if (PtInRect(&g_fsmenuagent._rcMenu, pt)) { SetFlag(g_fsmenuagent._dwState, MAS_LBUTTONUP); g_fsmenuagent.EndMenu(); } } ClearFlag(g_fsmenuagent._dwState, MAS_LBUTTONDOWN); break; case WM_LBUTTONDOWN: if (g_fsmenuagent._pfmiCur && (g_fsmenuagent._pfmiCur->dwEffect & DROPEFFECT_MOVE)) { TraceMsg(TF_MENU, "MenuDD: getting mouse down"); SetFlag(g_fsmenuagent._dwState, MAS_LBUTTONDOWN); g_fsmenuagent.SetDragItem(); } break; case WM_MOUSEMOVE: if (g_fsmenuagent._dwState & MAS_EDITMODE) { POINT pt; BOOL bInMenu; pt.x = GET_X_LPARAM(pmsg->lParam); pt.y = GET_Y_LPARAM(pmsg->lParam); g_fsmenuagent.SetCaretPos(&pt); bInMenu = PtInRect(&g_fsmenuagent._rcMenu, pt); #if 0 TraceMsg(TF_MENU, "MenuDD: %s (%d,%d) in [%d,%d,%d,%d]", bInMenu ? TEXT("in menu") : TEXT("not in menu"), pt.x, pt.y, g_fsmenuagent._rcMenu.left, g_fsmenuagent._rcMenu.top, g_fsmenuagent._rcMenu.right, g_fsmenuagent._rcMenu.bottom); #endif // Determine which cursor to show if ( !bInMenu ) g_fsmenuagent.SetItem(NULL); g_fsmenuagent.SetCursor(g_fsmenuagent.GetDragEffect()); } break; case WM_MENUSELECT: BLOCK { UINT uItem = GET_WM_MENUSELECT_CMD(pmsg->wParam, pmsg->lParam); HMENU hmenu = GET_WM_MENUSELECT_HMENU(pmsg->wParam, pmsg->lParam); // Is the menu going away? if (0 == uItem && NULL == hmenu) { // Yes; release menu drag/drop TraceMsg(TF_MENU, "MenuDD: menu being cancelled"); // Since we're in the middle of the hook chain, call // the next hook first, then remove ourselves lRet = CallNextHookEx(g_fsmenuagent._hhookMsg, nCode, wParam, lParam); // Was an item dropped? if (g_fsmenuagent._dwState & MAS_LBUTTONUP) { // Yes; remember it g_fsmenuagent.SetDropItem(); } g_fsmenuagent.Reset(); return lRet; } } break; } break; default: if (0 > nCode) return CallNextHookEx(g_fsmenuagent._hhookMsg, nCode, wParam, lParam); break; } // Pass it on to the next hook in the chain if (0 == lRet) lRet = CallNextHookEx(g_fsmenuagent._hhookMsg, nCode, wParam, lParam); return lRet; } CFSMenuAgent::CFSMenuAgent(void) { // This object is global, and not allocated. We must explicitly // initialize the variables. _dwState = 0; _hhookMsg = 0; _dwStateSav = 0; _pfmiCur = NULL; _pfmiDrag = NULL; _pfmiDrop = NULL; _hwndMenu = NULL; _hdc = NULL; _hcurSav = NULL; _hbr = NULL; } /*---------------------------------------------------------- Purpose: Initialize the menu drag/drop structure. This must anticipate being called when it is already initialized. Also, this will get called whenever a cascaded menu is opened. Be sure to maintain state across these junctures. */ void CFSMenuAgent::Init(void) { TraceMsg(TF_MENU, "Initialize menu drag/drop"); // Do not init _pfmiDrag, since this function is called for every // cascaded menu, and _pfmiDrag must be remembered across these // menus. _pfmiDrop = NULL; _dwStateSav = 0; if (NULL == _hhookMsg) { _hhookMsg = SetWindowsHookEx(WH_MSGFILTER, CFSMenuAgent_MsgHook, HINST_THISDLL, 0); } if (NULL == _hbr) { // Don't need to release this _hbr = GetSysColorBrush(COLOR_3DFACE); } } /*---------------------------------------------------------- Purpose: Make the menu go away */ void CFSMenuAgent::EndMenu(void) { ASSERT(IsWindow(_hwndMenu)); SendMessage(_hwndMenu, WM_CANCELMODE, 0, 0); } /*---------------------------------------------------------- Purpose: Decides whether to position the caret above or below the menu item, based upon the given point (cursor position). */ void CFSMenuAgent::SetCaretPos(LPPOINT ppt) { ASSERT(ppt); if (ppt->y < _yCenter) { // Change the caret position? if (IsFlagClear(_dwState, MAS_INSERTABOVE)) { // Yes SetFlag(_dwState, MAS_INSERTABOVE); UpdateInsertionCaret(); } } else { // Change the caret position? if (IsFlagSet(_dwState, MAS_INSERTABOVE)) { // Yes ClearFlag(_dwState, MAS_INSERTABOVE); UpdateInsertionCaret(); } } } void CFSMenuAgent::UpdateInsertionCaret(void) { if (_dwState & MAS_EDITMODE) { InvalidateRect(_hwndMenu, &_rcCur, FALSE); UpdateWindow(_hwndMenu); } } void CFSMenuAgent::SetDropItem(void) { // Only set the drop item if the drop effect is supported. ASSERT(_pfmiDrag); if (_pfmiCur && (_pfmiCur->dwEffect & _pfmiDrag->dwEffect)) _pfmiDrop = _pfmiCur; else _pfmiDrop = NULL; } /*---------------------------------------------------------- Purpose: Set the cursor based on the given flags */ HCURSOR CFSMenuAgent::SetCursor(DWORD dwEffect) { HCURSOR hcur = NULL; ASSERT(_dwState & MAS_EDITMODE); // Does this item support the requested drop effect? if (_pfmiCur && (dwEffect & _pfmiCur->dwEffect)) { // Yes UINT idCur; if (dwEffect & DROPEFFECT_MOVE) idCur = IDC_MENUMOVE; else if (dwEffect & DROPEFFECT_COPY) idCur = IDC_MENUCOPY; else { ASSERT_MSG(0, "Unknown drop effect!"); idCur = IDC_MENUDENY; } hcur = ::SetCursor(LoadMenuCursor(idCur)); } else { // No hcur = ::SetCursor(LoadMenuCursor(IDC_MENUDENY)); } return hcur; } DWORD CFSMenuAgent::GetDragEffect(void) { if (_pfmiDrag) return _pfmiDrag->dwEffect; else return DROPEFFECT_NONE; } void CFSMenuAgent::SetEditMode(BOOL bEdit, DWORD dwEffect) { // Only update if the state has changed if (bEdit && IsFlagClear(_dwState, MAS_EDITMODE)) { TraceMsg(TF_MENU, "MenuDD: entering edit mode"); SetFlag(_dwState, MAS_EDITMODE); _hcurSav = SetCursor(dwEffect); } else if (!bEdit && IsFlagSet(_dwState, MAS_EDITMODE)) { TraceMsg(TF_MENU, "MenuDD: leaving edit mode"); ClearFlag(_dwState, MAS_EDITMODE); ASSERT(_hcurSav); if (_hcurSav) { ::SetCursor(_hcurSav); _hcurSav = NULL; } } } void CFSMenuAgent::SetCurrentRect(HDC hdc, LPRECT prcItem) { HWND hwnd = WindowFromDC(hdc); ASSERT(hdc); ASSERT(prcItem); _hwndMenu = hwnd; _hdc = hdc; _rcCur = *prcItem; _rcCurScr = *prcItem; GetWindowRect(hwnd, &_rcMenu); MapWindowPoints(hwnd, NULL, (LPPOINT)&_rcCurScr, 2); _yCenter = _rcCurScr.top + (_rcCurScr.bottom - _rcCurScr.top) / 2; } /*---------------------------------------------------------- Purpose: Reset the menu agent. This is called when the menu goes away. The ProcessCommand method still needs some state information so it knows what action had taken place. This info is moved to a post-action field. */ void CFSMenuAgent::Reset(void) { TraceMsg(TF_MENU, "MenuDD: releasing edit mode resources"); // Remember the state for FileMenu_ProcessCommand _dwStateSav = _dwState; SetEditMode(FALSE, DROPEFFECT_NONE); TraceMsg(TF_MENU, "MenuDD: Hook removed for menu drag/drop"); if (_hhookMsg) { UnhookWindowsHookEx(_hhookMsg); _hhookMsg = NULL; } // Reset _pfmiCur = NULL; _hwndMenu = NULL; _hdc = NULL; _dwState = 0; _hbr = NULL; ASSERT(NULL == _hcurSav); } /*---------------------------------------------------------- Purpose: Have a whack at the WM_COMMAND, in case it is the result of drag and drop within the menu. Returns: TRUE if this function handled the command */ BOOL CFSMenuAgent::ProcessCommand(HWND hwnd, HMENU hmenuBar, UINT idMenu, HMENU hmenu, UINT idCmd) { BOOL bRet = FALSE; if (hmenu && _pfmiDrag && (_dwStateSav & MAS_EDITMODE)) { ASSERT(IS_VALID_STRUCT_PTR(_pfmiDrag, FILEMENUITEM)); // Did the user move an item within the menu? if (_pfmiDrop) { // Yes ASSERT(IS_VALID_STRUCT_PTR(_pfmiDrop, FILEMENUITEM)); int iPosTo = DPA_GetPtrIndex(_pfmiDrop->pfmh->hdpa, _pfmiDrop); if (IsFlagClear(_dwStateSav, MAS_INSERTABOVE)) iPosTo++; IEPlaySound(TEXT("MoveMenuItem"), FALSE); bRet = FileMenuItem_Move(hwnd, _pfmiDrag, _pfmiDrop->pfmh, iPosTo); // Re-order the items FileList_Reorder(_pfmiDrop->pfmh); } _pfmiDrag = NULL; _pfmiDrop = NULL; #if 0 // Did we successfully handle this? if (bRet) { // Yes; bring the menu back up so the user can continue // editting. HiliteMenuItem(hwnd, hmenuBar, idMenu, MF_BYCOMMAND | MF_HILITE); DrawMenuBar(hwnd); TrackPopupMenu(hmenu, TPM_LEFTALIGN, _rcMenu.left, _rcMenu.top, 0, hwnd, NULL); HiliteMenuItem(hwnd, hmenuBar, idMenu, MF_BYCOMMAND | MF_UNHILITE); DrawMenuBar(hwnd); } #endif // Always return true because we handled it bRet = TRUE; } return bRet; } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- void DeleteGlobalMemDCAndFont(void) { if (g_hdcMem) { DeleteDC(g_hdcMem); g_hdcMem = NULL; } if (g_hfont) { DeleteObject(g_hfont); g_hfont = NULL; } } DWORD GetItemTextExtent( IN HDC hdc, IN LPCTSTR lpsz) { SIZE sz; GetTextExtentPoint(hdc, lpsz, lstrlen(lpsz), &sz); // NB This is OK as long as an item's extend doesn't get very big. return MAKELONG((WORD)sz.cx, (WORD)sz.cy); } /*---------------------------------------------------------- Purpose: Validates pfmitem. This also initializes pfmitemOut given the mask and flags set in pfmitem. This helper function is useful for APIs to "cleanse" incoming FMITEM structures. Returns: TRUE if pfmitem is a valid structure Cond: -- */ BOOL IsValidFMItem( IN FMITEM const * pfmitem, OUT PFMITEM pfmitemOut) { BOOL bRet = FALSE; ASSERT(pfmitem); ASSERT(pfmitemOut); if (IS_VALID_READ_PTR(pfmitem, FMITEM) && SIZEOF(*pfmitem) == pfmitem->cbSize) { ZeroInit(pfmitemOut, SIZEOF(*pfmitemOut)); pfmitemOut->cbSize = SIZEOF(*pfmitemOut); pfmitemOut->dwMask = pfmitem->dwMask; if (pfmitemOut->dwMask & FMI_TYPE) pfmitemOut->dwType = pfmitem->dwType; if (pfmitemOut->dwMask & FMI_ID) pfmitemOut->uID = pfmitem->uID; if (pfmitemOut->dwMask & FMI_ITEM) pfmitemOut->uItem = pfmitem->uItem; if (pfmitemOut->dwMask & FMI_IMAGE) pfmitemOut->iImage = pfmitem->iImage; else pfmitemOut->iImage = -1; if (pfmitemOut->dwMask & FMI_DATA) pfmitemOut->pvData = pfmitem->pvData; if (pfmitemOut->dwMask & FMI_HMENU) pfmitemOut->hmenuSub = pfmitem->hmenuSub; if (pfmitemOut->dwMask & FMI_METRICS) pfmitemOut->cyItem = pfmitem->cyItem; if (pfmitemOut->dwMask & FMI_LPARAM) pfmitemOut->lParam = pfmitem->lParam; // The FMIT_STRING and FMIT_SEPARATOR are exclusive if (IsFlagSet(pfmitemOut->dwType, FMIT_STRING) && IsFlagSet(pfmitemOut->dwType, FMIT_SEPARATOR)) { bRet = FALSE; } else bRet = TRUE; } return bRet; } void FileMenuItem_GetDisplayName( IN PFILEMENUITEM pfmi, IN LPTSTR pszName, IN UINT cchName) { STRRET str; ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); ASSERT(IS_VALID_WRITE_BUFFER(pszName, TCHAR, cchName)); // Is this a special empty item? if (pfmi->Flags & FMI_EMPTY) { // Yep, load the string from a resource. LoadString(HINST_THISDLL, IDS_NONE, pszName, cchName); } else { // Nope, ask the folder for the name of the item. PFILEMENUHEADER pfmh = pfmi->pfmh; LPSHELLFOLDER psfTemp; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (pfmi->Flags & FMI_ALTITEM) { psfTemp = pfmh->psfAlt; } else { psfTemp = pfmh->psf; } // If it's got a pidl use that, else just use the normal menu string. if (psfTemp && pfmi->pidl && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL)) { if (SUCCEEDED(psfTemp->GetDisplayNameOf(pfmi->pidl, SHGDN_NORMAL, &str))) { StrRetToBuf(&str, pfmi->pidl, pszName, cchName); } else { *pszName = TEXT('\0'); } } else if (pfmi->psz) { lstrcpyn(pszName, pfmi->psz, cchName); } else { *pszName = TEXT('\0'); } } } #define FileMenuHeader_AllowAbort(pfmh) (!(pfmh->fmf & FMF_NOABORT)) /*---------------------------------------------------------- Purpose: Create a menu item structure to be stored in the hdpa Returns: TRUE on success Cond: -- */ BOOL FileMenuItem_Create( IN PFILEMENUHEADER pfmh, IN LPCITEMIDLIST pidl, OPTIONAL IN int iImage, IN DWORD dwFlags, // FMI_* OUT PFILEMENUITEM * ppfmi) { PFILEMENUITEM pfmi = (PFILEMENUITEM)LocalAlloc(LPTR, SIZEOF(FILEMENUITEM)); ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); ASSERT(ppfmi); ASSERT(NULL == pidl || IS_VALID_PIDL(pidl)); if (pfmi) { DWORD dwAttribs = SFGAO_FOLDER | SFGAO_FILESYSTEM; IShellFolder * psfTemp; BOOL bUseAlt = IsFlagSet(dwFlags, FMI_ALTITEM); pfmi->pfmh = pfmh; pfmi->pidl = (LPITEMIDLIST)pidl; pfmi->iImage = iImage; pfmi->Flags = dwFlags; pfmi->nOrder = INT_MAX; // New items go to the bottom if (bUseAlt) psfTemp = pfmh->psfAlt; else psfTemp = pfmh->psf; if (pidl && SUCCEEDED(psfTemp->GetAttributesOf(1, &pidl, &dwAttribs))) { if (dwAttribs & SFGAO_FOLDER) pfmi->Flags |= FMI_FOLDER; if (dwAttribs & SFGAO_FILESYSTEM) pfmi->Flags |= FMI_FILESYSTEM; } } *ppfmi = pfmi; return (NULL != pfmi); } /*---------------------------------------------------------- Purpose: Move an item within the same menu or across menus */ BOOL FileMenuItem_Move( HWND hwnd, PFILEMENUITEM pfmiFrom, PFILEMENUHEADER pfmhTo, int iPosTo) { BOOL bRet = FALSE; TCHAR szFrom[MAX_PATH + 1]; // +1 for double null ASSERT(IS_VALID_STRUCT_PTR(pfmiFrom, FILEMENUITEM)); ASSERT(IS_VALID_STRUCT_PTR(pfmhTo, FILEMENUHEADER)); PFILEMENUHEADER pfmhFrom = pfmiFrom->pfmh; HDPA hdpaFrom = pfmhFrom->hdpa; HDPA hdpaTo = pfmhTo->hdpa; BOOL bSameMenu = (pfmhFrom == pfmhTo); ASSERT(IsFlagSet(pfmhFrom->fmf, FMF_CANORDER)); // Is this item being moved within the same menu? if (bSameMenu) { // Yes; simply change the order of the menu below bRet = TRUE; } else { // No; need to move the actual file to the menu's associated // folder. Also note the placement of the item in the menu. TCHAR szTo[MAX_PATH + 1]; // +1 for double null IShellFolder * psf = pfmhFrom->psf; STRRET str; SHGetPathFromIDList(pfmhTo->pidlFolder, szTo); szTo[lstrlen(szTo) + 1] = 0; // double null if (SUCCEEDED(psf->GetDisplayNameOf(pfmiFrom->pidl, SHGDN_FORPARSING, &str))) { StrRetToBuf(&str, pfmiFrom->pidl, szFrom, SIZECHARS(szFrom)); szFrom[lstrlen(szFrom) + 1] = 0; // double null // WARNING: if you change this code to perform rename on // collision, be sure to update the pfmiFrom contents to // reflect that name change! SHFILEOPSTRUCT shop = {hwnd, FO_MOVE, szFrom, szTo, 0, }; bRet = (NO_ERROR == SHFileOperation(&shop)); if (bRet) { // Flush the notification so the menu is updated immediately. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSH, szFrom, NULL); } // The move operation will send a notification to the // window, which will eventually invalidate this menu // to have it rebuilt. However, before that happens // we want to record the position of this dragged item // in the destination menu. So change the order of // the menu anyway. } } if (bRet) { // Change the order of the menu int iPosFrom = DPA_GetPtrIndex(hdpaFrom, pfmiFrom); bRet = FALSE; // Account for the fact we delete before we insert within the // same menu if (bSameMenu && iPosTo > iPosFrom) iPosTo--; DPA_DeletePtr(hdpaFrom, iPosFrom); iPosTo = DPA_InsertPtr(hdpaTo, iPosTo, pfmiFrom); if (-1 != iPosTo) { // Update the header of the item pfmiFrom->pfmh = pfmhTo; // Move the menu items MENUITEMINFO mii; mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_DATA | MIIM_ID | MIIM_STATE | MIIM_SUBMENU | MIIM_TYPE; if (GetMenuItemInfo(pfmhFrom->hmenu, iPosFrom, TRUE, &mii)) { // Remove a submenu first so it doesn't get nuked if (GetSubMenu(pfmhFrom->hmenu, iPosFrom)) RemoveMenu(pfmhFrom->hmenu, iPosFrom, MF_BYPOSITION); DeleteMenu(pfmhFrom->hmenu, iPosFrom, MF_BYPOSITION); if ( !InsertMenuItem(pfmhTo->hmenu, iPosTo, TRUE, &mii) ) { TraceMsg(TF_ERROR, "Failed to move menu item"); DPA_DeletePtr(hdpaTo, iPosTo); } else { SetFlag(pfmhFrom->fmf, FMF_DIRTY); SetFlag(pfmhTo->fmf, FMF_DIRTY); bRet = TRUE; } } } else { // Punt TraceMsg(TF_ERROR, "Menu: could not insert moved item in the DPA"); } } return bRet; } /*---------------------------------------------------------- Purpose: Enumerates the folder and adds the files to the DPA. Returns: count of items in the list */ int FileList_Build( IN PFILEMENUHEADER pfmh, IN int cItems, IN BOOL bUseAlt) { HDPA hdpaTemp; HRESULT hres; LPITEMIDLIST pidlSkip = NULL; LPITEMIDLIST pidlProgs = NULL; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (FileMenuHeader_AllowAbort(pfmh) && g_fAbortInitMenu) return -1; if (bUseAlt) { hdpaTemp = pfmh->hdpaAlt; } else { hdpaTemp = pfmh->hdpa; } if (hdpaTemp && pfmh->psf) { LPENUMIDLIST penum; LPSHELLFOLDER psfTemp; // Take care with Programs folder. // If this is the parent of the programs folder set pidlSkip to // the last bit of the programs pidl. if (pfmh->fmf & FMF_NOPROGRAMS) { pidlProgs = SHCloneSpecialIDList(NULL, (bUseAlt ? CSIDL_COMMON_PROGRAMS : CSIDL_PROGRAMS), TRUE); if (ILIsParent((bUseAlt ? pfmh->pidlAltFolder : pfmh->pidlFolder), pidlProgs, TRUE)) { TraceMsg(TF_MENU, "FileList_Build: Programs parent."); pidlSkip = ILFindLastID(pidlProgs); } } // Decide which shell folder to enumerate. if (bUseAlt) { psfTemp = pfmh->psfAlt; } else { psfTemp = pfmh->psf; } // We now need to iterate over the children under this guy... hres = psfTemp->EnumObjects(NULL, pfmh->fFSFilter, &penum); if (SUCCEEDED(hres)) { ULONG celt; LPITEMIDLIST pidl = NULL; // The pidl is stored away into the pfmi structure, so // don't free it here while (penum->Next(1, &pidl, &celt) == S_OK && celt == 1) { PFILEMENUITEM pfmi; // Abort. if (FileMenuHeader_AllowAbort(pfmh) && g_fAbortInitMenu) break; if (pidlSkip && psfTemp->CompareIDs(0, pidlSkip, pidl) == 0) { ILFree(pidl); // Don't leak this one... TraceMsg(DM_TRACE, "FileList_Build: Skipping Programs."); continue; } // Is there a list of extensions on which we need to // filter? if (pfmh->pszFilterTypes) { STRRET str; DWORD dwAttribs = SFGAO_FOLDER | SFGAO_FILESYSTEM; psfTemp->GetAttributesOf(1, (LPCITEMIDLIST*)&pidl, &dwAttribs); // only apply the filter to file system objects if ((dwAttribs & SFGAO_FILESYSTEM) && SUCCEEDED(psfTemp->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str))) { TCHAR szFile[MAX_PATH]; StrRetToBuf(&str, pidl, szFile, SIZECHARS(szFile)); if (!(dwAttribs & SFGAO_FOLDER)) { LPTSTR psz = pfmh->pszFilterTypes; LPTSTR pszExt = PathFindExtension(szFile); if (TEXT('.') == *pszExt) pszExt++; while (*psz) { // Skip this file? if (0 == lstrcmpi(pszExt, psz)) break; // No psz += lstrlen(psz) + 1; } if ( !*psz ) { ILFree(pidl); // don't leak this continue; } } } } if (FileMenuItem_Create(pfmh, pidl, -1, bUseAlt ? FMI_ALTITEM : 0, &pfmi)) { if (!bUseAlt) { // Set the allowable drop effects (as a target). // We don't allow common user items to be moved. pfmi->dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY; } int idpa = DPA_AppendPtr(hdpaTemp, pfmi); ASSERTMSG(idpa != -1, "DPA_AppendPtr failed when adding file menu item"); if (idpa != -1) { // NB We only callback for non-folders at the moment // // HACK don't callback for non file system things // this callback is used to set hotkeys, and that tries // to load the PIDL passed back as a file, and that doesn't // work for non FS pidls if (pfmh->pfncb && (pfmi->Flags & FMI_FILESYSTEM)) { FMCBDATA fmcbdata = { 0 }; fmcbdata.lParam = pfmh->lParam; fmcbdata.hmenu = pfmh->hmenu; fmcbdata.iPos = idpa; // Don't know the id because it hasn't been // added to the menu yet fmcbdata.idCmd = (UINT)-1; if (bUseAlt) { fmcbdata.pidlFolder = pfmh->pidlAltFolder; } else { fmcbdata.pidlFolder = pfmh->pidlFolder; } fmcbdata.pidl = pidl; fmcbdata.psf = psfTemp; fmcbdata.pvHeader = pfmh; // if the caller returns S_FALSE then we will remove the item from the // menu, otherwise we behave as before. if (pfmh->pfncb(FMM_ADD, &fmcbdata, 0) == S_FALSE) { FileMenuItem_Destroy(pfmi); DPA_DeletePtr(pfmh->hdpa, idpa); } else { cItems++; } } else { cItems++; } } } } penum->Release(); } else { TraceMsg(TF_ERROR, "FileList_Build: Enumeration failed - leaving folder empty."); } ILFree(pidlProgs); } // Insert a special Empty item (unless the header flag says // not to). if (!cItems && hdpaTemp && !(pfmh->fmf & FMF_NOEMPTYITEM) && !bUseAlt) { PFILEMENUITEM pfmi; if (FileMenuItem_Create(pfmh, NULL, -1, FMI_EMPTY, &pfmi)) { DPA_SetPtr(hdpaTemp, cItems, pfmi); cItems++; } } return cItems; } #define FS_SORTBYNAME 0 #define FS_SORTBYORDINAL 1 //--------------------------------------------------------------------------- // Simplified version of the file info comparison function. int CALLBACK FileMenuItem_Compare(LPVOID pv1, LPVOID pv2, LPARAM lParam) { PFILEMENUITEM pfmi1 = (PFILEMENUITEM)pv1; PFILEMENUITEM pfmi2 = (PFILEMENUITEM)pv2; int nRet; TCHAR szName1[MAX_PATH]; TCHAR szName2[MAX_PATH]; switch (lParam) { case FS_SORTBYNAME: // Directories come first, then files if ((pfmi1->Flags & FMI_FOLDER) > (pfmi2->Flags & FMI_FOLDER)) return -1; else if ((pfmi1->Flags & FMI_FOLDER) < (pfmi2->Flags & FMI_FOLDER)) return 1; FileMenuItem_GetDisplayName(pfmi1, szName1, ARRAYSIZE(szName1)); FileMenuItem_GetDisplayName(pfmi2, szName2, ARRAYSIZE(szName2)); nRet = lstrcmpi(szName1, szName2); break; case FS_SORTBYORDINAL: if (pfmi1->nOrder == pfmi2->nOrder) nRet = 0; else nRet = (pfmi1->nOrder < pfmi2->nOrder ? -1 : 1); break; default: ASSERT_MSG(0, "Bad lParam passed to FileMenuItem_Compare"); nRet = 0; break; } return nRet; } LPVOID CALLBACK FileMenuItem_Merge(UINT uMsg, LPVOID pvDest, LPVOID pvSrc, LPARAM lParam) { PFILEMENUITEM pfmiDest = (PFILEMENUITEM)pvDest; PFILEMENUITEM pfmiSrc = (PFILEMENUITEM)pvSrc; LPVOID pvRet = pfmiDest; switch (uMsg) { case DPAMM_MERGE: // We just care about the order field pfmiDest->nOrder = pfmiSrc->nOrder; break; case DPAMM_DELETE: case DPAMM_INSERT: // Don't need to implement this ASSERT(0); pvRet = NULL; break; } return pvRet; } // Header for file menu streams typedef struct tagFMSTREAMHEADER { DWORD cbSize; // Size of header DWORD dwVersion; // Version of header } FMSTREAMHEADER; #define FMSTREAMHEADER_VERSION 1 typedef struct tagFMSTREAMITEM { DWORD cbSize; // Size including pidl (not for versioning) int nOrder; // User-specified order } FMSTREAMITEM; #define CB_FMSTREAMITEM (sizeof(FMSTREAMITEM)) HRESULT CALLBACK FileMenuItem_SaveStream(DPASTREAMINFO * pinfo, IStream * pstm, LPVOID pvData) { // We only write menu items with pidls PFILEMENUITEM pfmi = (PFILEMENUITEM)pinfo->pvItem; HRESULT hres = S_FALSE; if (pfmi->pidl) { FMSTREAMITEM fmsi; ULONG cbWrite; ULONG cbWritePidl; // Size of header, pidl, and ushort for pidl size. fmsi.cbSize = CB_FMSTREAMITEM + pfmi->pidl->mkid.cb + sizeof(USHORT); fmsi.nOrder = pfmi->nOrder; hres = pstm->Write(&fmsi, CB_FMSTREAMITEM, &cbWrite); if (SUCCEEDED(hres)) { hres = pstm->Write(pfmi->pidl, pfmi->pidl->mkid.cb + sizeof(USHORT), &cbWritePidl); ASSERT(fmsi.cbSize == cbWrite + cbWritePidl); } } return hres; } HRESULT CALLBACK FileMenuItem_LoadStream(DPASTREAMINFO * pinfo, IStream * pstm, LPVOID pvData) { HRESULT hres; FMSTREAMITEM fmsi; ULONG cbRead; PFILEMENUHEADER pfmh = (PFILEMENUHEADER)pvData; ASSERT(pfmh); hres = pstm->Read(&fmsi, CB_FMSTREAMITEM, &cbRead); if (SUCCEEDED(hres)) { if (CB_FMSTREAMITEM != cbRead) hres = E_FAIL; else { ASSERT(CB_FMSTREAMITEM < fmsi.cbSize); if (CB_FMSTREAMITEM < fmsi.cbSize) { UINT cb = fmsi.cbSize - CB_FMSTREAMITEM; LPITEMIDLIST pidl = _ILCreate(cb); if ( !pidl ) hres = E_OUTOFMEMORY; else { hres = pstm->Read(pidl, cb, &cbRead); if (SUCCEEDED(hres) && cb == cbRead && IS_VALID_PIDL(pidl)) { PFILEMENUITEM pfmi; if (FileMenuItem_Create(pfmh, pidl, -1, 0, &pfmi)) { pfmi->nOrder = fmsi.nOrder; pinfo->pvItem = pfmi; hres = S_OK; } else hres = E_OUTOFMEMORY; } else hres = E_FAIL; // Cleanup if (FAILED(hres)) ILFree(pidl); } } else hres = E_FAIL; } } ASSERT((S_OK == hres && pinfo->pvItem) || FAILED(hres)); return hres; } int CALLBACK FileMenuItem_DestroyCB(LPVOID pv, LPVOID pvData) { return FileMenuItem_Destroy((PFILEMENUITEM)pv); } BOOL FileList_Load( IN PFILEMENUHEADER pfmh, OUT HDPA * phdpa, IN IStream * pstm) { HDPA hdpa = NULL; FMSTREAMHEADER fmsh; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); ASSERT(phdpa); ASSERT(pstm); // Read the header for more info if (SUCCEEDED(pstm->Read(&fmsh, sizeof(fmsh), NULL)) && sizeof(fmsh) == fmsh.cbSize && FMSTREAMHEADER_VERSION == fmsh.dwVersion) { // Load the stream. (Should be ordered by name.) DPA_LoadStream(&hdpa, FileMenuItem_LoadStream, pstm, pfmh); } *phdpa = hdpa; return (NULL != hdpa); } HRESULT FileList_Save( IN PFILEMENUHEADER pfmh, IN IStream * pstm) { HRESULT hres = E_OUTOFMEMORY; FMSTREAMHEADER fmsh; HDPA hdpa; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); ASSERT(pstm); // Clone the array and sort by name for the purpose of persisting it hdpa = DPA_Clone(pfmh->hdpa, NULL); if (hdpa) { DPA_Sort(hdpa, FileMenuItem_Compare, FS_SORTBYNAME); // Save the header fmsh.cbSize = sizeof(fmsh); fmsh.dwVersion = FMSTREAMHEADER_VERSION; hres = pstm->Write(&fmsh, sizeof(fmsh), NULL); if (SUCCEEDED(hres)) { hres = DPA_SaveStream(hdpa, FileMenuItem_SaveStream, pstm, pfmh); } DPA_Destroy(hdpa); } return hres; } void FileList_Reorder(PFILEMENUHEADER pfmh) { int i; int cel; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); BOOL bCantOrder = (NULL == pfmh->pstm); // Update the order fields. While we're at it, massage the // dwEffect field so it reflects whether something can be // ordered based on the stream (no stream means no reorder). cel = DPA_GetPtrCount(pfmh->hdpa); for (i = 0; i < cel; i++) { PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_FastGetPtr(pfmh->hdpa, i); pfmi->nOrder = i; if (bCantOrder) pfmi->dwEffect = DROPEFFECT_NONE; } } // Caller should release the stream after using it BOOL FileList_GetStream(PFILEMENUHEADER pfmh, IStream ** ppstm) { if (NULL == pfmh->pstm) { if (pfmh->pfncb) { FMGETSTREAM fmgs = { 0 }; FMCBDATA fmcbdata; fmcbdata.lParam = pfmh->lParam; fmcbdata.hmenu = pfmh->hmenu; fmcbdata.idCmd = pfmh->idCmd; fmcbdata.iPos = -1; fmcbdata.pidlFolder = pfmh->pidlFolder; fmcbdata.pidl = NULL; fmcbdata.psf = pfmh->psf; fmcbdata.pvHeader = pfmh; if (S_OK == pfmh->pfncb(FMM_GETSTREAM, &fmcbdata, (LPARAM)&fmgs) && fmgs.pstm) { // Cache this stream away pfmh->pstm = fmgs.pstm; } } } else { // Reset the seek pointer to beginning LARGE_INTEGER dlibMove = { 0 }; pfmh->pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); } if (pfmh->pstm) pfmh->pstm->AddRef(); *ppstm = pfmh->pstm; return (NULL != *ppstm); } void FileList_Sort( PFILEMENUHEADER pfmh) { IStream * pstm; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); // First sort by name DPA_Sort(pfmh->hdpa, FileMenuItem_Compare, FS_SORTBYNAME); // Can this menu be sorted by the user? if ((pfmh->fmf & FMF_CANORDER) && FileList_GetStream(pfmh, &pstm)) { // Yes; get the stream and try to load the order info HDPA hdpaOrder; // Read the order from the stream if (FileList_Load(pfmh, &hdpaOrder, pstm)) { // Sort the menu according to this stream's order. // The persisted order is by name. This reduces the number of // sorts to two at load-time, and 1 at save-time. (Persisting // by ordinal number means we sort three times at load-time, and // none at save-time. We want to speed up the initial menu // creation as much as possible.) // (Already sorted by name above) DPA_Merge(pfmh->hdpa, hdpaOrder, DPAM_SORTED, FileMenuItem_Compare, FileMenuItem_Merge, FS_SORTBYNAME); DPA_Sort(pfmh->hdpa, FileMenuItem_Compare, FS_SORTBYORDINAL); DPA_DestroyCallback(hdpaOrder, FileMenuItem_DestroyCB, NULL); } pstm->Release(); } FileList_Reorder(pfmh); } //--------------------------------------------------------------------------- // Use the text extent of the given item and the size of the image to work // what the full extent of the item will be. DWORD GetItemExtent(HDC hdc, PFILEMENUITEM pfmi) { TCHAR szName[MAX_PATH]; szName[0] = 0; ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName)); PFILEMENUHEADER pfmh = pfmi->pfmh; ASSERT(pfmh); // Limit the width of the text? if (0 < pfmh->cxMax) { // Yes PathCompactPath(hdc, szName, pfmh->cxMax); } DWORD dwExtent = GetItemTextExtent(hdc, szName); WORD wHeight = HIWORD(dwExtent); // If no custom height - calc it. if (!pfmi->cyItem) { if (pfmh->fmf & FMF_LARGEICONS) wHeight = max(wHeight, ((WORD)g_cyIcon)) + 2; else wHeight = max(wHeight, ((WORD)g_cySmIcon)) + pfmh->cySpacing; } else { wHeight = max(wHeight, pfmi->cyItem); } ASSERT(pfmi->pfmh); // string, image, gap on either side of image, popup triangle // and background bitmap if there is one. // FEATURE: popup triangle size needs to be real WORD wWidth = LOWORD(dwExtent) + GetSystemMetrics(SM_CXMENUCHECK); // Keep track of the width and height of the bitmap. if (pfmh->hbmp && !pfmh->cxBmp && !pfmh->cyBmp) { BITMAP bmp; GetObject(pfmh->hbmp, SIZEOF(bmp), &bmp); pfmh->cxBmp = bmp.bmWidth; pfmh->cyBmp = bmp.bmHeight; } // Gap for bitmap. wWidth += (WORD) pfmh->cxBmpGap; // Space for image if there is one. // NB We currently always allow room for the image even if there // isn't one so that imageless items line up properly. if (pfmh->fmf & FMF_LARGEICONS) wWidth += g_cxIcon + (2 * CXIMAGEGAP); else wWidth += g_cxSmIcon + (2 * CXIMAGEGAP); return MAKELONG(wWidth, wHeight); } /*---------------------------------------------------------- Purpose: Get the PFILEMENUITEM of this menu item Returns: Cond: -- */ PFILEMENUITEM FileMenu_GetItemData( IN HMENU hmenu, IN UINT iItem, IN BOOL bByPos) { MENUITEMINFO mii; mii.cbSize = SIZEOF(MENUITEMINFO); mii.fMask = MIIM_DATA | MIIM_STATE; mii.cch = 0; // just in case if (GetMenuItemInfo(hmenu, iItem, bByPos, &mii)) return (PFILEMENUITEM)mii.dwItemData; return NULL; } PFILEMENUHEADER FileMenu_GetHeader(HMENU hmenu) { PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); if (pfmi && EVAL(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)) && EVAL(IS_VALID_STRUCT_PTR(pfmi->pfmh, FILEMENUHEADER))) { return pfmi->pfmh; } return NULL; } /*---------------------------------------------------------- Purpose: Create a file menu header. This header is to be associated with the given menu handle. If the menu handle already has header, simply return the existing header. Returns: pointer to header NULL on failure */ PFILEMENUHEADER FileMenuHeader_Create( IN HMENU hmenu, IN HBITMAP hbmp, IN int cxBmpGap, IN COLORREF clrBkg, IN int cySel, IN const FMCOMPOSE * pfmc) OPTIONAL { PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); PFILEMENUHEADER pfmh; // Does this guy already have a header? if (pfmi) { // Yes; use it pfmh = pfmi->pfmh; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); } else { // Nope, create one now. pfmh = (PFILEMENUHEADER)LocalAlloc(LPTR, SIZEOF(FILEMENUHEADER)); if (pfmh) { // Keep track of the header. TraceMsg(TF_MENU, "Creating filemenu header for %#08x (%x)", hmenu, pfmh); pfmh->hdpa = DPA_Create(0); if (pfmh->hdpa == NULL) { LocalFree((HLOCAL)pfmh); pfmh = NULL; } else { pfmh->hmenu = hmenu; pfmh->hbmp = hbmp; pfmh->cxBmpGap = cxBmpGap; pfmh->clrBkg = clrBkg; pfmh->cySel = cySel; pfmh->cySpacing = 6; // default for small icons } } } if (pfmc && pfmh) { // Set additional values if (IsFlagSet(pfmc->dwMask, FMC_CALLBACK)) { pfmh->pfncb = pfmc->pfnCallback; pfmh->lParam = pfmc->lParam; } if (IsFlagSet(pfmc->dwMask, FMC_CYMAX)) pfmh->cyMax = pfmc->cyMax; if (IsFlagSet(pfmc->dwMask, FMC_CXMAX)) pfmh->cxMax = pfmc->cxMax; if (IsFlagSet(pfmc->dwMask, FMC_CYSPACING)) pfmh->cySpacing = pfmc->cySpacing; if (IsFlagSet(pfmc->dwMask, FMC_FILTERTYPES)) { // This is a double-null terminated string MultiSz_AllocCopy(pfmc->pszFilterTypes, &pfmh->pszFilterTypes); } } return pfmh; } /*---------------------------------------------------------- Purpose: Set info specific to a folder. Returns: Cond: -- */ BOOL FileMenuHeader_SetFolderInfo( IN PFILEMENUHEADER pfmh, IN const FMCOMPOSE * pfmc) { ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); ASSERT(pfmc); // Keep track of the header. pfmh->idCmd = pfmc->id; if (IsFlagSet(pfmc->dwMask, FMC_FILTER)) pfmh->fFSFilter = pfmc->dwFSFilter; if (IsFlagSet(pfmc->dwMask, FMC_CYMAX)) pfmh->cyMax = pfmc->cyMax; if (IsFlagSet(pfmc->dwMask, FMC_CXMAX)) pfmh->cxMax = pfmc->cxMax; if (IsFlagSet(pfmc->dwMask, FMC_CYSPACING)) pfmh->cySpacing = pfmc->cySpacing; if (IsFlagSet(pfmc->dwMask, FMC_FILTERTYPES)) MultiSz_AllocCopy(pfmc->pszFilterTypes, &pfmh->pszFilterTypes); if (pfmc->pidlFolder) { pfmh->pidlFolder = ILClone(pfmc->pidlFolder); if (pfmh->pidlFolder) { LPSHELLFOLDER psfDesktop; if (SUCCEEDED(SHGetDesktopFolder(&psfDesktop))) { if (SUCCEEDED(psfDesktop->BindToObject(pfmh->pidlFolder, NULL, IID_IShellFolder, (PVOID *)&pfmh->psf))) { return TRUE; } } ILFree(pfmh->pidlFolder); } } return FALSE; } /*---------------------------------------------------------- Purpose: Create the tooltip window Returns: Cond: -- */ BOOL FileMenuHeader_CreateTooltipWindow( IN PFILEMENUHEADER pfmh) { ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); // Check if we need to create the main tooltip window if (g_hwndTip) { if (IsWindow(g_hwndTip)) { TCHAR szClass[MAX_PATH]; GetClassName(g_hwndTip, szClass, ARRAYSIZE(szClass)); if (lstrcmpi(szClass, TOOLTIPS_CLASS) != 0) g_hwndTip = NULL; } else g_hwndTip = NULL; } if (!g_hwndTip) Tooltip_Create(&g_hwndTip); ASSERT(IS_VALID_HANDLE(g_hwndTip, WND)); return NULL != g_hwndTip; } //--------------------------------------------------------------------------- // Give the submenu a marker item so we can check it's a filemenu item // at initpopupmenu time. BOOL FileMenuHeader_InsertMarkerItem(PFILEMENUHEADER pfmh) { PFILEMENUITEM pfmi; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (FileMenuItem_Create(pfmh, NULL, -1, FMI_MARKER | FMI_EXPAND, &pfmi)) { DPA_SetPtr(pfmh->hdpa, 0, pfmi); FileMenuHeader_InsertItem(pfmh, 0, FMII_DEFAULT); return TRUE; } TraceMsg(TF_ERROR, "FileMenuHeader_InsertMarkerItem: Can't create marker item."); return FALSE; } /*---------------------------------------------------------- Purpose: This functions adds the given item (index into DPA) into the actual menu. Returns: Cond: -- */ BOOL FileMenuHeader_InsertItem( IN PFILEMENUHEADER pfmh, IN UINT iItem, IN FMIIFLAGS fFlags) { PFILEMENUITEM pfmi; UINT fMenu; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); // Normal item. pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, iItem); if (!pfmi) return FALSE; if (pfmi->Flags & FMI_ON_MENU) return FALSE; else pfmi->Flags |= FMI_ON_MENU; // The normal stuff. fMenu = MF_BYPOSITION|MF_OWNERDRAW; // Keep track of where it's going in the menu. // The special stuff... if (fFlags & FMII_BREAK) { fMenu |= MF_MENUBARBREAK; } // Is it a folder (that's not open yet)? if (pfmi->Flags & FMI_FOLDER) { // Yep. Create a submenu item. HMENU hmenuSub = CreatePopupMenu(); if (hmenuSub) { MENUITEMINFO mii; LPITEMIDLIST pidlSub; PFILEMENUHEADER pfmhSub; FMCOMPOSE fmc; // Set the callback now so it can be called when adding items fmc.cbSize = SIZEOF(fmc); fmc.dwMask = FMC_CALLBACK; fmc.lParam = pfmh->lParam; fmc.pfnCallback = pfmh->pfncb; // Insert it into the parent menu. fMenu |= MF_POPUP; InsertMenu(pfmh->hmenu, iItem, fMenu, (UINT_PTR)hmenuSub, (LPTSTR)pfmi); // Set it's ID. mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_ID; mii.wID = pfmh->idCmd; SetMenuItemInfo(pfmh->hmenu, iItem, TRUE, &mii); pidlSub = ILCombine((pfmi->Flags & FMI_ALTITEM) ? pfmh->pidlAltFolder : pfmh->pidlFolder, pfmi->pidl); pfmhSub = FileMenuHeader_Create(hmenuSub, NULL, 0, (COLORREF)-1, 0, &fmc); if (pfmhSub) { // Inherit settings from the parent filemenu fmc.dwMask = FMC_PIDL | FMC_FILTER | FMC_CYMAX | FMC_CXMAX | FMC_CYSPACING; fmc.id = pfmh->idCmd; fmc.pidlFolder = pidlSub; fmc.dwFSFilter = pfmh->fFSFilter; fmc.cyMax = pfmh->cyMax; fmc.cxMax = pfmh->cxMax; fmc.cySpacing = pfmh->cySpacing; if (pfmh->pszFilterTypes) { fmc.dwMask |= FMC_FILTERTYPES; fmc.pszFilterTypes = pfmh->pszFilterTypes; } FileMenuHeader_SetFolderInfo(pfmhSub, &fmc); // Magically inherit certain flags // FEATURE: (scotth): can we inherit all the bits? pfmhSub->fmf = pfmh->fmf & FMF_INHERITMASK; // Build it a bit at a time. FileMenuHeader_InsertMarkerItem(pfmhSub); } ILFree(pidlSub); } } else { // Nope. if (pfmi->Flags & FMI_EMPTY) fMenu |= MF_DISABLED | MF_GRAYED; InsertMenu(pfmh->hmenu, iItem, fMenu, pfmh->idCmd, (LPTSTR)pfmi); } return TRUE; } /*---------------------------------------------------------- Purpose: Remove the rest of the items from the main list starting at the given index. Returns: -- Cond: -- */ void FileList_StripLeftOvers( IN PFILEMENUHEADER pfmh, IN int idpaStart, IN BOOL bUseAlt) { int cItems; int i; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); cItems = DPA_GetPtrCount(pfmh->hdpa); // Do this backwards to stop things moving around as // we delete them. for (i = cItems - 1; i >= idpaStart; i--) { PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i); if (pfmi) { // Tell the callback we're removing this if (pfmh->pfncb && pfmi->pidl && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL)) { FMCBDATA fmcbdata; fmcbdata.lParam = pfmh->lParam; fmcbdata.hmenu = pfmh->hmenu; fmcbdata.iPos = i; fmcbdata.idCmd = GetMenuItemID(pfmh->hmenu, i); if (bUseAlt) { fmcbdata.pidlFolder = pfmh->pidlAltFolder; fmcbdata.psf = pfmh->psfAlt; } else { fmcbdata.pidlFolder = pfmh->pidlFolder; fmcbdata.psf = pfmh->psf; } fmcbdata.pidl = pfmi->pidl; fmcbdata.pvHeader = pfmh; pfmh->pfncb(FMM_REMOVE, &fmcbdata, 0); } // (We don't need to worry about recursively deleting // subfolders because their contents haven't been added yet.) // Delete the item itself (note there is no menu item // to delete) FileMenuItem_Destroy(pfmi); DPA_DeletePtr(pfmh->hdpa, i); } } } /*---------------------------------------------------------- Purpose: This function adds a "More Items..." menu item at the bottom of the menu. It calls the callback to get the string. Returns: Cond: -- */ void FileMenuHeader_AddMoreItemsItem( IN PFILEMENUHEADER pfmh, IN UINT iPos) { PFILEMENUITEM pfmi; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (NULL == pfmh->pfncb) { // (scotth): this shouldn't be required, but we don't // have a default resource ID for this right now. TraceMsg(TF_ERROR, "Need a callback in order to add a More item."); ASSERT(0); } else if (FileMenuItem_Create(pfmh, NULL, -1, 0, &pfmi)) { FMCBDATA fmcbdata; FMMORESTRING fmms = {0}; // Make the pidl be the whole path to the folder pfmi->pidl = ILClone(pfmh->pidlFolder); pfmi->Flags |= FMI_IGNORE_PIDL; fmcbdata.hmenu = pfmh->hmenu; fmcbdata.iPos = -1; fmcbdata.idCmd = (UINT)-1; // (scotth): we don't ask for string for alternate lists fmcbdata.pidlFolder = NULL; fmcbdata.pidl = pfmi->pidl; fmcbdata.psf = pfmh->psf; // Was a string set? if (S_OK == pfmh->pfncb(FMM_GETMORESTRING, &fmcbdata, (LPARAM)&fmms)) { Sz_AllocCopy(fmms.szMoreString, &(pfmi->psz)); if (DPA_SetPtr(pfmh->hdpa, iPos, pfmi)) { MENUITEMINFO mii; // Set the command ID mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_DATA; mii.wID = fmms.uID; mii.fType = MFT_OWNERDRAW; mii.dwItemData = (DWORD_PTR)pfmi; EVAL(InsertMenuItem(pfmh->hmenu, iPos, TRUE, &mii)); } } } } /*---------------------------------------------------------- Purpose: Enumerates the DPA and adds each item into the menu. Inserts vertical breaks if the menu becomes too long. Returns: count of items added to menu Cond: -- */ int FileList_AddToMenu( IN PFILEMENUHEADER pfmh, IN BOOL bUseAlt, IN BOOL bAddSeparatorSpace) { UINT i, cItems; int cItemMac = 0; PFILEMENUITEM pfmi; int cyMenu, cyItem, cyMenuMax; HDC hdc; HFONT hfont, hfontOld; NONCLIENTMETRICS ncm; int idpa; HDPA hdpaT; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (bUseAlt) hdpaT = pfmh->hdpaAlt; else hdpaT = pfmh->hdpa; if (hdpaT) { cyItem = 0; cyMenu = pfmh->cyMenuSizeSinceLastBreak; if (0 < pfmh->cyMax) cyMenuMax = pfmh->cyMax; else cyMenuMax = GetSystemMetrics(SM_CYSCREEN); // Get the rough height of an item so we can work out when to break the // menu. User should really do this for us but that would be useful. hdc = GetDC(NULL); if (hdc) { ncm.cbSize = SIZEOF(NONCLIENTMETRICS); if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SIZEOF(ncm), &ncm, FALSE)) { hfont = CreateFontIndirect(&ncm.lfMenuFont); if (hfont) { hfontOld = SelectFont(hdc, hfont); cyItem = HIWORD(GetItemExtent(hdc, (PFILEMENUITEM)DPA_GetPtr(hdpaT, 0))); SelectObject(hdc, hfontOld); DeleteObject(hfont); } } ReleaseDC(NULL, hdc); } // If we are appending items to a menu, we need to account // for the separator. if (bAddSeparatorSpace) { cyMenu += cyItem; } cItems = DPA_GetPtrCount(hdpaT); for (i = 0; i < cItems; i++) { if (bUseAlt) { // Move the items from the alternate list to the main // list and use the new index. pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpaAlt, i); if (!pfmi) continue; idpa = DPA_AppendPtr(pfmh->hdpa, pfmi); } else { idpa = i; } // Keep a rough count of the height of the menu. cyMenu += cyItem; if (cyMenu > cyMenuMax) { // Add a vertical break? if ( !(pfmh->fmf & (FMF_NOBREAK | FMF_RESTRICTHEIGHT)) ) { // Yes FileMenuHeader_InsertItem(pfmh, idpa, FMII_BREAK); cyMenu = cyItem; } // Restrict height? else if (IsFlagSet(pfmh->fmf, FMF_RESTRICTHEIGHT)) { // Yes; remove the remaining items from the list FileList_StripLeftOvers(pfmh, idpa, bUseAlt); // (so cyMenuSizeSinceLastBreak is accurate) cyMenu -= cyItem; // Add a "more..." item at the end? if (pfmh->fmf & FMF_MOREITEMS) { // Yes FileMenuHeader_AddMoreItemsItem(pfmh, idpa); } // We won't go any further break; } } else { FileMenuHeader_InsertItem(pfmh, idpa, FMII_DEFAULT); cItemMac++; } } // Save the current cy size so we can use this again // if more items are appended to this menu. pfmh->cyMenuSizeSinceLastBreak = cyMenu; } return cItemMac; } BOOL FileList_AddImages( IN PFILEMENUHEADER pfmh, IN BOOL bUseAlt) { PFILEMENUITEM pfmi; int i, cItems; HDPA hdpaTemp; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (bUseAlt) { hdpaTemp = pfmh->hdpaAlt; } else { hdpaTemp = pfmh->hdpa; } cItems = DPA_GetPtrCount(hdpaTemp); for (i = 0; i < cItems; i++) { if (FileMenuHeader_AllowAbort(pfmh) && g_fAbortInitMenu) { TraceMsg(TF_MENU, "FileList_AddImages: Abort: Defering images till later."); break; } pfmi = (PFILEMENUITEM)DPA_GetPtr(hdpaTemp, i); if (pfmi && pfmi->pidl && (pfmi->iImage == -1) && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL)) { pfmi->iImage = SHMapPIDLToSystemImageListIndex( (bUseAlt ? pfmh->psfAlt : pfmh->psf), pfmi->pidl, NULL); } } return TRUE; } //--------------------------------------------------------------------------- BOOL FileMenuItem_Destroy(PFILEMENUITEM pfmi) { BOOL fRet = FALSE; ASSERT(NULL == pfmi || IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); if (pfmi) { if (pfmi->pidl) ILFree(pfmi->pidl); if (pfmi->psz) LFree(pfmi->psz); if (pfmi->pszTooltip) LFree(pfmi->pszTooltip); LocalFree(pfmi); fRet = TRUE; } return fRet; } //--------------------------------------------------------------------------- // Clean up the items created by FileList_Build; void FileList_UnBuild(PFILEMENUHEADER pfmh) { int cItems; int i; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); cItems = DPA_GetPtrCount(pfmh->hdpa); for (i=cItems-1; i>=0; i--) { PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i); if (FileMenuItem_Destroy(pfmi)) DPA_DeletePtr(pfmh->hdpa, i); } } // Flags for FileMenuHeader_AddFiles #define FMHAF_USEALT 0x0001 #define FMHAF_SEPARATOR 0x0002 //--------------------------------------------------------------------------- // Add menu items from an IContextMenu handler. HRESULT FileMenuHeader_AddFromContextMenu(PFILEMENUHEADER pfmh, HKEY hk) { // enumerate the key and create each of the context menu handlers, for that // we can then add the entries. return S_OK; } /*---------------------------------------------------------- Purpose: Add files to a file menu header. This function goes thru the following steps: - enumerates the folder and fills the hdpa list with items (files and subfolders) - sorts the list - gets the images for the items in the list - adds the items from list into actual menu The last step also (optionally) caps the length of the menu to the specified height. Ideally, this should happen at the enumeration time, except the required sort prevents this from happening. So we end up adding a bunch of items to the list and then removing them if there are too many. Returns: count of items added -1 if aborted Cond: -- */ HRESULT FileMenuHeader_AddFiles( IN PFILEMENUHEADER pfmh, IN int iPos, IN UINT uFlags, // FMHAF_* OUT int * pcItems) { HRESULT hres; BOOL bUseAlt = IsFlagSet(uFlags, FMHAF_USEALT); ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); int cItems = FileList_Build(pfmh, iPos, bUseAlt); // If the build was aborted cleanup and early out. if (FileMenuHeader_AllowAbort(pfmh) && g_fAbortInitMenu) { // Cleanup. TraceMsg(TF_MENU, "FileList_Build aborted."); FileList_UnBuild(pfmh); hres = E_ABORT; *pcItems = -1; } else { *pcItems = cItems; if (cItems > 1) FileList_Sort(pfmh); if (cItems != 0) { BOOL bSeparator = IsFlagSet(uFlags, FMHAF_SEPARATOR); if (bSeparator) { // insert a line FileMenu_AppendItem(pfmh->hmenu, (LPTSTR)FMAI_SEPARATOR, 0, -1, NULL, 0); } // Add the images *after* adding to the menu, since the menu // may be capped to a maximum height, and we can then prevent // adding images we won't need. *pcItems = FileList_AddToMenu(pfmh, bUseAlt, bSeparator); FileList_AddImages(pfmh, bUseAlt); } hres = (*pcItems < cItems) ? S_FALSE : S_OK; } if (g_fAbortInitMenu) g_fAbortInitMenu = FALSE; TraceMsg(TF_MENU, "FileMenuHeader_AddFiles: Added %d filemenu items.", cItems); return hres; } //---------------------------------------------------------------------------- // Free up a header (you should delete all the items first). void FileMenuHeader_Destroy(PFILEMENUHEADER pfmh) { ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); TraceMsg(TF_MENU, "Destroy filemenu for (%x)", pfmh); // Clean up the header. DPA_Destroy(pfmh->hdpa); if (pfmh->pidlFolder) { ILFree(pfmh->pidlFolder); pfmh->pidlFolder = NULL; } if (pfmh->psf) { pfmh->psf->Release(); pfmh->psf = NULL; } if (pfmh->pstm) { pfmh->pstm->Release(); pfmh->pstm = NULL; } if (pfmh->pidlAltFolder) { ILFree(pfmh->pidlAltFolder); pfmh->pidlAltFolder = NULL; } if (pfmh->psfAlt) { pfmh->psfAlt->Release(); pfmh->psfAlt = NULL; } if (pfmh->pszFilterTypes) { LFree(pfmh->pszFilterTypes); pfmh->pszFilterTypes = NULL; } LocalFree((HLOCAL)pfmh); // needed? } //--------------------------------------------------------------------------- // We create subemnu's with one marker item so we can check it's a file menu // at init popup time but we need to delete it before adding new items. BOOL FileMenuHeader_DeleteMarkerItem(PFILEMENUHEADER pfmh) { ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); // It should just be the only one in the menu. if (GetMenuItemCount(pfmh->hmenu) == 1) { // It should have the right id. if (GetMenuItemID(pfmh->hmenu, 0) == pfmh->idCmd) { // With item data and the marker flag set. PFILEMENUITEM pfmi = FileMenu_GetItemData(pfmh->hmenu, 0, TRUE); if (pfmi && (pfmi->Flags & FMI_MARKER)) { // Delete it. ASSERT(pfmh->hdpa); ASSERT(DPA_GetPtrCount(pfmh->hdpa) == 1); // NB The marker shouldn't have a pidl. ASSERT(!pfmi->pidl); LocalFree((HLOCAL)pfmi); DPA_DeletePtr(pfmh->hdpa, 0); DeleteMenu(pfmh->hmenu, 0, MF_BYPOSITION); // Cleanup OK. return TRUE; } } } TraceMsg(TF_MENU, "Can't find marker item."); return FALSE; } /*---------------------------------------------------------- Purpose: Add files to this menu. Returns: number of items added Cond: -- */ HRESULT FileMenu_AddFiles( IN HMENU hmenu, IN UINT iPos, IN OUT FMCOMPOSE * pfmc) { HRESULT hres = E_OUTOFMEMORY; BOOL fMarker = FALSE; PFILEMENUHEADER pfmh; // NOTE: this function takes in FMCOMPOSE, which can be A or W // version depending on the platform. Since the function // is internal, wrapped by FileMenu_ComposeA/W, it expects // the pidl to be valid, and will not use the pszFolder field. if (IsFlagClear(pfmc->dwMask, FMC_FILTER)) pfmc->dwFSFilter = 0; if (IsFlagClear(pfmc->dwMask, FMC_FLAGS)) pfmc->dwFlags = 0; // (FileMenuHeader_Create might return an existing header) pfmh = FileMenuHeader_Create(hmenu, NULL, 0, (COLORREF)-1, 0, pfmc); if (pfmh) { PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); if (pfmi) { // Clean up marker item if there is one. if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND)) { // Nope, do it now. TraceMsg(TF_MENU, "Removing marker item."); FileMenuHeader_DeleteMarkerItem(pfmh); fMarker = TRUE; if (iPos) iPos--; } } // Add the new stuff FileMenuHeader_SetFolderInfo(pfmh, pfmc); // Tack on more flags pfmh->fmf |= pfmc->dwFlags; SetFlag(pfmh->fmf, FMF_NOABORT); hres = FileMenuHeader_AddFiles(pfmh, iPos, 0, &pfmc->cItems); ClearFlag(pfmh->fmf, FMF_NOABORT); if ((E_ABORT == hres || 0 == pfmc->cItems) && fMarker) { // Aborted or no items. Put the marker back (if there used // to be one). FileMenuHeader_InsertMarkerItem(pfmh); } } return hres; } //--------------------------------------------------------------------------- // Returns the number of items added. STDAPI_(UINT) FileMenu_AppendFilesForPidl( HMENU hmenu, LPITEMIDLIST pidl, BOOL bInsertSeparator) { int cItems = 0; BOOL fMarker = FALSE; PFILEMENUHEADER pfmh; PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); ASSERT(IS_VALID_HANDLE(hmenu, MENU)); ASSERT(IS_VALID_PIDL(pidl)); // // Get the filemenu header from the first filemenu item // if (!pfmi) return 0; pfmh = pfmi->pfmh; if (pfmh) { // Clean up marker item if there is one. if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND)) { // Nope, do it now. // TraceMsg(DM_TRACE, "t.fm_ii: Removing marker item."); FileMenuHeader_DeleteMarkerItem(pfmh); fMarker = TRUE; } // Add the new stuff. if (pidl) { LPSHELLFOLDER psfDesktop; if (SUCCEEDED(SHGetDesktopFolder(&psfDesktop))) { pfmh->pidlAltFolder = ILClone(pidl); if (pfmh->pidlAltFolder) { pfmh->hdpaAlt = DPA_Create(0); if (pfmh->hdpaAlt) { if (SUCCEEDED(psfDesktop->BindToObject(pfmh->pidlAltFolder, NULL, IID_IShellFolder, (LPVOID *)&pfmh->psfAlt))) { UINT uFlags = FMHAF_USEALT; if (bInsertSeparator) uFlags |= FMHAF_SEPARATOR; pfmh->fmf |= FMF_NOABORT; FileMenuHeader_AddFiles(pfmh, 0, uFlags, &cItems); pfmh->fmf = pfmh->fmf & ~FMF_NOABORT; } DPA_Destroy (pfmh->hdpaAlt); pfmh->hdpaAlt = NULL; } } // we assume this is a static object... which it is. // psfDesktop->Release(); } } if (cItems <= 0 && fMarker) { // Aborted or no item s. Put the marker back (if there used // to be one). FileMenuHeader_InsertMarkerItem(pfmh); } } return cItems; } //--------------------------------------------------------------------------- // Delete all the menu items listed in the given header. UINT FileMenuHeader_DeleteAllItems( IN PFILEMENUHEADER pfmh) { int i; int cItems = 0; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)) { // Notify. if (pfmh->pfncb) { FMCBDATA fmcbdata; fmcbdata.lParam = pfmh->lParam; fmcbdata.hmenu = pfmh->hmenu; fmcbdata.iPos = 0; fmcbdata.idCmd = (UINT)-1; fmcbdata.pidlFolder = pfmh->pidlFolder; fmcbdata.pidl = NULL; fmcbdata.psf = pfmh->psf; fmcbdata.pvHeader = pfmh; pfmh->pfncb(FMM_DELETEALL, &fmcbdata, 0); } // Clean up the items. cItems = DPA_GetPtrCount(pfmh->hdpa); // Do this backwards to stop things moving around as // we delete them. for (i = cItems - 1; i >= 0; i--) { PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i); if (pfmi) { // Does this item have a subfolder? if (pfmi->Flags & FMI_FOLDER) { // Yep. // Get the submenu for this item. // Delete all it's items. FileMenu_DeleteAllItems(GetSubMenu(pfmh->hmenu, i)); } // Delete the item itself. DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION); FileMenuItem_Destroy(pfmi); DPA_DeletePtr(pfmh->hdpa, i); } } } return cItems; } //--------------------------------------------------------------------------- // NB The creator of the filemenu has to explicitly call FileMenu_DAI to free // up FileMenu items because USER doesn't send WM_DELETEITEM for ownerdraw // menu. Great eh? // Returns the number of items deleted. UINT FileMenu_DeleteAllItems(HMENU hmenu) { PFILEMENUHEADER pfmh; if (!IsMenu(hmenu)) return 0; // need to set this guy back to NULL, since it's no longer valid after // we delete the menu items. g_pfmiLastSelNonFolder = NULL; pfmh = FileMenu_GetHeader(hmenu); if (pfmh) { ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); // Save the order if necessary if (IsFlagSet(pfmh->fmf, FMF_DIRTY | FMF_CANORDER)) { FileMenu_SaveOrder(pfmh->hmenu); ClearFlag(pfmh->fmf, FMF_DIRTY); } UINT cItems = FileMenuHeader_DeleteAllItems(pfmh); FileMenuHeader_Destroy(pfmh); return cItems; } return 0; } //--------------------------------------------------------------------------- STDAPI_(void) FileMenu_Destroy(HMENU hmenu) { TraceMsg(TF_MENU, "Destroying filemenu for %#08x", hmenu); FileMenu_DeleteAllItems(hmenu); DestroyMenu(hmenu); // Reset the menu tracking agent g_fsmenuagent.Reset(); // // Delete current global g_hdcMem and g_hfont so they'll be // refreshed with current font metrics next time the menu size // is calculated. This is needed in case the menu is being destroyed // as part of a system metrics change. // DeleteGlobalMemDCAndFont(); } //--------------------------------------------------------------------------- // Cause the given filemenu to be rebuilt. STDAPI_(void) FileMenu_Invalidate(HMENU hmenu) { ASSERT(IS_VALID_HANDLE(hmenu, MENU)); // Is this a filemenu? // NB First menu item must be a FileMenuItem. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); if (pfmi) { ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); // Yep, Is there already a marker here? if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND)) { TraceMsg(TF_MENU, "Menu is already invalid."); } else if (pfmi->pfmh) { PFILEMENUHEADER pfmhSave = pfmi->pfmh; FileMenuHeader_DeleteAllItems(pfmi->pfmh); ASSERT(IS_VALID_STRUCT_PTR(pfmhSave, FILEMENUHEADER)); // above call freed pfmi FileMenuHeader_InsertMarkerItem(pfmhSave); } } } //--------------------------------------------------------------------------- // Cause the given filemenu to be marked invalid but don't delete any items // yet. void FileMenu_DelayedInvalidate(HMENU hmenu) { // Is this a filemenu? // NB First menu item must be a FileMenuItem. PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); if (pfmi && pfmi->pfmh) SetFlag(pfmi->pfmh->fmf, FMF_DELAY_INVALID); } BOOL FileMenu_IsDelayedInvalid(HMENU hmenu) { PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); return (pfmi && pfmi->pfmh && IsFlagSet(pfmi->pfmh->fmf, FMF_DELAY_INVALID)); } /*---------------------------------------------------------- Purpose: Compose a file menu. Ansi version Returns: S_OK if all the files were added S_FALSE if some did not get added (reached cyMax) error on something bad Cond: -- */ STDAPI FileMenu_ComposeA( IN HMENU hmenu, IN UINT nMethod, IN FMCOMPOSEA * pfmc) { HRESULT hres = E_INVALIDARG; if (IS_VALID_WRITE_PTR(pfmc, FMCOMPOSEA) && SIZEOF(*pfmc) == pfmc->cbSize) { FMCOMPOSEA fmc; fmc = *pfmc; if (IsFlagSet(fmc.dwMask, FMC_STRING)) { // Convert string to pidl TCHAR szFolder[MAX_PATH]; #ifdef UNICODE MultiByteToWideChar(CP_ACP, 0, fmc.pszFolder, -1, szFolder, SIZECHARS(szFolder)); #else lstrcpy(szFolder, fmc.pszFolder); #endif fmc.pidlFolder = ILCreateFromPath(szFolder); if (NULL == fmc.pidlFolder) { hres = E_OUTOFMEMORY; goto Bail; } } else if (IsFlagClear(fmc.dwMask, FMC_PIDL)) { // Either FMC_PIDL or FMC_STRING must be set hres = E_INVALIDARG; goto Bail; } switch (nMethod) { case FMCM_INSERT: hres = FileMenu_AddFiles(hmenu, 0, (FMCOMPOSE *)&fmc); break; case FMCM_APPEND: hres = FileMenu_AddFiles(hmenu, GetMenuItemCount(hmenu), (FMCOMPOSE *)&fmc); break; case FMCM_REPLACE: FileMenu_DeleteAllItems(hmenu); hres = FileMenu_AddFiles(hmenu, 0, (FMCOMPOSE *)&fmc); break; default: ASSERT(0); goto Bail; } pfmc->cItems = fmc.cItems; Bail: // Cleanup if (IsFlagSet(fmc.dwMask, FMC_STRING) && fmc.pidlFolder) ILFree(fmc.pidlFolder); } return hres; } /*---------------------------------------------------------- Purpose: Compose a file menu. Unicode version Returns: Cond: -- */ STDAPI FileMenu_ComposeW( IN HMENU hmenu, IN UINT nMethod, IN FMCOMPOSEW * pfmc) { HRESULT hres = E_INVALIDARG; if (IS_VALID_WRITE_PTR(pfmc, FMCOMPOSEW) && SIZEOF(*pfmc) == pfmc->cbSize) { FMCOMPOSEW fmc; fmc = *pfmc; if (IsFlagSet(fmc.dwMask, FMC_STRING)) { // Convert string to pidl TCHAR szFolder[MAX_PATH]; #ifdef UNICODE lstrcpy(szFolder, fmc.pszFolder); #else WideCharToMultiByte(CP_ACP, 0, fmc.pszFolder, -1, szFolder, SIZECHARS(szFolder), NULL, NULL); #endif fmc.pidlFolder = ILCreateFromPath(szFolder); if (NULL == fmc.pidlFolder) { hres = E_OUTOFMEMORY; goto Bail; } } else if (IsFlagClear(fmc.dwMask, FMC_PIDL)) { // Either FMC_PIDL or FMC_STRING must be set hres = E_INVALIDARG; goto Bail; } switch (nMethod) { case FMCM_INSERT: hres = FileMenu_AddFiles(hmenu, 0, (FMCOMPOSE *)&fmc); break; case FMCM_APPEND: hres = FileMenu_AddFiles(hmenu, GetMenuItemCount(hmenu), (FMCOMPOSE *)&fmc); break; case FMCM_REPLACE: FileMenu_DeleteAllItems(hmenu); hres = FileMenu_AddFiles(hmenu, 0, (FMCOMPOSE *)&fmc); break; default: ASSERT(0); goto Bail; } pfmc->cItems = fmc.cItems; Bail: // Cleanup if (IsFlagSet(fmc.dwMask, FMC_STRING) && fmc.pidlFolder) ILFree(fmc.pidlFolder); } return hres; } LRESULT FileMenu_DrawItem(HWND hwnd, DRAWITEMSTRUCT *pdi) { int y, x; TCHAR szName[MAX_PATH]; DWORD dwExtent; int cxIcon, cyIcon; RECT rcBkg; HBRUSH hbrOld = NULL; UINT cyItem, dyItem; HIMAGELIST himl; RECT rcClip; if ((pdi->itemAction & ODA_SELECT) || (pdi->itemAction & ODA_DRAWENTIRE)) { PFILEMENUHEADER pfmh; PFILEMENUITEM pfmi = (PFILEMENUITEM)pdi->itemData; IShellFolder * psf; #ifndef UNIX ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); #endif if (!pfmi) { TraceMsg(TF_ERROR, "FileMenu_DrawItem: Filemenu is invalid (no item data)."); return FALSE; } pfmh = pfmi->pfmh; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (pfmi->Flags & FMI_ALTITEM) psf = pfmh->psfAlt; else psf = pfmh->psf; // Adjust for large/small icons. if (pfmh->fmf & FMF_LARGEICONS) { cxIcon = g_cxIcon; cyIcon = g_cyIcon; } else { cxIcon = g_cxSmIcon; cyIcon = g_cxSmIcon; } // Is the menu just starting to get drawn? if (pdi->itemAction & ODA_DRAWENTIRE) { if (pfmi == DPA_GetPtr(pfmh->hdpa, 0)) { // Yes; reset the last selection item g_pfmiLastSelNonFolder = NULL; g_pfmiLastSel = NULL; // Initialize to handle drag and drop? if (pfmh->fmf & FMF_CANORDER) { // Yes g_fsmenuagent.Init(); } } } if (pdi->itemState & ODS_SELECTED) { if (pfmh->fmf & FMF_CANORDER) { // Pass on the current hDC and selection rect so the // drag/drop hook can actively draw RECT rc = pdi->rcItem; hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUTEXT)); // With no background image, the caret goes all the way // across; otherwise it stops in line with the bitmap. if (pfmh->hbmp) rc.left += pfmh->cxBmpGap; g_fsmenuagent.SetCurrentRect(pdi->hDC, &rc); g_fsmenuagent.SetItem(pfmi); // Are we in edit mode? if (MenuDD_IsButtonDown()) { // Yes g_fsmenuagent.SetEditMode(TRUE, DROPEFFECT_MOVE); } } // Determine the selection colors // // Normal menu colors apply until we are in edit mode, in which // case the menu item is drawn unselected and an insertion caret // is drawn above or below the current item. The exception is // if the item is a cascaded menu item, then we draw it // normally, but also show the insertion caret. (We do this // because Office does this, and also, USER draws the arrow // in the selected color always, so it looks kind of funny // if we don't select the menu item.) // // Is the user dragging and dropping and we're not over // a cascaded menu item? if ((pfmh->fmf & FMF_CANORDER) && MenuDD_InEditMode() && !(pfmi->Flags & FMI_FOLDER)) { // Yes; show the item in the unselected colors // (dwRop = SRCAND) hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUTEXT)); } else { // No SetBkColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHT)); SetTextColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_HIGHLIGHTTEXT)); } // REVIEW HACK NB - keep track of the last selected item. // NB The keyboard handler needs to know about all selections // but the WM_COMMAND stuff only cares about non-folders. g_pfmiLastSel = pfmi; if (!(pfmi->Flags & FMI_FOLDER)) g_pfmiLastSelNonFolder = pfmi; // Get the rect of the item in screen coords. g_rcItem = pdi->rcItem; MapWindowPoints(WindowFromDC(pdi->hDC), NULL, (LPPOINT)&g_rcItem, 2); } else { // dwRop = SRCAND; hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUTEXT)); } // Initial start pos. x = pdi->rcItem.left+CXIMAGEGAP; // Draw the background image. if (pfmh->hbmp) { // Draw it the first time the first item paints. if (pfmi == DPA_GetPtr(pfmh->hdpa, 0) && (pdi->itemAction & ODA_DRAWENTIRE)) { if (!g_hdcMem) { g_hdcMem = CreateCompatibleDC(pdi->hDC); ASSERT(g_hdcMem); } if (g_hdcMem) { HBITMAP hbmOld; if (!pfmh->yBmp) { GetClipBox(pdi->hDC, &rcClip); pfmh->yBmp = rcClip.bottom; } hbmOld = SelectBitmap(g_hdcMem, pfmh->hbmp); BitBlt(pdi->hDC, 0, pfmh->yBmp-pfmh->cyBmp, pfmh->cxBmp, pfmh->cyBmp, g_hdcMem, 0, 0, SRCCOPY); SelectBitmap(g_hdcMem, hbmOld); } } x += pfmh->cxBmpGap; } // Background color for when the bitmap runs out. if ((pfmh->clrBkg != (COLORREF)-1) && (pfmi == DPA_GetPtr(pfmh->hdpa, 0)) && (pdi->itemAction & ODA_DRAWENTIRE)) { HBRUSH hbr; if (!pfmh->yBmp) { GetClipBox(pdi->hDC, &rcClip); pfmh->yBmp = rcClip.bottom; } rcBkg.top = 0; rcBkg.left = 0; rcBkg.bottom = pfmh->yBmp - pfmh->cyBmp; rcBkg.right = max(pfmh->cxBmp, pfmh->cxBmpGap); hbr = CreateSolidBrush(pfmh->clrBkg); if (hbr) { FillRect(pdi->hDC, &rcBkg, hbr); DeleteObject(hbr); } } // Special case the separator. if (pfmi->Flags & FMI_SEPARATOR) { // With no background image it goes all the way across otherwise // it stops in line with the bitmap. if (pfmh->hbmp) pdi->rcItem.left += pfmh->cxBmpGap; pdi->rcItem.bottom = (pdi->rcItem.top+pdi->rcItem.bottom)/2; DrawEdge(pdi->hDC, &pdi->rcItem, EDGE_ETCHED, BF_BOTTOM); // Early out. goto ExitProc; } // Have the selection not include the icon to speed up drawing while // tracking. pdi->rcItem.left += pfmh->cxBmpGap; // Get the name. FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName)); // Limit the width of the text? if (0 < pfmh->cxMax) { // Yes PathCompactPath(pdi->hDC, szName, pfmh->cxMax); } // NB Keep a plain copy of the name for testing and accessibility. if (!pfmi->psz) Sz_AllocCopy(szName, &(pfmi->psz)); dwExtent = GetItemTextExtent(pdi->hDC, szName); y = (pdi->rcItem.bottom+pdi->rcItem.top-HIWORD(dwExtent))/2; // Support custom heights for the selection rectangle. if (pfmh->cySel) { cyItem = pdi->rcItem.bottom-pdi->rcItem.top; // Is there room? if ((cyItem > pfmh->cySel) && (pfmh->cySel > HIWORD(dwExtent))) { dyItem = (cyItem-pfmh->cySel)/2; pdi->rcItem.top += dyItem ; pdi->rcItem.bottom -= dyItem; } } else if(!(pfmh->fmf & FMF_LARGEICONS)) { // Shrink the selection rect for small icons a bit. pdi->rcItem.top += 1; pdi->rcItem.bottom -= 1; } // Draw the text. int fDSFlags; if (pfmi->Flags & FMI_IGNORE_PIDL) { // // If the string is not coming from a pidl, // we can format the menu text. // fDSFlags = DST_PREFIXTEXT; } else if ((pfmi->Flags & FMI_ON_MENU) == 0) { // // Norton Desktop Navigator 95 replaces the Start->&Run // menu item with a &Run pidl. Even though the text is // from a pidl, we still want to format the "&R" correctly. // fDSFlags = DST_PREFIXTEXT; } else { // // All other strings coming from pidls are displayed // as is to preserve any & in their display name. // fDSFlags = DST_TEXT; } if ((pfmi->Flags & FMI_EMPTY) || (pfmi->Flags & FMI_DISABLED)) { if (pdi->itemState & ODS_SELECTED) { if (GetSysColor(COLOR_GRAYTEXT) == GetSysColor(COLOR_HIGHLIGHTTEXT)) { fDSFlags |= DSS_UNION; } else { SetTextColor(pdi->hDC, GetSysColor(COLOR_GRAYTEXT)); } } else { fDSFlags |= DSS_DISABLED; } ExtTextOut(pdi->hDC, 0, 0, ETO_OPAQUE, &pdi->rcItem, NULL, 0, NULL); DrawState(pdi->hDC, NULL, NULL, (LONG_PTR)szName, lstrlen(szName), x+cxIcon+CXIMAGEGAP, y, 0, 0, fDSFlags); } else { ExtTextOut(pdi->hDC, x+cxIcon+CXIMAGEGAP, y, ETO_OPAQUE, &pdi->rcItem, NULL, 0, NULL); DrawState(pdi->hDC, NULL, NULL, (LONG_PTR)szName, lstrlen(szName), x+cxIcon+CXIMAGEGAP, y, 0, 0, fDSFlags); } // Get the image if it needs it, if ((pfmi->iImage == -1) && pfmi->pidl && psf && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL)) { pfmi->iImage = SHMapPIDLToSystemImageListIndex(psf, pfmi->pidl, NULL); } // Draw the image (if there is one). if (pfmi->iImage != -1) { int nDC = 0; // Try to center image. y = (pdi->rcItem.bottom+pdi->rcItem.top-cyIcon)/2; if (pfmh->fmf & FMF_LARGEICONS) { himl = g_himlIcons; // Handle minor drawing glitches that can occur with large icons. if ((pdi->itemState & ODS_SELECTED) && (y < pdi->rcItem.top)) { nDC = SaveDC(pdi->hDC); IntersectClipRect(pdi->hDC, pdi->rcItem.left, pdi->rcItem.top, pdi->rcItem.right, pdi->rcItem.bottom); } } else { himl = g_himlIconsSmall; } ImageList_DrawEx(himl, pfmi->iImage, pdi->hDC, x, y, 0, 0, GetBkColor(pdi->hDC), CLR_NONE, ILD_NORMAL); // Restore the clip rect if we were doing custom clipping. if (nDC) RestoreDC(pdi->hDC, nDC); } // Is the user dragging and dropping onto an item that accepts // a drop? if ((pfmh->fmf & FMF_CANORDER) && (pdi->itemState & ODS_SELECTED) && MenuDD_InEditMode() && (pfmi->dwEffect & g_fsmenuagent.GetDragEffect())) { // Yes; draw the insertion caret RECT rc = pdi->rcItem; POINT pt; // We actively draw the insertion caret on mouse moves. // When the cursor moves between menu items, the msg hook // does not get a mouse move until after this paint. But // we need to update the caret position correctly, so do // it here too. GetCursorPos(&pt); g_fsmenuagent.SetCaretPos(&pt); rc.left += 4; rc.right -= 8; TraceMsg(TF_MENU, "MenuDD: showing caret %s", MenuDD_InsertAbove() ? TEXT("above") : TEXT("below")); if (MenuDD_InsertAbove()) { // Hide any existing caret HBRUSH hbrSav = SelectBrush(pdi->hDC, MenuDD_GetBrush()); PatBlt(pdi->hDC, rc.left, pdi->rcItem.bottom - 2, (rc.right - rc.left), 2, PATCOPY); SelectBrush(pdi->hDC, hbrSav); // Show caret in new position PatBlt(pdi->hDC, rc.left, pdi->rcItem.top, (rc.right - rc.left), 2, BLACKNESS); } else { // Hide any existing caret HBRUSH hbrSav = SelectBrush(pdi->hDC, MenuDD_GetBrush()); PatBlt(pdi->hDC, rc.left, pdi->rcItem.top, (rc.right - rc.left), 2, PATCOPY); SelectBrush(pdi->hDC, hbrSav); // Show caret in new position PatBlt(pdi->hDC, rc.left, pdi->rcItem.bottom - 2, (rc.right - rc.left), 2, BLACKNESS); } } } ExitProc: // Cleanup. if (hbrOld) SelectObject(pdi->hDC, hbrOld); return TRUE; } DWORD FileMenuItem_GetExtent(PFILEMENUITEM pfmi) { DWORD dwExtent = 0; if (pfmi) { if (pfmi->Flags & FMI_SEPARATOR) { dwExtent = MAKELONG(0, GetSystemMetrics(SM_CYMENUSIZE)/2); } else { PFILEMENUHEADER pfmh = pfmi->pfmh; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (!g_hdcMem) { g_hdcMem = CreateCompatibleDC(NULL); ASSERT(g_hdcMem); } if (g_hdcMem) { // Get the rough height of an item so we can work out when to break the // menu. User should really do this for us but that would be useful. if (!g_hfont) { NONCLIENTMETRICS ncm; ncm.cbSize = SIZEOF(ncm); if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SIZEOF(ncm), &ncm, FALSE)) { g_hfont = CreateFontIndirect(&ncm.lfMenuFont); ASSERT(g_hfont); } } if (g_hfont) { HFONT hfontOld = SelectFont(g_hdcMem, g_hfont); dwExtent = GetItemExtent(g_hdcMem, pfmi); SelectFont(g_hdcMem, hfontOld); // NB We hang on to the font, it'll get stomped by // FM_TPME on the way out. } // NB We hang on to the DC, it'll get stomped by FM_TPME on the way out. } } } else { TraceMsg(TF_ERROR, "FileMenu_GetExtent: Filemenu is invalid."); } return dwExtent; } LRESULT FileMenu_MeasureItem(HWND hwnd, MEASUREITEMSTRUCT *lpmi) { DWORD dwExtent = FileMenuItem_GetExtent((PFILEMENUITEM)lpmi->itemData); lpmi->itemHeight = HIWORD(dwExtent); lpmi->itemWidth = LOWORD(dwExtent); return TRUE; } STDAPI_(DWORD) FileMenu_GetItemExtent(HMENU hmenu, UINT iItem) { DWORD dwRet = 0; PFILEMENUHEADER pfmh = FileMenu_GetHeader(hmenu); ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (pfmh) dwRet = FileMenuItem_GetExtent((PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, iItem)); return dwRet; } //---------------------------------------------------------------------------- STDAPI_(HMENU) FileMenu_FindSubMenuByPidl(HMENU hmenu, LPITEMIDLIST pidlFS) { PFILEMENUHEADER pfmh; int i; if (!pidlFS) { ASSERT(0); return NULL; } if (ILIsEmpty(pidlFS)) return hmenu; pfmh = FileMenu_GetHeader(hmenu); if (pfmh) { int cItems = DPA_GetPtrCount(pfmh->hdpa); for (i = cItems - 1 ; i >= 0; i--) { // HACK: We directly call this FS function to compare two pidls. // For all items, see if it's the one we're looking for. PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i); if (pfmi && pfmi->pidl && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL) && 0 == pfmh->psf->CompareIDs(0, pidlFS, pfmi->pidl)) { HMENU hmenuSub; if ((pfmi->Flags & FMI_FOLDER) && (NULL != (hmenuSub = GetSubMenu(hmenu, i)))) { // recurse to find the next sub menu return FileMenu_FindSubMenuByPidl(hmenuSub, (LPITEMIDLIST)ILGetNext(pidlFS)); } else { ASSERT(0); // we're in trouble. break; } } } } return NULL; } /*---------------------------------------------------------- Purpose: Fills the given filemenu with contents of the appropriate directory. Returns: S_OK if all the files were added S_FALSE if some did not get added (reached cyMax) error on something bad Cond: -- */ STDAPI FileMenu_InitMenuPopupEx( IN HMENU hmenu, IN OUT PFMDATA pfmdata) { HRESULT hres = E_INVALIDARG; PFILEMENUITEM pfmi; PFILEMENUHEADER pfmh; ASSERT(IS_VALID_HANDLE(hmenu, MENU)); if (IS_VALID_WRITE_PTR(pfmdata, FMDATA) && SIZEOF(*pfmdata) == pfmdata->cbSize) { hres = E_FAIL; // assume error g_fAbortInitMenu = FALSE; // Is this a filemenu? pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); if (pfmi) { ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); pfmh = pfmi->pfmh; if (pfmh) { // Yes if (IsFlagSet(pfmh->fmf, FMF_DELAY_INVALID)) { FileMenu_Invalidate(hmenu); ClearFlag(pfmh->fmf, FMF_DELAY_INVALID); } // (scotth): this can return S_OK but not // set the cItems field if this menu has already // been filled out. hres = S_OK; // Have we already filled this thing out? if (IsFlagSet(pfmi->Flags, FMI_MARKER | FMI_EXPAND)) { // No, do it now. Get the previously init'ed header. FileMenuHeader_DeleteMarkerItem(pfmh); // Fill it full of stuff. hres = FileMenuHeader_AddFiles(pfmh, 0, 0, &pfmdata->cItems); if (E_ABORT == hres) { // Aborted - put the marker back. FileMenuHeader_InsertMarkerItem(pfmh); } else if (pfmh->pidlAltFolder) { pfmh->hdpaAlt = DPA_Create(0); if (pfmh->hdpaAlt) { int cItems; if (E_ABORT == FileMenuHeader_AddFiles(pfmh, 0, FMHAF_SEPARATOR | FMHAF_USEALT, &cItems)) { // Aborted - put the marker back. FileMenuHeader_InsertMarkerItem(pfmh); } DPA_Destroy (pfmh->hdpaAlt); pfmh->hdpaAlt = NULL; } } } } } } return hres; } /*---------------------------------------------------------- Purpose: Fills the given filemenu with contents of the appropriate directory. Returns: FALSE if the given menu isn't a filemenu Cond: -- */ STDAPI_(BOOL) FileMenu_InitMenuPopup( IN HMENU hmenu) { FMDATA fmdata = {SIZEOF(fmdata)}; // zero init everything else return SUCCEEDED(FileMenu_InitMenuPopupEx(hmenu, &fmdata)); } BOOL FileMenu_IsUnexpanded(HMENU hmenu) { BOOL fRet = FALSE; PFILEMENUITEM pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); if (pfmi) { if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND)) { fRet = TRUE; } } return fRet; } //--------------------------------------------------------------------------- // This sets whether to load all the images while creating the menu or to // defer it until the menu is actually being drawn. STDAPI_(void) FileMenu_AbortInitMenu(void) { g_fAbortInitMenu = TRUE; } /*---------------------------------------------------------- Purpose: Returns a clone of the last selected pidl Returns: Cond: -- */ STDAPI_(BOOL) FileMenu_GetLastSelectedItemPidls( IN HMENU hmenu, OUT LPITEMIDLIST * ppidlFolder, OPTIONAL OUT LPITEMIDLIST * ppidlItem) OPTIONAL { BOOL bRet = FALSE; LPITEMIDLIST pidlFolder = NULL; LPITEMIDLIST pidlItem = NULL; // FEATURE (scotth): this global should be moved into the // instance data of the header. if (g_pfmiLastSelNonFolder) { // Get to the header. PFILEMENUHEADER pfmh = g_pfmiLastSelNonFolder->pfmh; if (pfmh) { ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); bRet = TRUE; if (ppidlFolder) { if (g_pfmiLastSelNonFolder->Flags & FMI_ALTITEM) pidlFolder = ILClone(pfmh->pidlAltFolder); else pidlFolder = ILClone(pfmh->pidlFolder); bRet = (NULL != pidlFolder); } if (bRet && ppidlItem) { if (g_pfmiLastSelNonFolder->pidl) { pidlItem = ILClone(g_pfmiLastSelNonFolder->pidl); bRet = (NULL != pidlItem); } else bRet = FALSE; } if (!bRet) { if (pidlFolder) { // Failed; free the pidl we just allocated ILFree(pidlFolder); pidlFolder = NULL; } } } } // Init because callers get lazy and don't pay attention to the return // value. if (ppidlFolder) *ppidlFolder = pidlFolder; if (ppidlItem) *ppidlItem = pidlItem; if (!bRet) TraceMsg(TF_WARNING, "No previously selected item."); return bRet; } /*---------------------------------------------------------- Purpose: Returns the command ID and hmenu of the last selected menu item. The given hmenuRoot is the parent hmenu that must be a FileMenu. Returns: S_OK S_FALSE if there was no last selected item Cond: -- */ STDAPI FileMenu_GetLastSelectedItem( IN HMENU hmenu, OUT HMENU * phmenu, OPTIONAL OUT UINT * puItem) OPTIONAL { HRESULT hres = S_FALSE; if (phmenu) *phmenu = NULL; if (puItem) *puItem = 0; if (g_pfmiLastSelNonFolder) { // Get to the header. PFILEMENUHEADER pfmh = g_pfmiLastSelNonFolder->pfmh; if (pfmh) { ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); if (phmenu) *phmenu = pfmh->hmenu; if (puItem) { // (scotth): this isn't stored right now ASSERT(0); } hres = S_OK; } } return hres; } int FileMenuHeader_LastSelIndex(PFILEMENUHEADER pfmh) { int i; PFILEMENUITEM pfmi; for (i = GetMenuItemCount(pfmh->hmenu)-1;i >= 0; i--) { pfmi = FileMenu_GetItemData(pfmh->hmenu, i, TRUE); if (pfmi && (pfmi == g_pfmiLastSel)) return i; } return -1; } //--------------------------------------------------------------------------- // If the string contains &ch or begins with ch then return TRUE. BOOL _MenuCharMatch(LPCTSTR lpsz, TCHAR ch, BOOL fIgnoreAmpersand) { LPTSTR pchAS; // Find the first ampersand. pchAS = StrChr(lpsz, TEXT('&')); if (pchAS && !fIgnoreAmpersand) { // Yep, is the next char the one we want. if (CharUpperChar(*CharNext(pchAS)) == CharUpperChar(ch)) { // Yep. return TRUE; } } else if (CharUpperChar(*lpsz) == CharUpperChar(ch)) { return TRUE; } return FALSE; } STDAPI_(LRESULT) FileMenu_HandleMenuChar(HMENU hmenu, TCHAR ch) { UINT iItem, cItems, iStep; PFILEMENUITEM pfmi; int iFoundOne; TCHAR szName[MAX_PATH]; PFILEMENUHEADER pfmh; iFoundOne = -1; iStep = 0; iItem = 0; cItems = GetMenuItemCount(hmenu); // Start from the last place we looked from. pfmh = FileMenu_GetHeader(hmenu); if (pfmh) { iItem = FileMenuHeader_LastSelIndex(pfmh) + 1; if (iItem >= cItems) iItem = 0; } while (iStep < cItems) { pfmi = FileMenu_GetItemData(hmenu, iItem, TRUE); if (pfmi) { BOOL bIgnoreAmpersand = (pfmi->pidl && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL)); FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName)); if (_MenuCharMatch(szName, ch, bIgnoreAmpersand)) { // Found (another) match. if (iFoundOne != -1) { // More than one, select the first. return MAKELRESULT(iFoundOne, MNC_SELECT); } else { // Found at least one. iFoundOne = iItem; } } } iItem++; iStep++; // Wrap. if (iItem >= cItems) iItem = 0; } // Did we find one? if (iFoundOne != -1) { // Just in case the user types ahead without the selection being drawn. pfmi = FileMenu_GetItemData(hmenu, iFoundOne, TRUE); if (!(pfmi->Flags & FMI_FOLDER)) g_pfmiLastSelNonFolder = pfmi; return MAKELRESULT(iFoundOne, MNC_EXECUTE); } else { // Didn't find it. return MAKELRESULT(0, MNC_IGNORE); } } /*---------------------------------------------------------- Purpose: Create a filemenu from a given normal menu Returns: Cond: -- */ STDAPI_(BOOL) FileMenu_CreateFromMenu( IN HMENU hmenu, IN COLORREF clr, IN int cxBmpGap, IN HBITMAP hbmp, IN int cySel, IN DWORD fmf) { BOOL fRet = FALSE; if (hmenu) { PFILEMENUHEADER pfmh = FileMenuHeader_Create(hmenu, hbmp, cxBmpGap, clr, cySel, NULL); if (!g_himlIcons || !g_himlIconsSmall) Shell_GetImageLists(&g_himlIcons, &g_himlIconsSmall); if (pfmh) { // Default flags. pfmh->fmf = fmf; if (FileMenuHeader_InsertMarkerItem(pfmh)) fRet = TRUE; else { // REARCHITECT: (scotth): FileMenuHeader_Create can return a pointer // that is already stored in a filemenu item, in which case this // destroy will stomp a data structure. TraceMsg(TF_ERROR, "Can't create file menu."); FileMenuHeader_Destroy(pfmh); } } } else { TraceMsg(TF_ERROR, "Menu is null."); } return fRet; } HMENU FileMenu_Create(COLORREF clr, int cxBmpGap, HBITMAP hbmp, int cySel, DWORD fmf) { HMENU hmenuRet = NULL; HMENU hmenu = CreatePopupMenu(); if (hmenu) { if (FileMenu_CreateFromMenu(hmenu, clr, cxBmpGap, hbmp, cySel, fmf)) hmenuRet = hmenu; else DestroyMenu(hmenu); } return hmenuRet; } /*---------------------------------------------------------- Purpose: Insert a generic item into a filemenu Returns: Cond: -- */ STDAPI FileMenu_InsertItemEx( IN HMENU hmenu, IN UINT iPos, IN FMITEM const * pfmitem) { HRESULT hres = E_INVALIDARG; PFILEMENUITEM pfmi; FMITEM fmitem; // Is this a filemenu? pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); if (IsValidFMItem(pfmitem, &fmitem) && pfmi) { // Yes PFILEMENUHEADER pfmh = pfmi->pfmh; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); // Have we cleaned up the marker item? if ((pfmi->Flags & FMI_MARKER) && (pfmi->Flags & FMI_EXPAND)) { // Nope, do it now. FileMenuHeader_DeleteMarkerItem(pfmh); } hres = E_OUTOFMEMORY; // Add the new item. if (FileMenuItem_Create(pfmh, NULL, fmitem.iImage, 0, &pfmi)) { if (fmitem.pvData && IsFlagSet(fmitem.dwType, FMIT_STRING)) { if (!Sz_AllocCopy((LPTSTR)fmitem.pvData, &(pfmi->psz))) TraceMsg(TF_ERROR, "Unable to allocate menu item text."); pfmi->Flags |= FMI_IGNORE_PIDL; } pfmi->cyItem = fmitem.cyItem; pfmi->lParam = fmitem.lParam; DPA_InsertPtr(pfmh->hdpa, iPos, pfmi); if (IsFlagSet(fmitem.dwType, FMIT_SEPARATOR)) { // Override the setting made above, since separator and // text are mutually exclusive pfmi->Flags = FMI_SEPARATOR; InsertMenu(hmenu, iPos, MF_BYPOSITION|MF_OWNERDRAW|MF_DISABLED|MF_SEPARATOR, fmitem.uID, (LPTSTR)pfmi); } else if (fmitem.hmenuSub) { MENUITEMINFO mii; pfmi->Flags |= FMI_FOLDER; if ((iPos == 0xffff) || (iPos == 0xffffffff)) iPos = GetMenuItemCount(pfmh->hmenu); InsertMenu(pfmh->hmenu, iPos, MF_BYPOSITION|MF_OWNERDRAW|MF_POPUP, (UINT_PTR)fmitem.hmenuSub, (LPTSTR)pfmi); // Set it's ID. mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_ID; // mii.wID = pfmh->idCmd; mii.wID = fmitem.uID; SetMenuItemInfo(pfmh->hmenu, iPos, TRUE, &mii); } else { InsertMenu(hmenu, iPos, MF_BYPOSITION|MF_OWNERDRAW, fmitem.uID, (LPTSTR)pfmi); } hres = S_OK; } } return hres; } /*---------------------------------------------------------- Purpose: Old function to insert a generic item onto a filemenu Returns: Cond: -- */ STDAPI_(BOOL) FileMenu_InsertItem( IN HMENU hmenu, IN LPTSTR psz, IN UINT id, IN int iImage, IN HMENU hmenuSub, IN UINT cyItem, IN UINT iPos) { FMITEM fmitem; fmitem.cbSize = SIZEOF(fmitem); fmitem.dwMask = FMI_TYPE | FMI_ID | FMI_IMAGE | FMI_HMENU | FMI_METRICS; if ((LPTSTR)FMAI_SEPARATOR == psz) { fmitem.dwType = FMIT_SEPARATOR; } else if (NULL == psz) { fmitem.dwType = 0; } else { fmitem.dwType = FMIT_STRING; #ifdef UNICODE fmitem.dwType |= FMIT_UNICODE; #endif fmitem.dwMask |= FMI_DATA; fmitem.pvData = psz; } fmitem.uID = id; fmitem.iImage = iImage; fmitem.hmenuSub = hmenuSub; fmitem.cyItem = cyItem; return SUCCEEDED(FileMenu_InsertItemEx(hmenu, iPos, &fmitem)); } /*---------------------------------------------------------- Purpose: Get info about this filemenu item. */ STDAPI FileMenu_GetItemInfo( IN HMENU hmenu, IN UINT uItem, IN BOOL bByPos, OUT PFMITEM pfmitem) { HRESULT hres = E_INVALIDARG; if (IS_VALID_WRITE_PTR(pfmitem, FMITEM) && SIZEOF(*pfmitem) == pfmitem->cbSize) { PFILEMENUITEM pfmi; hres = E_FAIL; pfmi = FileMenu_GetItemData(hmenu, uItem, bByPos); if (pfmi) { // (scotth): we don't fill in all the fields if (IsFlagSet(pfmitem->dwMask, FMI_LPARAM)) pfmitem->lParam = pfmi->lParam; hres = S_OK; } } return hres; } /*---------------------------------------------------------- Purpose: Save the order of the menu to the given stream. */ STDAPI FileMenu_SaveOrder(HMENU hmenu) { HRESULT hres = E_FAIL; PFILEMENUITEM pfmi; IStream * pstm; pfmi = FileMenu_GetItemData(hmenu, 0, TRUE); if (pfmi && FileList_GetStream(pfmi->pfmh, &pstm)) { hres = FileList_Save(pfmi->pfmh, pstm); pstm->Release(); } return hres; } STDAPI_(BOOL) FileMenu_AppendItem(HMENU hmenu, LPTSTR psz, UINT id, int iImage, HMENU hmenuSub, UINT cyItem) { return FileMenu_InsertItem(hmenu, psz, id, iImage, hmenuSub, cyItem, 0xffff); } STDAPI_(BOOL) FileMenu_TrackPopupMenuEx(HMENU hmenu, UINT Flags, int x, int y, HWND hwndOwner, LPTPMPARAMS lpTpm) { BOOL fRet = TrackPopupMenuEx(hmenu, Flags, x, y, hwndOwner, lpTpm); // Cleanup. DeleteGlobalMemDCAndFont(); return fRet; } //---------------------------------------------------------------------------- // Like Users only this works on submenu's too. // NB Returns 0 for seperators. UINT FileMenu_GetMenuItemID(HMENU hmenu, UINT iItem) { MENUITEMINFO mii; mii.cbSize = SIZEOF(MENUITEMINFO); mii.fMask = MIIM_ID; mii.cch = 0; // just in case if (GetMenuItemInfo(hmenu, iItem, TRUE, &mii)) return mii.wID; return 0; } PFILEMENUITEM _FindItemByCmd(PFILEMENUHEADER pfmh, UINT id, int *piPos) { if (pfmh) { int cItems, i; cItems = DPA_GetPtrCount(pfmh->hdpa); for (i = 0; i < cItems; i++) { PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i); if (pfmi) { // Is this the right item? // NB This ignores menu items. if (id == GetMenuItemID(pfmh->hmenu, i)) { // Yep. if (piPos) *piPos = i; return pfmi; } } } } return NULL; } PFILEMENUITEM _FindMenuOrItemByCmd(PFILEMENUHEADER pfmh, UINT id, int *piPos) { if (pfmh) { int cItems, i; cItems = DPA_GetPtrCount(pfmh->hdpa); for (i = 0; i < cItems; i++) { PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, i); if (pfmi) { // Is this the right item? // NB This includes menu items. if (id == FileMenu_GetMenuItemID(pfmh->hmenu, i)) { // Yep. if (piPos) *piPos = i; return pfmi; } } } } return NULL; } //---------------------------------------------------------------------------- // NB This deletes regular items or submenus. STDAPI_(BOOL) FileMenu_DeleteItemByCmd(HMENU hmenu, UINT id) { PFILEMENUHEADER pfmh; if (!IsMenu(hmenu)) return FALSE; if (!id) return FALSE; pfmh = FileMenu_GetHeader(hmenu); if (pfmh) { int i; PFILEMENUITEM pfmi = _FindMenuOrItemByCmd(pfmh, id, &i); if (pfmi) { // If it's a submenu, delete it's items first. HMENU hmenuSub = GetSubMenu(pfmh->hmenu, i); if (hmenuSub) FileMenu_DeleteAllItems(hmenuSub); // Delete the item itself. DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION); FileMenuItem_Destroy(pfmi); DPA_DeletePtr(pfmh->hdpa, i); return TRUE; } } return FALSE; } STDAPI_(BOOL) FileMenu_DeleteItemByIndex(HMENU hmenu, UINT iItem) { PFILEMENUHEADER pfmh; if (!IsMenu(hmenu)) return FALSE; pfmh = FileMenu_GetHeader(hmenu); if (pfmh) { PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, iItem); if (pfmi) { // Delete the item itself. DeleteMenu(pfmh->hmenu, iItem, MF_BYPOSITION); FileMenuItem_Destroy(pfmi); DPA_DeletePtr(pfmh->hdpa, iItem); return TRUE; } } return FALSE; } //--------------------------------------------------------------------------- // Search for the first sub menu of the given menu, who's first item's ID // is id. Returns NULL, if nothing is found. HMENU _FindMenuItemByFirstID(HMENU hmenu, UINT id, int *pi) { int cMax, c; MENUITEMINFO mii; ASSERT(hmenu); // Search all items. mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_ID; mii.cch = 0; // just in case cMax = GetMenuItemCount(hmenu); for (c=0; chmenu, i, MF_BYPOSITION); FileMenuItem_Destroy(pfmi); DPA_DeletePtr(pfmh->hdpa, i); return TRUE; } } return FALSE; } STDAPI_(BOOL) FileMenu_DeleteSeparator(HMENU hmenu) { int i; PFILEMENUHEADER pfmh; if (!IsMenu(hmenu)) return FALSE; pfmh = FileMenu_GetHeader(hmenu); if (pfmh) { PFILEMENUITEM pfmi = _FindItemByCmd(pfmh, 0, &i); if (pfmi) { // Yep. DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION); if (pfmi->pidl) ILFree(pfmi->pidl); LocalFree((HLOCAL)pfmi); DPA_DeletePtr(pfmh->hdpa, i); return TRUE; } } return FALSE; } STDAPI_(BOOL) FileMenu_InsertSeparator(HMENU hmenu, UINT iPos) { return FileMenu_InsertItem(hmenu, (LPTSTR)FMAI_SEPARATOR, 0, -1, NULL, 0, iPos); } STDAPI_(BOOL) FileMenu_IsFileMenu(HMENU hmenu) { return FileMenu_GetHeader(hmenu) ? TRUE : FALSE; } STDAPI_(BOOL) FileMenu_EnableItemByCmd(HMENU hmenu, UINT id, BOOL fEnable) { PFILEMENUHEADER pfmh; if (!IsMenu(hmenu)) return FALSE; if (!id) return FALSE; pfmh = FileMenu_GetHeader(hmenu); if (pfmh) { PFILEMENUITEM pfmi = _FindItemByCmd(pfmh, id, NULL); if (pfmi) { if (fEnable) { pfmi->Flags &= ~FMI_DISABLED; EnableMenuItem(pfmh->hmenu, id, MF_BYCOMMAND | MF_ENABLED); } else { pfmi->Flags |= FMI_DISABLED; EnableMenuItem(pfmh->hmenu, id, MF_BYCOMMAND | MF_GRAYED); } return TRUE; } } else { TraceMsg(TF_ERROR, "Menu is not a filemenu."); } return FALSE; } STDAPI_(BOOL) FileMenu_GetPidl(HMENU hmenu, UINT iPos, LPITEMIDLIST *ppidl) { BOOL fRet = FALSE; PFILEMENUHEADER pfmh = FileMenu_GetHeader(hmenu); if (pfmh) { PFILEMENUITEM pfmi = (PFILEMENUITEM)DPA_GetPtr(pfmh->hdpa, iPos); if (pfmi) { if (pfmh->pidlFolder && pfmi->pidl && IsFlagClear(pfmi->Flags, FMI_IGNORE_PIDL)) { *ppidl = ILCombine(pfmh->pidlFolder, pfmi->pidl); fRet = TRUE; } } } return fRet; } BOOL Tooltip_Create(HWND *phwndTip) { BOOL fRet = FALSE; *phwndTip = CreateWindow(TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, HINST_THISDLL, NULL); if (*phwndTip) { TOOLINFO ti; ti.cbSize = SIZEOF(ti); ti.uFlags = TTF_TRACK; ti.hwnd = NULL; ti.uId = 0; ti.lpszText = NULL; ti.hinst = HINST_THISDLL; SetRectEmpty(&ti.rect); SendMessage(*phwndTip, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); fRet = TRUE; } return fRet; } void Tooltip_SetText(HWND hwndTip, LPCTSTR pszText) { if (hwndTip) { TOOLINFO ti; ti.cbSize = SIZEOF(ti); ti.uFlags = 0; ti.hwnd = NULL; ti.uId = 0; ti.lpszText = (LPTSTR)pszText; ti.hinst = HINST_THISDLL; SendMessage(hwndTip, TTM_UPDATETIPTEXT, 0, (LPARAM)(LPTOOLINFO)&ti); } } void Tooltip_Hide(HWND hwndTip) { if (hwndTip) { TOOLINFO ti; ti.cbSize = SIZEOF(ti); ti.hwnd = NULL; ti.uId = 0; SendMessage(hwndTip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti); } } void Tooltip_Show(HWND hwndTip) { if (hwndTip) { TOOLINFO ti; ti.cbSize = SIZEOF(ti); ti.hwnd = NULL; ti.uId = 0; SendMessage(hwndTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti); SetWindowPos(hwndTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); } } void Tooltip_SetPos(HWND hwndTip, int x, int y) { ASSERT(IsWindow(hwndTip)); SendMessage(hwndTip, TTM_TRACKPOSITION, 0, MAKELPARAM(x, y)); } /*---------------------------------------------------------- Purpose: Ask the callback for a tooltip. Returns: Cond: -- */ void FileMenuItem_GetTooltip( IN PFILEMENUITEM pfmi) { ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); PFILEMENUHEADER pfmh = pfmi->pfmh; if (pfmh->pfncb) { FMCBDATA fmcbdata; FMTOOLTIP fmtt = {0}; if (pfmi->pszTooltip) { // Free the previous tooltip LocalFree(pfmi->pszTooltip); pfmi->pszTooltip = NULL; } fmcbdata.lParam = pfmh->lParam; fmcbdata.hmenu = pfmh->hmenu; fmcbdata.iPos = -1; fmcbdata.idCmd = (UINT)-1; // (scotth): we don't ask for tooltips for alternate lists fmcbdata.pidlFolder = pfmh->pidlFolder; fmcbdata.pidl = pfmi->pidl; fmcbdata.psf = pfmh->psf; // Was a tooltip set? if (S_OK == pfmh->pfncb(FMM_GETTOOLTIP, &fmcbdata, (LPARAM)&fmtt)) { Sz_AllocCopyW(fmtt.pszTip, &(pfmi->pszTooltip)); SHFree(fmtt.pszTip); if (pfmi->pszTooltip) { // Set the other settings if (IsFlagSet(fmtt.dwMask, FMTT_MARGIN)) { pfmi->rcMargin = fmtt.rcMargin; SetFlag(pfmi->Flags, FMI_MARGIN); } if (IsFlagSet(fmtt.dwMask, FMTT_MAXWIDTH)) { pfmi->dwMaxTipWidth = fmtt.dwMaxWidth; SetFlag(pfmi->Flags, FMI_MAXTIPWIDTH); } if (IsFlagSet(fmtt.dwMask, FMTT_DRAWFLAGS)) { pfmi->uDrawFlags = fmtt.uDrawFlags; SetFlag(pfmi->Flags, FMI_DRAWFLAGS); } if (IsFlagSet(fmtt.dwMask, FMTT_TABSTOP)) { pfmi->dwTabstop = fmtt.dwTabstop; SetFlag(pfmi->Flags, FMI_TABSTOP); } } } } } /*---------------------------------------------------------- Purpose: Called on WM_MENUSELECT */ STDAPI_(BOOL) FileMenu_HandleMenuSelect( IN HMENU hmenu, IN WPARAM wparam, IN LPARAM lparam) { UINT id = LOWORD(wparam); BOOL fTip = FALSE; BOOL fRet = FALSE; PFILEMENUITEM pfmi = g_pfmiLastSelNonFolder; if (hmenu && pfmi) { ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); PFILEMENUHEADER pfmh = pfmi->pfmh; if (pfmh && IsFlagSet(pfmh->fmf, FMF_TOOLTIPS) && FileMenuHeader_CreateTooltipWindow(pfmh)) { // Have we asked for the tooltip? if (IsFlagClear(pfmi->Flags, FMI_ASKEDFORTOOLTIP)) { // No; do it now FileMenuItem_GetTooltip(pfmi); SetFlag(pfmi->Flags, FMI_ASKEDFORTOOLTIP); } // Does this have a tooltip? if (pfmi->pszTooltip && FileMenu_GetHeader(hmenu) == pfmh) { // Yes Tooltip_Hide(g_hwndTip); Tooltip_SetPos(g_hwndTip, g_rcItem.left + X_TIPOFFSET, g_rcItem.bottom); Tooltip_SetText(g_hwndTip, pfmi->pszTooltip); if (IsFlagSet(pfmi->Flags, FMI_MAXTIPWIDTH)) SendMessage(g_hwndTip, TTM_SETMAXTIPWIDTH, 0, (LPARAM)pfmi->dwMaxTipWidth); if (IsFlagSet(pfmi->Flags, FMI_MARGIN)) SendMessage(g_hwndTip, TTM_SETMARGIN, 0, (LPARAM)&pfmi->rcMargin); Tooltip_Show(g_hwndTip); fTip = TRUE; } } fRet = TRUE; } if (!fTip && IsWindow(g_hwndTip)) Tooltip_Hide(g_hwndTip); return fRet; } STDAPI_(void) FileMenu_EditMode(BOOL bEdit) { g_fsmenuagent.SetEditMode(bEdit, DROPEFFECT_MOVE); } /*---------------------------------------------------------- Purpose: */ STDAPI_(BOOL) FileMenu_ProcessCommand( IN HWND hwnd, IN HMENU hmenuBar, IN UINT idMenu, IN HMENU hmenu, IN UINT idCmd) { return g_fsmenuagent.ProcessCommand(hwnd, hmenuBar, idMenu, hmenu, idCmd); } void FileMenuHeader_HandleUpdateImage(PFILEMENUHEADER pfmh, int iImage) { int i; PFILEMENUITEM pfmi; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); ASSERT(-1 != iImage); // Look for any image indexes that are being changed for (i = GetMenuItemCount(pfmh->hmenu) - 1; i >= 0; i--) { pfmi = FileMenu_GetItemData(pfmh->hmenu, i, TRUE); if (pfmi) { ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM)); if (pfmi->iImage == iImage) { // Invalidate this image. It will be recalculated when // the menu item is redrawn. pfmi->iImage = -1; } HMENU hmenuSub = GetSubMenu(pfmh->hmenu, i); if (hmenuSub) { PFILEMENUHEADER pfmhT = FileMenu_GetHeader(hmenuSub); if (pfmhT) FileMenuHeader_HandleUpdateImage(pfmhT, iImage); } } } } BOOL FileMenuHeader_HandleNotify(PFILEMENUHEADER pfmh, LPCITEMIDLIST * ppidl, LONG lEvent) { BOOL bRet; int iImage; ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER)); switch (lEvent) { case SHCNE_UPDATEIMAGE: if (EVAL(ppidl && ppidl[0])) { iImage = *(int UNALIGNED *)((BYTE *)ppidl[0] + 2); if (-1 != iImage) FileMenuHeader_HandleUpdateImage(pfmh, iImage); } bRet = TRUE; break; default: bRet = FALSE; break; } return bRet; } STDAPI_(BOOL) FileMenu_HandleNotify(HMENU hmenu, LPCITEMIDLIST * ppidl, LONG lEvent) { BOOL bRet = FALSE; PFILEMENUHEADER pfmh = FileMenu_GetHeader(hmenu); if (hmenu && pfmh) { bRet = FileMenuHeader_HandleNotify(pfmh, ppidl, lEvent); } return bRet; }