windows-nt/Source/XPSP1/NT/shell/shell32/fsmenu.cpp
2020-09-26 16:20:57 +08:00

1073 lines
32 KiB
C++

#include "shellprv.h"
#include "fsmenu.h"
#include "ids.h"
#include <limits.h>
#include "filetbl.h"
#include <oleacc.h> // MSAAMENUINFO stuff
#define CXIMAGEGAP 6
typedef enum
{
FMII_DEFAULT = 0x0000,
FMII_BREAK = 0x0001
} FMIIFLAGS;
#define FMI_MARKER 0x00000001
#define FMI_EXPAND 0x00000004
#define FMI_EMPTY 0x00000008
#define FMI_ON_MENU 0x00000040
// One of these per file menu.
typedef struct
{
HMENU hmenu; // Menu.
HDPA hdpa; // List of items (see below).
const struct _FILEMENUITEM *pfmiLastSel;
UINT idCmd; // Command.
UINT grfFlags; // enum filter
DWORD dwMask; // FMC_ flags
PFNFMCALLBACK pfnCallback; // Callback function.
LPARAM lParam; // Parameter passed for callback handler
int cyMenuSizeSinceLastBreak; // Size of menu (cy)
} FILEMENUHEADER;
// 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 _FILEMENUITEM
{
MSAAMENUINFO msaa; // accessibility must be first.
FILEMENUHEADER *pfmh; // The header.
IShellFolder *psf; // Shell Folder.
LPITEMIDLIST pidl; // IDlist for item.
int iImage; // Image index to use.
DWORD dwFlags; // Misc flags above.
DWORD dwAttributes; // GetAttributesOf(), SFGAO_ bits (only some)
LPTSTR psz; // Text when not using pidls.
LPARAM lParam; // Application data
} FILEMENUITEM;
#if defined(DEBUG)
BOOL IsValidPFILEMENUHEADER(FILEMENUHEADER *pfmh)
{
return (IS_VALID_WRITE_PTR(pfmh, FILEMENUHEADER) &&
IS_VALID_HANDLE(pfmh->hmenu, MENU) &&
IS_VALID_HANDLE(pfmh->hdpa, DPA));
}
BOOL IsValidPFILEMENUITEM(FILEMENUITEM *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)));
}
#endif
DWORD GetItemTextExtent(HDC hdc, 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);
}
void FileMenuItem_GetDisplayName(FILEMENUITEM *pfmi, LPTSTR pszName, UINT cchName)
{
ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
// Is this a special empty item?
if (pfmi->dwFlags & FMI_EMPTY)
{
// Yep, load the string from a resource.
LoadString(HINST_THISDLL, IDS_NONE, pszName, cchName);
}
else
{
*pszName = 0;
// If it's got a pidl use that, else just use the normal menu string.
if (pfmi->psz)
{
lstrcpyn(pszName, pfmi->psz, cchName);
}
else if (pfmi->pidl && pfmi->psf && SUCCEEDED(DisplayNameOf(pfmi->psf, pfmi->pidl, SHGDN_NORMAL, pszName, cchName)))
{
pfmi->psz = StrDup(pszName);
}
}
}
// Create a menu item structure to be stored in the hdpa
BOOL FileMenuItem_Create(FILEMENUHEADER *pfmh, IShellFolder *psf, LPCITEMIDLIST pidl, DWORD dwFlags, FILEMENUITEM **ppfmi)
{
FILEMENUITEM *pfmi = (FILEMENUITEM *)LocalAlloc(LPTR, sizeof(*pfmi));
if (pfmi)
{
pfmi->pfmh = pfmh;
pfmi->iImage = -1;
pfmi->dwFlags = dwFlags;
pfmi->pidl = pidl ? ILClone(pidl) : NULL;
pfmi->psf = psf;
if (pfmi->psf)
pfmi->psf->AddRef();
if (pfmi->psf && pfmi->pidl)
{
pfmi->dwAttributes = SFGAO_FOLDER;
pfmi->psf->GetAttributesOf(1, &pidl, &pfmi->dwAttributes);
}
// fill in msaa stuff
pfmi->msaa.dwMSAASignature = MSAA_MENU_SIG;
// prep the pfmi->psz cached displayname
WCHAR sz[MAX_PATH];
FileMenuItem_GetDisplayName(pfmi, sz, ARRAYSIZE(sz));
// just use the same string ref, so we dont dupe the allocation.
pfmi->msaa.pszWText = pfmi->psz;
pfmi->msaa.cchWText = pfmi->msaa.pszWText ? lstrlenW(pfmi->msaa.pszWText) : 0;
}
*ppfmi = pfmi;
return (NULL != pfmi);
}
BOOL FileMenuItem_Destroy(FILEMENUITEM *pfmi)
{
BOOL fRet = FALSE;
if (pfmi)
{
ILFree(pfmi->pidl);
LocalFree(pfmi->psz);
ATOMICRELEASE(pfmi->psf);
LocalFree(pfmi);
fRet = TRUE;
}
return fRet;
}
// Enumerates the folder and adds the files to the DPA.
// Returns: count of items in the list
int FileList_Build(FILEMENUHEADER *pfmh, IShellFolder *psf, int cItems)
{
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
if (pfmh->hdpa)
{
// special case the single empty item, and remove it.
// this is because we expect to get called multiple times in FileList_Build on a single menu.
if ((1 == cItems) && (1 == DPA_GetPtrCount(pfmh->hdpa)))
{
FILEMENUITEM *pfmiEmpty = (FILEMENUITEM*)DPA_GetPtr(pfmh->hdpa, 0);
if (pfmiEmpty->dwFlags & FMI_EMPTY)
{
DeleteMenu(pfmh->hmenu, 0, MF_BYPOSITION);
FileMenuItem_Destroy(pfmiEmpty);
DPA_DeletePtr(pfmh->hdpa, 0);
cItems = 0;
}
}
// We now need to iterate over the children under this guy...
IEnumIDList *penum;
if (S_OK == psf->EnumObjects(NULL, pfmh->grfFlags, &penum))
{
LPITEMIDLIST pidl;
while (S_OK == penum->Next(1, &pidl, NULL))
{
FILEMENUITEM *pfmi;
if (FileMenuItem_Create(pfmh, psf, pidl, 0, &pfmi))
{
int idpa = DPA_AppendPtr(pfmh->hdpa, pfmi);
ASSERTMSG(idpa != -1, "DPA_AppendPtr failed when adding file menu item");
if (idpa != -1)
{
// if the caller returns S_FALSE then we will remove the item from the
// menu, otherwise we behave as before.
if (pfmh->pfnCallback(FMM_ADD, pfmh->lParam, psf, pidl) == S_FALSE)
{
FileMenuItem_Destroy(pfmi);
DPA_DeletePtr(pfmh->hdpa, idpa);
}
else
{
cItems++;
}
}
}
ILFree(pidl);
}
penum->Release();
}
}
// Insert a special Empty item
if (!cItems && pfmh->hdpa)
{
FILEMENUITEM *pfmi;
if (FileMenuItem_Create(pfmh, NULL, NULL, FMI_EMPTY, &pfmi))
{
DPA_SetPtr(pfmh->hdpa, cItems, pfmi);
cItems++;
}
}
return cItems;
}
// 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, FILEMENUITEM *pfmi)
{
TCHAR szName[MAX_PATH];
szName[0] = 0;
ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
FILEMENUHEADER *pfmh = pfmi->pfmh;
ASSERT(pfmh);
DWORD dwExtent = GetItemTextExtent(hdc, szName);
UINT uHeight = HIWORD(dwExtent);
// If no custom height - calc it.
uHeight = max(uHeight, ((WORD)g_cySmIcon)) + 6;
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
UINT uWidth = LOWORD(dwExtent) + GetSystemMetrics(SM_CXMENUCHECK);
// 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.
uWidth += g_cxSmIcon + (2 * CXIMAGEGAP);
return MAKELONG(uWidth, uHeight);
}
// Get the FILEMENUITEM *of this menu item
FILEMENUITEM *FileMenu_GetItemData(HMENU hmenu, UINT iItem, BOOL bByPos)
{
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_DATA | MIIM_STATE;
return GetMenuItemInfo(hmenu, iItem, bByPos, &mii) ? (FILEMENUITEM *)mii.dwItemData : NULL;
}
FILEMENUHEADER *FileMenu_GetHeader(HMENU hmenu)
{
FILEMENUITEM *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;
}
// 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.
FILEMENUHEADER *FileMenuHeader_Create(HMENU hmenu, const FMCOMPOSE *pfmc)
{
FILEMENUHEADER *pfmh;
FILEMENUITEM *pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
// 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 = (FILEMENUHEADER *)LocalAlloc(LPTR, sizeof(*pfmh));
if (pfmh)
{
pfmh->hdpa = DPA_Create(0);
if (pfmh->hdpa == NULL)
{
LocalFree((HLOCAL)pfmh);
pfmh = NULL;
}
else
{
pfmh->hmenu = hmenu;
}
}
}
if (pfmc && pfmh)
{
pfmh->idCmd = pfmc->idCmd;
pfmh->grfFlags = pfmc->grfFlags;
pfmh->dwMask = pfmc->dwMask;
pfmh->pfnCallback = pfmc->pfnCallback;
pfmh->lParam = pfmc->lParam;
}
return pfmh;
}
BOOL FileMenuHeader_InsertMarkerItem(FILEMENUHEADER *pfmh, IShellFolder *psf);
// This functions adds the given item (index into DPA) into the actual menu.
BOOL FileMenuHeader_InsertItem(FILEMENUHEADER *pfmh, UINT iItem, FMIIFLAGS fFlags)
{
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
// Normal item.
FILEMENUITEM *pfmi = (FILEMENUITEM *)DPA_GetPtr(pfmh->hdpa, iItem);
if (!pfmi || (pfmi->dwFlags & FMI_ON_MENU))
return FALSE;
pfmi->dwFlags |= FMI_ON_MENU;
// The normal stuff.
UINT 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->dwAttributes & SFGAO_FOLDER) && !(pfmh->dwMask & FMC_NOEXPAND))
{
// Yep. Create a submenu item.
HMENU hmenuSub = CreatePopupMenu();
if (hmenuSub)
{
FMCOMPOSE fmc = {0};
// Set the callback now so it can be called when adding items
fmc.lParam = pfmh->lParam;
fmc.pfnCallback = pfmh->pfnCallback;
fmc.dwMask = pfmh->dwMask;
fmc.idCmd = pfmh->idCmd;
fmc.grfFlags = pfmh->grfFlags;
// Insert it into the parent menu.
InsertMenu(pfmh->hmenu, iItem, fMenu | MF_POPUP, (UINT_PTR)hmenuSub, (LPTSTR)pfmi);
// Set it's ID.
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_ID;
mii.wID = pfmh->idCmd;
SetMenuItemInfo(pfmh->hmenu, iItem, TRUE, &mii);
IShellFolder *psf;
if (SUCCEEDED(pfmi->psf->BindToObject(pfmi->pidl, NULL, IID_PPV_ARG(IShellFolder, &psf))))
{
FILEMENUHEADER *pfmhSub = FileMenuHeader_Create(hmenuSub, &fmc);
if (pfmhSub)
{
// Build it a bit at a time.
FileMenuHeader_InsertMarkerItem(pfmhSub, psf);
}
psf->Release();
}
}
}
else
{
// Nope.
if (pfmi->dwFlags & FMI_EMPTY)
fMenu |= MF_DISABLED | MF_GRAYED;
InsertMenu(pfmh->hmenu, iItem, fMenu, pfmh->idCmd, (LPTSTR)pfmi);
}
return TRUE;
}
// Give the submenu a marker item so we can check it's a filemenu item
// at initpopupmenu time.
BOOL FileMenuHeader_InsertMarkerItem(FILEMENUHEADER *pfmh, IShellFolder *psf)
{
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
FILEMENUITEM *pfmi;
if (FileMenuItem_Create(pfmh, psf, NULL, FMI_MARKER | FMI_EXPAND, &pfmi))
{
DPA_SetPtr(pfmh->hdpa, 0, pfmi);
FileMenuHeader_InsertItem(pfmh, 0, FMII_DEFAULT);
return TRUE;
}
return FALSE;
}
// 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
int FileList_AddToMenu(FILEMENUHEADER *pfmh)
{
int cItemMac = 0;
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
if (pfmh->hdpa)
{
int cyItem = 0;
int cyMenu = pfmh->cyMenuSizeSinceLastBreak;
int 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 hdc = GetDC(NULL);
if (hdc)
{
NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(ncm);
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE))
{
HFONT hfont = CreateFontIndirect(&ncm.lfMenuFont);
if (hfont)
{
HFONT hfontOld = SelectFont(hdc, hfont);
cyItem = HIWORD(GetItemExtent(hdc, (FILEMENUITEM *)DPA_GetPtr(pfmh->hdpa, 0)));
SelectObject(hdc, hfontOld);
DeleteObject(hfont);
}
}
ReleaseDC(NULL, hdc);
}
UINT cItems = DPA_GetPtrCount(pfmh->hdpa);
for (UINT i = 0; i < cItems; i++)
{
// Keep a rough count of the height of the menu.
cyMenu += cyItem;
if (cyMenu > cyMenuMax)
{
// Add a vertical break?
FileMenuHeader_InsertItem(pfmh, i, FMII_BREAK);
cyMenu = cyItem;
}
else
{
FileMenuHeader_InsertItem(pfmh, i, 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(FILEMENUHEADER *pfmh)
{
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
int cItems = DPA_GetPtrCount(pfmh->hdpa);
for (int i = 0; i < cItems; i++)
{
FILEMENUITEM *pfmi = (FILEMENUITEM *)DPA_GetPtr(pfmh->hdpa, i);
if (pfmi && pfmi->pidl && (pfmi->iImage == -1))
{
pfmi->iImage = SHMapPIDLToSystemImageListIndex(pfmi->psf, pfmi->pidl, NULL);
}
}
return TRUE;
}
// 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(FILEMENUHEADER *pfmh, IShellFolder **ppsf)
{
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
if (GetMenuItemCount(pfmh->hmenu) == 1)
{
if (GetMenuItemID(pfmh->hmenu, 0) == pfmh->idCmd)
{
FILEMENUITEM *pfmi = FileMenu_GetItemData(pfmh->hmenu, 0, TRUE);
if (pfmi && (pfmi->dwFlags & FMI_MARKER))
{
// Delete it.
ASSERT(pfmh->hdpa);
ASSERT(DPA_GetPtrCount(pfmh->hdpa) == 1);
if (ppsf)
{
*ppsf = pfmi->psf; // transfer the ref
pfmi->psf = NULL;
}
ASSERT(NULL == pfmi->psf);
// NB The marker shouldn't have a pidl.
ASSERT(NULL == pfmi->pidl);
LocalFree((HLOCAL)pfmi);
DPA_DeletePtr(pfmh->hdpa, 0);
DeleteMenu(pfmh->hmenu, 0, MF_BYPOSITION);
// Cleanup OK.
return TRUE;
}
}
}
return FALSE;
}
// 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
HRESULT FileMenuHeader_AddFiles(FILEMENUHEADER *pfmh, IShellFolder *psf, int iPos, int *pcItems)
{
HRESULT hr;
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
int cItems = FileList_Build(pfmh, psf, iPos);
// If the build was aborted cleanup and early out.
*pcItems = cItems;
if (cItems != 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);
FileList_AddImages(pfmh);
}
hr = (*pcItems < cItems) ? S_FALSE : S_OK;
TraceMsg(TF_MENU, "FileMenuHeader_AddFiles: Added %d filemenu items.", cItems);
return hr;
}
// Add files to this menu.
// Returns: number of items added
HRESULT FileMenu_AddFiles(HMENU hmenu, UINT iPos, FMCOMPOSE *pfmc)
{
HRESULT hr = E_OUTOFMEMORY;
BOOL fMarker = FALSE;
// (FileMenuHeader_Create might return an existing header)
FILEMENUHEADER *pfmh = FileMenuHeader_Create(hmenu, pfmc);
if (pfmh)
{
FILEMENUITEM *pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
if (pfmi)
{
// Clean up marker item if there is one.
if ((FMI_MARKER | FMI_EXPAND) == (pfmi->dwFlags & (FMI_MARKER | FMI_EXPAND)))
{
// Nope, do it now.
FileMenuHeader_DeleteMarkerItem(pfmh, NULL);
fMarker = TRUE;
if (iPos)
iPos--;
}
}
hr = FileMenuHeader_AddFiles(pfmh, pfmc->psf, iPos, &pfmc->cItems);
if ((0 == pfmc->cItems) && fMarker)
{
// Aborted or no items. Put the marker back (if there used
// to be one).
FileMenuHeader_InsertMarkerItem(pfmh, NULL);
}
}
return hr;
}
// creator of the filemenu has to explicitly call to free
// up FileMenu items because USER doesn't send WM_DELETEITEM for ownerdraw
// menu. Great eh?
// Returns the number of items deleted.
void FileMenu_DeleteAllItems(HMENU hmenu)
{
FILEMENUHEADER *pfmh = FileMenu_GetHeader(hmenu);
if (pfmh)
{
// Clean up the items.
UINT cItems = DPA_GetPtrCount(pfmh->hdpa);
// backwards stop things dont move as we delete
for (int i = cItems - 1; i >= 0; i--)
{
FILEMENUITEM *pfmi = (FILEMENUITEM *)DPA_GetPtr(pfmh->hdpa, i);
if (pfmi)
{
HMENU hmenuSub = GetSubMenu(pfmh->hmenu, i); // cascade item?
if (hmenuSub)
{
// Yep. Get the submenu for this item, Delete all items.
FileMenu_DeleteAllItems(hmenuSub);
}
// Delete the item itself.
DeleteMenu(pfmh->hmenu, i, MF_BYPOSITION);
FileMenuItem_Destroy(pfmi);
DPA_DeletePtr(pfmh->hdpa, i);
}
}
// Clean up the header.
DPA_Destroy(pfmh->hdpa);
LocalFree((HLOCAL)pfmh);
}
}
STDAPI FileMenu_Compose(HMENU hmenu, UINT nMethod, FMCOMPOSE *pfmc)
{
HRESULT hr = E_INVALIDARG;
switch (nMethod)
{
case FMCM_INSERT:
hr = FileMenu_AddFiles(hmenu, 0, pfmc);
break;
case FMCM_APPEND:
hr = FileMenu_AddFiles(hmenu, GetMenuItemCount(hmenu), pfmc);
break;
case FMCM_REPLACE:
FileMenu_DeleteAllItems(hmenu);
hr = FileMenu_AddFiles(hmenu, 0, pfmc);
break;
}
return hr;
}
LPITEMIDLIST FileMenuItem_FullIDList(const FILEMENUITEM *pfmi)
{
LPITEMIDLIST pidlFolder, pidl = NULL;
if (SUCCEEDED(SHGetIDListFromUnk(pfmi->psf, &pidlFolder)))
{
pidl = ILCombine(pidlFolder, pfmi->pidl);
ILFree(pidlFolder);
}
return pidl;
}
void FileMenuItem_SetItem(const FILEMENUITEM *pfmi, BOOL bClear)
{
if (bClear)
{
pfmi->pfmh->pfmiLastSel = NULL;
pfmi->pfmh->pfnCallback(FMM_SETLASTPIDL, pfmi->pfmh->lParam, NULL, NULL);
}
else
{
pfmi->pfmh->pfmiLastSel = pfmi;
LPITEMIDLIST pidl = FileMenuItem_FullIDList(pfmi);
if (pidl)
{
pfmi->pfmh->pfnCallback(FMM_SETLASTPIDL, pfmi->pfmh->lParam, NULL, pidl);
ILFree(pidl);
}
}
}
LRESULT FileMenu_DrawItem(HWND hwnd, DRAWITEMSTRUCT *pdi)
{
BOOL fFlatMenu = FALSE;
BOOL fFrameRect = FALSE;
SystemParametersInfo(SPI_GETFLATMENU, 0, (void *)&fFlatMenu, 0);
if ((pdi->itemAction & ODA_SELECT) || (pdi->itemAction & ODA_DRAWENTIRE))
{
HBRUSH hbrOld = NULL;
FILEMENUITEM *pfmi = (FILEMENUITEM *)pdi->itemData;
ASSERT(IS_VALID_STRUCT_PTR(pfmi, FILEMENUITEM));
if (!pfmi)
{
TraceMsg(TF_ERROR, "FileMenu_DrawItem: Filemenu is invalid (no item data).");
return FALSE;
}
FILEMENUHEADER *pfmh = pfmi->pfmh;
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
// Adjust for large/small icons.
int cxIcon = g_cxSmIcon;
int 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
FileMenuItem_SetItem(pfmi, TRUE);
}
}
if (pdi->itemState & ODS_SELECTED)
{
// 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.)
//
if (fFlatMenu)
{
SetBkColor(pdi->hDC, GetSysColor(COLOR_MENUHILIGHT));
hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUHILIGHT));
fFrameRect = TRUE;
}
else
{
// No
SetBkColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHT));
SetTextColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_HIGHLIGHTTEXT));
}
// inform callback of last item
FileMenuItem_SetItem(pfmi, FALSE);
}
else
{
// dwRop = SRCAND;
hbrOld = SelectBrush(pdi->hDC, GetSysColorBrush(COLOR_MENUTEXT));
}
// Initial start pos.
int x = pdi->rcItem.left + CXIMAGEGAP;
// Get the name.
TCHAR szName[MAX_PATH];
FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
// NB Keep a plain copy of the name for testing and accessibility.
if (!pfmi->psz)
pfmi->psz = StrDup(szName);
DWORD dwExtent = GetItemTextExtent(pdi->hDC, szName);
int y = (pdi->rcItem.bottom+pdi->rcItem.top - HIWORD(dwExtent)) / 2;
// Shrink the selection rect for small icons a bit.
pdi->rcItem.top += 1;
pdi->rcItem.bottom -= 1;
// Draw the text.
int fDSFlags;
if ((pfmi->dwFlags & 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->dwFlags & FMI_EMPTY)
{
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);
}
if (fFrameRect)
{
HBRUSH hbrFill = (HBRUSH)GetSysColorBrush(COLOR_HIGHLIGHT);
HBRUSH hbrSave = (HBRUSH)SelectObject(pdi->hDC, hbrFill);
int x = pdi->rcItem.left;
int y = pdi->rcItem.top;
int cx = pdi->rcItem.right - x - 1;
int cy = pdi->rcItem.bottom - y - 1;
PatBlt(pdi->hDC, x, y, 1, cy, PATCOPY);
PatBlt(pdi->hDC, x + 1, y, cx, 1, PATCOPY);
PatBlt(pdi->hDC, x, y + cy, cx, 1, PATCOPY);
PatBlt(pdi->hDC, x + cx, y + 1, 1, cy, PATCOPY);
SelectObject(pdi->hDC, hbrSave);
}
// Get the image if it needs it,
if ((pfmi->iImage == -1) && pfmi->pidl && pfmi->psf)
{
pfmi->iImage = SHMapPIDLToSystemImageListIndex(pfmi->psf, pfmi->pidl, NULL);
}
// Draw the image (if there is one).
if (pfmi->iImage != -1)
{
// Try to center image.
y = (pdi->rcItem.bottom + pdi->rcItem.top - cyIcon) / 2;
HIMAGELIST himl;
Shell_GetImageLists(NULL, &himl);
ImageList_DrawEx(himl, pfmi->iImage, pdi->hDC, x, y, 0, 0,
GetBkColor(pdi->hDC), CLR_NONE, ILD_NORMAL);
}
if (hbrOld)
SelectObject(pdi->hDC, hbrOld);
}
return TRUE;
}
DWORD FileMenuItem_GetExtent(FILEMENUITEM *pfmi)
{
DWORD dwExtent = 0;
if (pfmi)
{
FILEMENUHEADER *pfmh = pfmi->pfmh;
ASSERT(IS_VALID_STRUCT_PTR(pfmh, FILEMENUHEADER));
HDC hdcMem = CreateCompatibleDC(NULL);
if (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.
NONCLIENTMETRICS ncm = {0};
ncm.cbSize = sizeof(ncm);
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE))
{
HFONT hfont = CreateFontIndirect(&ncm.lfMenuFont);
if (hfont)
{
HFONT hfontOld = SelectFont(hdcMem, hfont);
dwExtent = GetItemExtent(hdcMem, pfmi);
SelectFont(hdcMem, hfontOld);
DeleteObject(hfont);
}
}
DeleteDC(hdcMem);
}
}
return dwExtent;
}
LRESULT FileMenu_MeasureItem(HWND hwnd, MEASUREITEMSTRUCT *pmi)
{
DWORD dwExtent = FileMenuItem_GetExtent((FILEMENUITEM *)pmi->itemData);
pmi->itemHeight = HIWORD(dwExtent);
pmi->itemWidth = LOWORD(dwExtent);
return TRUE;
}
// Fills the given filemenu with contents of the appropriate folder
//
// Returns: S_OK if all the files were added
// error on something bad
STDAPI FileMenu_InitMenuPopup(HMENU hmenu)
{
HRESULT hr = E_FAIL;
FILEMENUITEM *pfmi = FileMenu_GetItemData(hmenu, 0, TRUE);
if (pfmi)
{
FILEMENUHEADER *pfmh = pfmi->pfmh;
if (pfmh)
{
hr = S_OK;
// Have we already filled this thing out?
if ((FMI_MARKER | FMI_EXPAND) == (pfmi->dwFlags & (FMI_MARKER | FMI_EXPAND)))
{
// No, do it now. Get the previously init'ed header.
IShellFolder *psf;
if (FileMenuHeader_DeleteMarkerItem(pfmh, &psf))
{
// Fill it full of stuff.
int cItems;
hr = FileMenuHeader_AddFiles(pfmh, psf, 0, &cItems);
psf->Release();
}
}
}
}
return hr;
}
int FileMenuHeader_LastSelIndex(FILEMENUHEADER *pfmh)
{
for (int i = GetMenuItemCount(pfmh->hmenu) - 1; i >= 0; i--)
{
FILEMENUITEM *pfmi = FileMenu_GetItemData(pfmh->hmenu, i, TRUE);
if (pfmi && (pfmi == pfmh->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)
{
// Find the first ampersand.
LPTSTR 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)
{
FILEMENUITEM *pfmi;
TCHAR szName[MAX_PATH];
int iFoundOne = -1;
UINT iStep = 0;
UINT iItem = 0;
UINT cItems = GetMenuItemCount(hmenu);
// Start from the last place we looked from.
FILEMENUHEADER *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)
{
FileMenuItem_GetDisplayName(pfmi, szName, ARRAYSIZE(szName));
if (_MenuCharMatch(szName, ch, pfmi->pidl ? TRUE : FALSE))
{
// 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);
FileMenuItem_SetItem(pfmi, FALSE);
return MAKELRESULT(iFoundOne, MNC_EXECUTE);
}
else
{
// Didn't find it.
return MAKELRESULT(0, MNC_IGNORE);
}
}