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

2128 lines
63 KiB

//---------------------------------------------------------------------------
// Helper routines for an owner draw menu showing the contents of a directory.
//---------------------------------------------------------------------------
#include "shellprv.h"
#pragma hdrstop
#define CXIMAGEGAP 6
// #define SRCSTENCIL 0x00B8074AL
//---------------------------------------------------------------------------
typedef enum
{
FMII_NULL = 0x0000,
FMII_BREAK = 0x0001,
} FMIIFLAGS;
typedef enum
{
FMI_NULL = 0x0000,
FMI_MARKER = 0x0001,
FMI_FOLDER = 0x0002,
FMI_EXPAND = 0x0004,
FMI_EMPTY = 0x0008,
FMI_SEPARATOR = 0x0010,
FMI_DISABLED = 0x0020, // Enablingly Challenged ???
FMI_ALTITEM = 0x0040, // Item came from alternate pidl
} FILEMENUITEMFLAGS;
// One of these per file menu.
typedef struct
{
IShellFolder *psf; // Shell Folder.
HMENU hmenu; // Menu.
LPITEMIDLIST pidlFolder; // Pidl for the folder.
HDPA hdpaFMI; // List of items (see below).
UINT idItems; // Command.
FMFLAGS 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.
IShellFolder *psfAlt; // Alternate Shell Folder.
LPITEMIDLIST pidlAltFolder; // Pidl for the alternate folder.
HDPA hdpaFMIAlt; // Alternate dpa
int cyMenuSizeSinceLastBreak; // Size of menu (cy)
} FILEMENUHEADER, *PFILEMENUHEADER;
// One of these for each file menu item.
typedef struct
{
PFILEMENUHEADER pFMH; // The header.
int iImage; // Image index to use.
FILEMENUITEMFLAGS Flags; // Misc flags above.
LPITEMIDLIST pidl; // IDlist for item.
LPTSTR psz; // Text when not using pidls.
UINT cyItem; // Custom height.
} FILEMENUITEM, *PFILEMENUITEM;
//---------------------------------------------------------------------------
#pragma data_seg(DATASEG_PERINSTANCE)
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;
#pragma data_seg()
// in defviewx.c
extern HRESULT SHGetIconFromPIDL(IShellFolder *psf, IShellIcon *psi, LPCITEMIDLIST pidl, UINT flags, int *piImage);
//---------------------------------------------------------------------------
// STDMETHODIMP FS_CompareItemIDs(LPCSHITEMID pmkid1, LPCSHITEMID pmkid2);
int FileMenuHeader_AddFilesForPidl(PFILEMENUHEADER pFMH);
BOOL FileMenuHeader_InsertItem(PFILEMENUHEADER pFMH, UINT iItem, FMIIFLAGS fFlags);
UINT FileMenu_DeleteAllItems(HMENU hmenu);
UINT FileMenuHeader_DeleteAllItems(PFILEMENUHEADER pFMH);
//---------------------------------------------------------------------------
void DeleteGlobalMemDCAndFont(void)
{
if (g_hdcMem)
{
DeleteDC(g_hdcMem);
g_hdcMem = NULL;
}
if (g_hfont)
{
DeleteObject(g_hfont);
g_hfont = NULL;
}
}
//---------------------------------------------------------------------------
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(PFILEMENUITEM pFMI, LPTSTR pszName, UINT cchName)
{
STRRET str;
VDATEINPUTBUF(pszName, TCHAR, cchName);
Assert(pFMI);
Assert(pszName);
// Is this a special empy item
if (pFMI->Flags & FMI_EMPTY)
{
// Yep, load the string from a resource.
LoadString(HINST_THISDLL, IDS_NONE, pszName, cchName);
}
else
{
PFILEMENUHEADER pFMH = pFMI->pFMH;
LPSHELLFOLDER psfTemp;
// Nope, ask the folder for the name of the item.
Assert(pFMH);
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)
{
if (SUCCEEDED(psfTemp->lpVtbl->GetDisplayNameOf(psfTemp, pFMI->pidl, SHGDN_NORMAL, &str)))
{
StrRetToStrN(pszName, cchName, &str, pFMI->pidl);
}
}
else if (pFMI->psz)
{
lstrcpyn(pszName, pFMI->psz, cchName);
}
else
{
*pszName = TEXT('\0');
}
}
}
#define FileMenuHeader_AllowAbort(pFMH) (!(pFMH->fmf & FMF_NOABORT))
//---------------------------------------------------------------------------
// Returns the count of items added to the list.
int WINAPI FileList_Build(PFILEMENUHEADER pFMH, BOOL bUseAlt)
{
#ifdef DEBUG
TCHAR szName[MAX_PATH];
#endif
int cItems = 0;
int cDPAItems;
HDPA dpaTemp;
HRESULT hres;
LPITEMIDLIST pidlSkip = NULL;
LPITEMIDLIST pidlProgs = NULL;
Assert(pFMH);
if (FileMenuHeader_AllowAbort(pFMH) && g_fAbortInitMenu)
return 0;
if (bUseAlt) {
dpaTemp = pFMH->hdpaFMIAlt;
} else {
dpaTemp = pFMH->hdpaFMI;
}
if (dpaTemp && pFMH->psf)
{
LPENUMIDLIST penum;
LPSHELLFOLDER psfTemp;
cDPAItems = DPA_GetPtrCount(dpaTemp);
// 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))
{
DebugMsg(DM_TRACE, TEXT("s.fl_b: 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->lpVtbl->EnumObjects(psfTemp, NULL, pFMH->fFSFilter, &penum);
if (SUCCEEDED(hres))
{
UINT celt;
LPITEMIDLIST pidl = NULL;
while (penum->lpVtbl->Next(penum, 1, &pidl, &celt) == S_OK && celt == 1)
{
PFILEMENUITEM pFMI;
// Abort.
if (FileMenuHeader_AllowAbort(pFMH) && g_fAbortInitMenu)
break;
// HACK: We directly call this FS function to compare two pidls.
// We may want to skip this item.
// if (pidlSkip && FS_CompareItemIDs(&pidlSkip->mkid, &pidl->mkid) == 0)
if (pidlSkip && psfTemp->lpVtbl->CompareIDs(psfTemp, 0, pidlSkip, pidl) == 0)
{
ILFree(pidl); // Don't leak this one...
DebugMsg(DM_TRACE, TEXT("s.fl_b: Skipping Programs."));
continue;
}
pFMI = (LPVOID)LocalAlloc(LPTR, SIZEOF(FILEMENUITEM));
if (pFMI)
{
DWORD dwAttribs = SFGAO_FOLDER | SFGAO_FILESYSTEM;
pFMI->pFMH = pFMH;
pFMI->pidl = pidl;
pFMI->iImage = -1;
// pFMI->iItem = 0;
pFMI->Flags = bUseAlt ? FMI_ALTITEM : 0;
if (SUCCEEDED(psfTemp->lpVtbl->GetAttributesOf(psfTemp, 1, &pidl, &dwAttribs)))
{
if (dwAttribs & SFGAO_FOLDER)
{
#ifdef DEBUG
FileMenuItem_GetDisplayName(pFMI, szName, ARRAYSIZE(szName));
// DebugMsg(DM_TRACE, "fl_b: Folder %s", szName);
#endif
pFMI->Flags |= FMI_FOLDER;
}
else
{
// NB We only callback for non-folders at the mo.
//
// 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 && (dwAttribs & SFGAO_FILESYSTEM)) {
if (bUseAlt) {
pFMH->pfncb(pFMH->pidlAltFolder, pidl);
} else {
pFMH->pfncb(pFMH->pidlFolder, pidl);
}
}
#ifdef DEBUG
FileMenuItem_GetDisplayName(pFMI, szName, ARRAYSIZE(szName));
// DebugMsg(DM_TRACE, "fl_b: Non-Folder %s", szName);
#endif
}
}
DPA_InsertPtr(dpaTemp, cDPAItems, pFMI);
cItems++;
cDPAItems++;
}
}
penum->lpVtbl->Release(penum);
}
else
{
DebugMsg(DM_ERROR, TEXT("s.fl_b: Enumeration failed - leaving folder empty."));
}
ILFree(pidlProgs);
}
// Insert a special Empty item (unless the header flag says
// not to).
if (!cItems && dpaTemp && !(pFMH->fmf & FMF_NOEMPTYITEM) && !bUseAlt)
{
PFILEMENUITEM pFMI = (LPVOID)LocalAlloc(LPTR, SIZEOF(FILEMENUITEM));
if (pFMI)
{
pFMI->pFMH = pFMH;
pFMI->pidl = NULL;
pFMI->iImage = -1;
pFMI->Flags = FMI_EMPTY;
DPA_SetPtr(dpaTemp, cItems, pFMI);
cItems++;
}
}
return cItems;
}
//---------------------------------------------------------------------------
// Simplified version of the file info comparison function.
int CALLBACK FileMenuItem_Compare(PFILEMENUITEM pfmi1, PFILEMENUITEM pfmi2, LPARAM lParam)
{
TCHAR szName1[MAX_PATH];
TCHAR szName2[MAX_PATH];
// Directories come first, then files and links.
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));
return lstrcmpi(szName1, szName2);
}
//---------------------------------------------------------------------------
// Sort the list alphabetically.
void WINAPI FileList_Sort(HDPA hdpa)
{
DPA_Sort(hdpa, FileMenuItem_Compare, 0);
}
//---------------------------------------------------------------------------
// 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 WINAPI GetItemExtent(HDC hdc, PFILEMENUITEM pFMI)
{
WORD wHeight;
WORD wWidth;
DWORD dwExtent;
TCHAR szName[MAX_PATH];
PFILEMENUHEADER pFMH;
BITMAP bmp;
Assert(pFMI);
FileMenuItem_GetDisplayName(pFMI, szName, ARRAYSIZE(szName));
dwExtent = GetItemTextExtent(hdc, szName);
wHeight = HIWORD(dwExtent);
pFMH = pFMI->pFMH;
Assert(pFMH);
// 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)) + 6;
}
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.
// BUGBUG popup triangle size needs to be real
wWidth = LOWORD(dwExtent) + GetSystemMetrics(SM_CXMENUCHECK);
// Keep track of the width and height of the bitmap.
if (pFMH->hbmp && !pFMH->cxBmp && !pFMH->cyBmp)
{
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);
}
//---------------------------------------------------------------------------
PFILEMENUITEM WINAPI FileMenu_GetItemData(HMENU hmenu, UINT iItem)
{
MENUITEMINFO mii;
mii.cbSize = SIZEOF(MENUITEMINFO);
mii.fMask = MIIM_DATA | MIIM_STATE;
mii.cch = 0; // just in case
if (GetMenuItemInfo(hmenu, iItem, TRUE, &mii))
return (PFILEMENUITEM)mii.dwItemData;
return NULL;
}
//----------------------------------------------------------------------------
PFILEMENUHEADER FileMenu_GetHeader(HMENU hmenu)
{
PFILEMENUITEM pFMI = FileMenu_GetItemData(hmenu, 0);
return pFMI ? pFMI->pFMH : NULL;
}
//---------------------------------------------------------------------------
PFILEMENUHEADER FileMenuHeader_Create(HMENU hmenu, HBITMAP hbmp,
int cxBmpGap, COLORREF clrBkg, int cySel, PFNFMCALLBACK pfncb)
{
// Does this guy already have a header?
PFILEMENUITEM pFMI = FileMenu_GetItemData(hmenu, 0);
if (pFMI)
{
// Yep.
pFMI->pFMH->pfncb = pfncb;
return pFMI->pFMH;
}
else
{
// Nope, create one now.
PFILEMENUHEADER pFMH = (PFILEMENUHEADER)LocalAlloc(LPTR, SIZEOF(FILEMENUHEADER));
if (pFMH)
{
// Keep track of the header.
// DebugMsg(DM_TRACE, "s.fmh_c: Creating filemenu for %#08x (%x)", hmenu, pFMH);
pFMH->hdpaFMI = DPA_Create(0);
if (pFMH->hdpaFMI == NULL)
{
LocalFree((HLOCAL)pFMH);
return FALSE;
}
pFMH->hmenu = hmenu;
pFMH->hbmp = hbmp;
pFMH->cxBmpGap = cxBmpGap;
pFMH->clrBkg = clrBkg;
pFMH->cySel = cySel;
pFMH->pfncb = pfncb;
return pFMH;
}
}
return FALSE;
}
//---------------------------------------------------------------------------
// Set info specific to a folder.
BOOL FileMenuHeader_SetFolderInfo(PFILEMENUHEADER pFMH,
UINT idNewItems, LPCITEMIDLIST pidlFolder, UINT fFSFilter)
{
Assert(pFMH);
// Keep track of the header.
pFMH->idItems = idNewItems;
pFMH->fFSFilter = fFSFilter;
if (pidlFolder)
{
pFMH->pidlFolder = ILClone(pidlFolder);
if (pFMH->pidlFolder)
{
LPSHELLFOLDER psfDesktop = Desktop_GetShellFolder(TRUE);
if (SUCCEEDED(psfDesktop->lpVtbl->BindToObject(psfDesktop,
pFMH->pidlFolder, NULL, &IID_IShellFolder, &pFMH->psf)))
{
return TRUE;
}
ILFree(pFMH->pidlFolder);
}
}
return FALSE;
}
//---------------------------------------------------------------------------
// 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 = (PFILEMENUITEM)LocalAlloc(LPTR, SIZEOF(FILEMENUITEM));
if (pFMI)
{
pFMI->pFMH = pFMH;
pFMI->pidl = NULL;
pFMI->iImage = -1;
pFMI->Flags = FMI_MARKER|FMI_EXPAND;
DPA_SetPtr(pFMH->hdpaFMI, 0, pFMI);
FileMenuHeader_InsertItem(pFMH, 0, FMII_NULL);
return TRUE;
}
DebugMsg(DM_ERROR, TEXT("t.fmh_imi: Can't create marker item."));
return FALSE;
}
//---------------------------------------------------------------------------
// Insert the given at the given position.
BOOL FileMenuHeader_InsertItem(PFILEMENUHEADER pFMH, UINT iItem, FMIIFLAGS fFlags)
{
PFILEMENUITEM pFMI;
UINT fMenu;
Assert(pFMH);
// Normal item.
pFMI = DPA_GetPtr(pFMH->hdpaFMI, iItem);
if (!pFMI)
return FALSE;
// 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;
// Insert it into the parent menu.
fMenu |= MF_POPUP;
InsertMenu(pFMH->hmenu, iItem, fMenu, (UINT)hmenuSub, (LPTSTR)pFMI);
// Set it's ID.
mii.cbSize = SIZEOF(mii);
mii.fMask = MIIM_ID;
mii.wID = pFMH->idItems;
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, pFMH->pfncb);
Assert(pFMH);
if (pFMH)
{
FileMenuHeader_SetFolderInfo(pFMHSub, pFMH->idItems, pidlSub, pFMH->fFSFilter);
// Magically inherit the NOPROGRAMS flag.
pFMHSub->fmf = pFMH->fmf & FMF_NOPROGRAMS;
// 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->idItems, (LPTSTR)pFMI);
}
return TRUE;
}
//---------------------------------------------------------------------------
void WINAPI FileList_AddToMenu(PFILEMENUHEADER pFMH, BOOL bUseAlt, BOOL bAddSeparatorSpace)
{
UINT i, cItems, cItemsAlt;
PFILEMENUITEM pFMI;
int cyMenu, cyItem, cyMenuMax;
HDC hdc;
HFONT hfont, hfontOld;
NONCLIENTMETRICS ncm;
int Index;
cyItem = 0;
cyMenu = pFMH->cyMenuSizeSinceLastBreak;
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 = SelectObject(hdc, hfont);
cyItem = HIWORD(GetItemExtent(hdc, DPA_GetPtr((bUseAlt ? pFMH->hdpaFMIAlt : pFMH->hdpaFMI), 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(pFMH->hdpaFMI);
if (bUseAlt) {
cItemsAlt = DPA_GetPtrCount(pFMH->hdpaFMIAlt);
}
for (i = 0; i < (bUseAlt ? cItemsAlt : cItems); i++)
{
if (bUseAlt) {
//
// We need to move the items from the alternate pidl
// to the main pidl and use the new index.
//
pFMI = DPA_GetPtr(pFMH->hdpaFMIAlt, i);
if (!pFMI)
continue;
Index = DPA_InsertPtr (pFMH->hdpaFMI, cItems, pFMI);
cItems++;
} else {
Index = i;
}
// Keep a rough count of the height of the menu.
cyMenu += cyItem;
if (cyMenu > cyMenuMax && !(pFMH->fmf & FMF_NOBREAK))
{
FileMenuHeader_InsertItem(pFMH, Index, FMII_BREAK);
cyMenu = cyItem;
}
else
{
FileMenuHeader_InsertItem(pFMH, Index, FMII_NULL);
}
}
//
// Save the current cy size so we can use this again
// if more items are appended to this menu.
//
pFMH->cyMenuSizeSinceLastBreak = cyMenu;
}
#define PREFETCH_ICONS
#ifdef PREFETCH_ICONS
//---------------------------------------------------------------------------
BOOL WINAPI FileList_AddImages(PFILEMENUHEADER pFMH, BOOL bUseAlt)
{
PFILEMENUITEM pFMI;
LPSHELLFOLDER psf;
LPSHELLICON psi = NULL;
int i, cItems;
HDPA hdpaTemp;
if (bUseAlt) {
hdpaTemp = pFMH->hdpaFMIAlt;
psf = pFMH->psfAlt;
} else {
hdpaTemp = pFMH->hdpaFMI;
psf = pFMH->psf;
}
if (psf)
psf->lpVtbl->QueryInterface( psf, &IID_IShellIcon, &psi );
cItems = DPA_GetPtrCount(hdpaTemp);
for (i = 0; i < cItems; i++)
{
if (FileMenuHeader_AllowAbort(pFMH) && g_fAbortInitMenu)
{
DebugMsg(DM_TRACE, TEXT("s.fl_ai: Abort: Defering images till later."));
break;
}
pFMI = DPA_GetPtr(hdpaTemp, i);
if (pFMI && (pFMI->pidl) && (pFMI->iImage == -1))
{
SHGetIconFromPIDL( psf, psi, pFMI->pidl, 0, &pFMI->iImage );
}
}
if (psi)
{
psi->lpVtbl->Release( psi );
}
return TRUE;
}
#else
#define FileList_AddImages(foo, bar)
#endif
//---------------------------------------------------------------------------
// Clean up the items created but FileList_Build;
void FileList_UnBuild(PFILEMENUHEADER pFMH)
{
int cItems;
int i;
cItems = DPA_GetPtrCount(pFMH->hdpaFMI);
for (i=cItems-1; i>=0; i--)
{
PFILEMENUITEM pFMI = DPA_GetPtr(pFMH->hdpaFMI, i);
if (pFMI)
{
if (pFMI->pidl)
ILFree(pFMI->pidl);
if (pFMI->psz)
LocalFree((HLOCAL)pFMI->psz);
LocalFree((HLOCAL)pFMI);
DPA_DeletePtr(pFMH->hdpaFMI, i);
}
}
}
//---------------------------------------------------------------------------
int FileMenuHeader_AddFilesForPidl(PFILEMENUHEADER pFMH)
{
int cItems = FileList_Build(pFMH, FALSE);
// If the build was aborted cleanup and early out.
if (FileMenuHeader_AllowAbort(pFMH) && g_fAbortInitMenu)
{
// Cleanup.
DebugMsg(DM_TRACE, TEXT("c.fmh_affp: FileList_Build aborted."));
FileList_UnBuild(pFMH);
cItems = -1;
}
else
{
if (cItems > 1)
FileList_Sort(pFMH->hdpaFMI);
if (cItems != 0)
{
pFMH->cyMenuSizeSinceLastBreak = 0;
FileList_AddImages(pFMH, FALSE);
FileList_AddToMenu(pFMH, FALSE, FALSE);
}
}
if (g_fAbortInitMenu)
g_fAbortInitMenu = FALSE;
// DebugMsg(DM_TRACE, "ts.fm_af: Added %d filemenu items.", cItems);
return cItems;
}
//---------------------------------------------------------------------------
int FileMenuHeader_AppendFilesForPidl(PFILEMENUHEADER pFMH, BOOL bInsertSeparator)
{
int cItems = FileList_Build(pFMH, TRUE);
// If the build was aborted cleanup and early out.
if (FileMenuHeader_AllowAbort(pFMH) && g_fAbortInitMenu)
{
// Cleanup.
DebugMsg(DM_TRACE, TEXT("c.fmh_affp: FileList_Build aborted."));
FileList_UnBuild(pFMH);
cItems = -1;
}
else
{
if (cItems > 1)
FileList_Sort(pFMH->hdpaFMIAlt);
if (cItems != 0)
{
if (bInsertSeparator) {
// insert a line
FileMenu_AppendItem(pFMH->hmenu, (LPTSTR)FMAI_SEPARATOR, 0, -1, NULL, 0);
}
FileList_AddImages(pFMH, TRUE);
FileList_AddToMenu(pFMH, TRUE, bInsertSeparator);
}
}
if (g_fAbortInitMenu)
g_fAbortInitMenu = FALSE;
// DebugMsg(DM_TRACE, "ts.fm_af: Added %d filemenu items.", cItems);
return cItems;
}
//----------------------------------------------------------------------------
// Free up a header (you should delete all the items first).
void FileMenuHeader_Destroy(PFILEMENUHEADER pFMH)
{
Assert(pFMH);
//DebugMsg(DM_TRACE, "s.fmh_d: Destroy filemenu for (%x)", pFMH);
// Clean up the header.
DPA_Destroy(pFMH->hdpaFMI);
if (pFMH->pidlFolder)
{
ILFree(pFMH->pidlFolder);
pFMH->pidlFolder = NULL;
}
if (pFMH->psf)
{
pFMH->psf->lpVtbl->Release(pFMH->psf);
pFMH->psf = NULL;
}
if (pFMH->pidlAltFolder)
{
ILFree(pFMH->pidlAltFolder);
pFMH->pidlAltFolder = NULL;
}
if (pFMH->psfAlt)
{
pFMH->psfAlt->lpVtbl->Release(pFMH->psfAlt);
pFMH->psfAlt = 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)
{
// 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->idItems)
{
// With item data and the marker flag set.
PFILEMENUITEM pFMI = FileMenu_GetItemData(pFMH->hmenu, 0);
if (pFMI && (pFMI->Flags & FMI_MARKER))
{
// Delete it.
Assert(pFMH->hdpaFMI);
Assert(DPA_GetPtrCount(pFMH->hdpaFMI) == 1);
// NB The marker shouldn't have a pidl.
Assert(!pFMI->pidl);
LocalFree((HLOCAL)pFMI);
DPA_DeletePtr(pFMH->hdpaFMI, 0);
DeleteMenu(pFMH->hmenu, 0, MF_BYPOSITION);
// Cleanup OK.
return TRUE;
}
}
}
// DebugMsg(DM_TRACE,"t.fmh_dei: Can't find marker item.");
return FALSE;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Returns the number of items added.
UINT WINAPI FileMenu_AddFilesForPidl(HMENU hmenu, UINT iPos, UINT idNewItems,
LPITEMIDLIST pidl, FMFLAGS fmf, UINT fMenuFilter, PFNFMCALLBACK pfncb)
{
int cItems = 0;
BOOL fMarker = FALSE;
PFILEMENUHEADER pFMH = FileMenuHeader_Create(hmenu, NULL, 0, (COLORREF)-1, 0, pfncb);
if (pFMH)
{
PFILEMENUITEM pFMI = FileMenu_GetItemData(hmenu, 0);
if (pFMI)
{
// Clean up marker item if there is one.
if ((pFMI->Flags & FMI_MARKER) && (pFMI->Flags & FMI_EXPAND))
{
// Nope, do it now.
// DebugMsg(DM_TRACE, "t.fm_ii: Removing marker item.");
FileMenuHeader_DeleteMarkerItem(pFMH);
fMarker = TRUE;
}
}
// Add the new stuf.
FileMenuHeader_SetFolderInfo(pFMH, idNewItems, pidl, fMenuFilter);
pFMH->fmf = (pFMH->fmf & ~FMF_FILESMASK) | (fmf & FMF_FILESMASK);
pFMH->fmf |= FMF_NOABORT;
cItems = FileMenuHeader_AddFilesForPidl(pFMH);
pFMH->fmf = pFMH->fmf & ~FMF_NOABORT;
if (cItems <= 0 && fMarker)
{
// Aborted or no items. Put the marker back (if there used
// to be one).
FileMenuHeader_InsertMarkerItem(pFMH);
}
}
return cItems;
}
//---------------------------------------------------------------------------
// Returns the number of items added.
UINT WINAPI FileMenu_AppendFilesForPidl(HMENU hmenu, LPITEMIDLIST pidl,
BOOL bInsertSeparator)
{
int cItems = 0;
BOOL fMarker = FALSE;
PFILEMENUHEADER pFMH;
PFILEMENUITEM pFMI = FileMenu_GetItemData(hmenu, 0);
//
// 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.
// DebugMsg(DM_TRACE, "t.fm_ii: Removing marker item.");
FileMenuHeader_DeleteMarkerItem(pFMH);
fMarker = TRUE;
}
// Add the new stuff.
if (pidl)
{
LPSHELLFOLDER psfDesktop = Desktop_GetShellFolder(TRUE);
pFMH->pidlAltFolder = ILClone(pidl);
if (pFMH->pidlAltFolder) {
pFMH->hdpaFMIAlt = DPA_Create(0);
if (pFMH->hdpaFMIAlt) {
if (SUCCEEDED(psfDesktop->lpVtbl->BindToObject(psfDesktop,
pFMH->pidlAltFolder, NULL, &IID_IShellFolder, &pFMH->psfAlt)))
{
pFMH->fmf |= FMF_NOABORT;
cItems = FileMenuHeader_AppendFilesForPidl(pFMH, bInsertSeparator);
pFMH->fmf = pFMH->fmf & ~FMF_NOABORT;
}
DPA_Destroy (pFMH->hdpaFMIAlt);
pFMH->hdpaFMIAlt = NULL;
}
}
}
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(PFILEMENUHEADER pFMH)
{
int i;
int cItems = 0;
if (!pFMH)
{
DebugMsg(DM_ERROR, TEXT("s.fmh_dai: Invalid filemenu header."));
Assert(0);
return 0;
}
// Notify.
if (pFMH->pfncb)
pFMH->pfncb(pFMH->pidlFolder, NULL);
// Clean up the items.
cItems = DPA_GetPtrCount(pFMH->hdpaFMI);
// Do this backwards to stop things moving around as
// we delete them.
for (i = cItems - 1; i >= 0; i--)
{
PFILEMENUITEM pFMI = DPA_GetPtr(pFMH->hdpaFMI, 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);
// NB Empty menu's don't have item pidls.
if (pFMI->pidl)
ILFree(pFMI->pidl);
//
if (pFMI->psz)
LocalFree((HLOCAL)pFMI->psz);
LocalFree((HLOCAL)pFMI);
DPA_DeletePtr(pFMH->hdpaFMI, 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 WINAPI FileMenu_DeleteAllItems(HMENU hmenu)
{
PFILEMENUHEADER pFMH;
if (!IsMenu(hmenu))
return 0;
// From an item we should be able to get to the menu header.
pFMH = FileMenu_GetHeader(hmenu);
// Now we have the header cleaning up is easier.
if (pFMH)
{
UINT cItems = FileMenuHeader_DeleteAllItems(pFMH);
FileMenuHeader_Destroy(pFMH);
return cItems;
}
return 0;
}
//---------------------------------------------------------------------------
void WINAPI FileMenu_Destroy(HMENU hmenu)
{
// DebugMsg(DM_TRACE, "s.fm_d: Destroying filemenu for %#08x", hmenu);
FileMenu_DeleteAllItems(hmenu);
DestroyMenu(hmenu);
//
// 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();
}
//---------------------------------------------------------------------------
// Remove all the items from the given menu and add filemenu items for
// the given directory.
UINT WINAPI FileMenu_ReplaceUsingPidl(HMENU hmenu, UINT idNewItems,
LPITEMIDLIST pidl, UINT fMenuFilter, PFNFMCALLBACK pfnfmcb)
{
UINT cItems;
// DWORD dwTime = GetTickCount();
FileMenu_DeleteAllItems(hmenu);
cItems = FileMenu_AddFilesForPidl(hmenu, 0, idNewItems, pidl, FMF_NONE, fMenuFilter, pfnfmcb);
// DebugMsg(DM_TRACE,"ts.fm_r: %d msec to replace menu", GetTickCount()-dwTime);
return cItems;
}
//---------------------------------------------------------------------------
// Cause the given filemenu to be rebuilt.
void WINAPI FileMenu_Invalidate(HMENU hmenu)
{
// Is this a filemenu?
// NB First menu item must be a FileMenuItem.
PFILEMENUITEM pFMI = FileMenu_GetItemData(hmenu, 0);
if (pFMI)
{
// Yep, Is there already a marker here?
if ((pFMI->Flags & FMI_MARKER) && (pFMI->Flags & FMI_EXPAND))
{
DebugMsg(DM_TRACE, TEXT("c.gm_i: Menu is already invalid."));
}
else if (pFMI->pFMH)
{
PFILEMENUHEADER pFMHSave = pFMI->pFMH;
FileMenuHeader_DeleteAllItems(pFMI->pFMH);
// above call freed pFMI
FileMenuHeader_InsertMarkerItem(pFMHSave);
}
}
}
//---------------------------------------------------------------------------
// Add filemenu items for the given directory to begining of the given
// menu.
UINT WINAPI FileMenu_InsertUsingPidl(HMENU hmenu, UINT idNewItems, LPITEMIDLIST pidl, FMFLAGS fmf,
UINT fFSFilter, PFNFMCALLBACK pfnfmcb)
{
// DWORD dwTime = GetTickCount();
UINT cItems = FileMenu_AddFilesForPidl(hmenu, 0, idNewItems, pidl, fmf, fFSFilter, pfnfmcb);
// DebugMsg(DM_TRACE,"ts.fm_i: %d msec to replace menu", GetTickCount()-dwTime);
return cItems;
}
//---------------------------------------------------------------------------
LRESULT WINAPI 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;
extern HIMAGELIST himlIconsSmall;
extern HIMAGELIST himlIcons;
RECT rcClip;
if ((pdi->itemAction & ODA_SELECT) || (pdi->itemAction & ODA_DRAWENTIRE))
{
PFILEMENUHEADER pFMH;
PFILEMENUITEM pFMI = (PFILEMENUITEM)pdi->itemData;
if (!pFMI)
{
DebugMsg(DM_ERROR, TEXT("fm_di: Filemenu is invalid (no item data)."));
return FALSE;
}
pFMH = pFMI->pFMH;
Assert(pFMH);
// Adjust for large/small icons.
if (pFMH->fmf & FMF_LARGEICONS)
{
cxIcon = g_cxIcon;
cyIcon = g_cyIcon;
}
else
{
cxIcon = g_cxSmIcon;
cyIcon = g_cxSmIcon;
}
// Reset the last selection item when a menu is
// drawn the first time.
if (pFMI == DPA_GetPtr(pFMH->hdpaFMI, 0) &&
(pdi->itemAction & ODA_DRAWENTIRE))
{
g_pFMILastSelNonFolder = NULL;
g_pFMILastSel = NULL;
}
if (pdi->itemState & ODS_SELECTED)
{
SetBkColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHT));
SetTextColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
hbrOld = SelectObject(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;
}
else
{
// dwRop = SRCAND;
hbrOld = SelectObject(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->hdpaFMI, 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 = SelectObject(g_hdcMem, pFMH->hbmp);
BitBlt(pdi->hDC, 0, pFMH->yBmp-pFMH->cyBmp, pFMH->cxBmp, pFMH->cyBmp, g_hdcMem, 0, 0, SRCCOPY);
SelectObject(g_hdcMem, hbmOld);
}
}
x += pFMH->cxBmpGap;
}
// Background color for when the bitmap runs out.
if ((pFMH->clrBkg != (COLORREF)-1) &&
(pFMI == DPA_GetPtr(pFMH->hdpaFMI, 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);
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));
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.
if ((pFMI->Flags & FMI_EMPTY) || (pFMI->Flags & FMI_DISABLED))
{
int fDSFlags;
if (pdi->itemState & ODS_SELECTED)
{
if (GetSysColor(COLOR_GRAYTEXT) == GetSysColor(COLOR_HIGHLIGHTTEXT))
{
if (pFMI->psz)
fDSFlags = DST_PREFIXTEXT| DSS_UNION;
else
fDSFlags = DST_TEXT| DSS_UNION;
}
else
{
SetTextColor(pdi->hDC, GetSysColor(COLOR_GRAYTEXT));
if (pFMI->psz)
fDSFlags = DST_PREFIXTEXT;
else
fDSFlags = DST_TEXT;
}
}
else
{
if (pFMI->psz)
fDSFlags = DST_PREFIXTEXT | DSS_DISABLED;
else
fDSFlags = DST_TEXT | DSS_DISABLED;
}
ExtTextOut(pdi->hDC, 0, 0, ETO_OPAQUE, &pdi->rcItem, NULL, 0, NULL);
DrawState(pdi->hDC, NULL, NULL, (LONG)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)szName, lstrlen(szName), x+cxIcon+CXIMAGEGAP,
y, 0, 0, pFMI->psz? DST_PREFIXTEXT : DST_TEXT);
}
// Get the image if it needs it,
if ((pFMI->iImage == -1) && pFMI->pidl && pFMH->psf)
{
pFMI->iImage = SHMapPIDLToSystemImageListIndex(pFMH->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 = 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 = 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);
}
}
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(pFMH);
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 = SelectObject(g_hdcMem, g_hfont);
dwExtent = GetItemExtent(g_hdcMem, pFMI);
SelectObject(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
{
DebugMsg(DM_ERROR, TEXT("fm_gie: Filemenu is invalid."));
}
return dwExtent;
}
//---------------------------------------------------------------------------
LRESULT WINAPI FileMenu_MeasureItem(HWND hwnd, MEASUREITEMSTRUCT *lpmi)
{
DWORD dwExtent = FileMenuItem_GetExtent((PFILEMENUITEM)lpmi->itemData);
lpmi->itemHeight = HIWORD(dwExtent);
lpmi->itemWidth = LOWORD(dwExtent);
return TRUE;
}
//----------------------------------------------------------------------------
DWORD WINAPI FileMenu_GetItemExtent(HMENU hmenu, UINT iItem)
{
return FileMenuItem_GetExtent(DPA_GetPtr(FileMenu_GetHeader(hmenu)->hdpaFMI, iItem));
}
//----------------------------------------------------------------------------
HMENU WINAPI 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->hdpaFMI);
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 = DPA_GetPtr(pFMH->hdpaFMI, i);
// if (pFMI && pFMI->pidl && FS_CompareItemIDs(&pidlFS->mkid, &pFMI->pidl->mkid) == 0)
if (pFMI && pFMI->pidl && pFMH->psf->lpVtbl->CompareIDs(pFMH->psf, 0, pidlFS, pFMI->pidl) == 0)
{
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 screwed.
break;
}
}
}
}
return NULL;
}
//---------------------------------------------------------------------------
// Fills the given filemenu with contents of the appropriate directory.
// Returns FALSE if the given menu isn't a filemenu.
BOOL WINAPI FileMenu_InitMenuPopup(HMENU hmenu)
{
PFILEMENUITEM pFMI;
PFILEMENUHEADER pFMH;
g_fAbortInitMenu = FALSE;
// Is this a filemenu?
// NB First menu item must be a FileMenuItem.
pFMI = FileMenu_GetItemData(hmenu, 0);
if (pFMI)
{
pFMH = pFMI->pFMH;
// Yep, have we already filled this thing out?
if ((pFMI->Flags & FMI_MARKER) && (pFMI->Flags & FMI_EXPAND))
{
// Nope, do it now.
// DebugMsg(DM_TRACE, "t.fm_imp: Exanding folder menu.");
// Get the previously init'ed header.
if (pFMH)
{
FileMenuHeader_DeleteMarkerItem(pFMH);
// Fill it full of stuff.
if (FileMenuHeader_AddFilesForPidl(pFMH) == -1)
{
// Aborted - put the marker back.
FileMenuHeader_InsertMarkerItem(pFMH);
} else {
if (pFMH->pidlAltFolder) {
pFMH->hdpaFMIAlt = DPA_Create(0);
if (pFMH->hdpaFMIAlt) {
if (FileMenuHeader_AppendFilesForPidl(pFMH, TRUE) == -1) {
// Aborted - put the marker back.
FileMenuHeader_InsertMarkerItem(pFMH);
}
DPA_Destroy (pFMH->hdpaFMIAlt);
pFMH->hdpaFMIAlt = NULL;
}
}
}
}
}
return TRUE;
}
// Nope.
return FALSE;
}
//---------------------------------------------------------------------------
// This sets whether to load all the images while creating the menu or to
// defer it until the menu is actually being drawn.
void WINAPI FileMenu_AbortInitMenu(void)
{
g_fAbortInitMenu = TRUE;
}
//---------------------------------------------------------------------------
// Returns a clone of the last selected pidls.
BOOL WINAPI FileMenu_GetLastSelectedItemPidls(HMENU hmenu, LPITEMIDLIST *ppidlFolder, LPITEMIDLIST *ppidlItem)
{
if (g_pFMILastSelNonFolder)
{
// Get to the header.
PFILEMENUHEADER pFMH = g_pFMILastSelNonFolder->pFMH;
if (pFMH)
{
if (ppidlFolder)
if (g_pFMILastSelNonFolder->Flags & FMI_ALTITEM) {
*ppidlFolder = ILClone(pFMH->pidlAltFolder);
} else {
*ppidlFolder = ILClone(pFMH->pidlFolder);
}
if (ppidlItem)
{
*ppidlItem = ILClone(g_pFMILastSelNonFolder->pidl);
}
return TRUE;
}
}
DebugMsg(DM_ERROR, TEXT("c.fm_glsip: No previously selected item."));
return FALSE;
}
//---------------------------------------------------------------------------
#define AnsiUpperChar(c) ( (TCHAR)LOWORD((DWORD)CharUpper((LPTSTR)(DWORD)MAKELONG((DWORD) c, 0))) )
//---------------------------------------------------------------------------
int FileMenuHeader_LastSelIndex(PFILEMENUHEADER pFMH)
{
int i;
PFILEMENUITEM pFMI;
for (i = GetMenuItemCount(pFMH->hmenu)-1;i >= 0; i--)
{
pFMI = FileMenu_GetItemData(pFMH->hmenu, i);
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 (AnsiUpperChar(*CharNext(pchAS)) == AnsiUpperChar(ch))
{
// Yep.
return TRUE;
}
}
else if (AnsiUpperChar(*lpsz) == AnsiUpperChar(ch))
{
return TRUE;
}
return FALSE;
}
//---------------------------------------------------------------------------
LRESULT WINAPI 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);
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);
if (!(pFMI->Flags & FMI_FOLDER))
g_pFMILastSelNonFolder = pFMI;
return MAKELRESULT(iFoundOne, MNC_EXECUTE);
}
else
{
// Didn't find it.
return MAKELRESULT(0, MNC_IGNORE);
}
}
//----------------------------------------------------------------------------
// Create a filemenu
HMENU WINAPI FileMenu_Create(COLORREF clr, int cxBmpGap, HBITMAP hbmp, int cySel, FMFLAGS fmf)
{
HMENU hmenu = CreatePopupMenu();
if (hmenu)
{
PFILEMENUHEADER pFMH = FileMenuHeader_Create(hmenu, hbmp, cxBmpGap, clr, cySel, NULL);
if (pFMH)
{
// Default flags.
pFMH->fmf = fmf;
if (FileMenuHeader_InsertMarkerItem(pFMH))
return hmenu;
// FU
FileMenuHeader_Destroy(pFMH);
}
DestroyMenu(hmenu);
}
DebugMsg(DM_ERROR, TEXT("s.fm_c: Can't create file menu."));
return NULL;
}
//----------------------------------------------------------------------------
// Append generic item onto a filemenu.
BOOL WINAPI FileMenu_AppendItem(HMENU hmenu, LPTSTR psz, UINT id, int iImage,
HMENU hmenuSub, UINT cyItem)
{
PFILEMENUITEM pFMI;
UINT iItem;
// DebugMsg(DM_TRACE, "t.fm_ii:...");
// Is this a filemenu?
// NB First menu item must be a FileMenuItem.
pFMI = FileMenu_GetItemData(hmenu, 0);
if (pFMI)
{
PFILEMENUHEADER pFMH = pFMI->pFMH;
Assert(pFMH);
// Yep, have we cleaned up the marker item?
if ((pFMI->Flags & FMI_MARKER) && (pFMI->Flags & FMI_EXPAND))
{
// Nope, do it now.
// DebugMsg(DM_TRACE, "t.fm_ii: Removing marker item.");
FileMenuHeader_DeleteMarkerItem(pFMH);
}
// Add the new item.
pFMI = (PFILEMENUITEM)LocalAlloc(LPTR, SIZEOF(FILEMENUITEM));
if (pFMI)
{
if (psz && (psz != (LPTSTR) FMAI_SEPARATOR))
{
pFMI->psz = (LPVOID)LocalAlloc(LPTR, (lstrlen(psz)+1) * SIZEOF(TCHAR));
if (pFMI->psz)
{
lstrcpy(pFMI->psz, psz);
}
else
{
DebugMsg(DM_ERROR, TEXT("s.dm_ai: Unable to allocate menu item text."));
}
}
pFMI->pFMH = pFMH;
pFMI->iImage = iImage;
pFMI->cyItem = cyItem;
// It's going on the end.
iItem = DPA_GetPtrCount(pFMH->hdpaFMI);
DPA_SetPtr(pFMH->hdpaFMI, iItem, pFMI);
//
if (psz == (LPTSTR) FMAI_SEPARATOR)
{
pFMI->Flags = FMI_SEPARATOR;
InsertMenu(hmenu, iItem, MF_BYPOSITION|MF_OWNERDRAW|MF_DISABLED|MF_SEPARATOR, id, (LPTSTR)pFMI);
return TRUE;
}
else if (hmenuSub)
{
MENUITEMINFO mii;
pFMI->Flags = FMI_FOLDER;
InsertMenu(pFMH->hmenu, iItem, MF_BYPOSITION|MF_OWNERDRAW|MF_POPUP, (UINT)hmenuSub, (LPTSTR)pFMI);
// Set it's ID.
mii.cbSize = SIZEOF(mii);
mii.fMask = MIIM_ID;
// mii.wID = pFMH->idItems;
mii.wID = id;
SetMenuItemInfo(pFMH->hmenu, iItem, TRUE, &mii);
return TRUE;
}
else
{
InsertMenu(hmenu, iItem, MF_BYPOSITION|MF_OWNERDRAW, id, (LPTSTR)pFMI);
return TRUE;
}
}
DebugMsg(DM_ERROR, TEXT("t.fmh_ii: Can't create new item."));
}
else
{
DebugMsg(DM_ERROR, TEXT("t.fm_ii: Not a valid file menu."));
}
return FALSE;
}
//----------------------------------------------------------------------------
BOOL WINAPI 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 WINAPI _FindItemByCmd(PFILEMENUHEADER pFMH, UINT id, int *piPos)
{
if (pFMH)
{
int cItems, i;
cItems = DPA_GetPtrCount(pFMH->hdpaFMI);
for (i = 0; i < cItems; i++)
{
PFILEMENUITEM pFMI = DPA_GetPtr(pFMH->hdpaFMI, 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 WINAPI _FindMenuOrItemByCmd(PFILEMENUHEADER pFMH, UINT id, int *piPos)
{
if (pFMH)
{
int cItems, i;
cItems = DPA_GetPtrCount(pFMH->hdpaFMI);
for (i = 0; i < cItems; i++)
{
PFILEMENUITEM pFMI = DPA_GetPtr(pFMH->hdpaFMI, 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.
BOOL WINAPI FileMenu_DeleteItemByCmd(HMENU hmenu, UINT id)
{
PFILEMENUHEADER pFMH;
// DebugMsg(DM_TRACE, "s.fm_dibc:...");
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.
// DebugMsg(DM_TRACE, "s.fm_dibc: Deleting %d", i);
DeleteMenu(pFMH->hmenu, i, MF_BYPOSITION);
if (pFMI->pidl)
ILFree(pFMI->pidl);
if (pFMI->psz)
LocalFree((HLOCAL)pFMI->psz);
LocalFree((HLOCAL)pFMI);
DPA_DeletePtr(pFMH->hdpaFMI, i);
return TRUE;
}
}
return FALSE;
}
//----------------------------------------------------------------------------
BOOL WINAPI FileMenu_DeleteItemByIndex(HMENU hmenu, UINT iItem)
{
PFILEMENUHEADER pFMH;
// DebugMsg(DM_TRACE, "s.fm_dibi:...");
if (!IsMenu(hmenu))
return FALSE;
pFMH = FileMenu_GetHeader(hmenu);
if (pFMH)
{
PFILEMENUITEM pFMI = DPA_GetPtr(pFMH->hdpaFMI, iItem);
if (pFMI)
{
// Delete the item itself.
// DebugMsg(DM_TRACE, "s.fm_dibc: Deleting %d", iItem);
DeleteMenu(pFMH->hmenu, iItem, MF_BYPOSITION);
if (pFMI->pidl)
ILFree(pFMI->pidl);
if (pFMI->psz)
LocalFree((HLOCAL)pFMI->psz);
LocalFree((HLOCAL)pFMI);
DPA_DeletePtr(pFMH->hdpaFMI, 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; c<cMax; c++)
{
// Is this item a submenu?
HMENU hmenuSub = GetSubMenu(hmenu, c);
if (hmenuSub && GetMenuItemInfo(hmenuSub, 0, TRUE, &mii))
{
if (mii.wID == id)
{
// Found it!
if (pi)
*pi = c;
return hmenuSub;
}
}
}
return NULL;
}
//----------------------------------------------------------------------------
BOOL WINAPI FileMenu_DeleteMenuItemByFirstID(HMENU hmenu, UINT id)
{
int i;
PFILEMENUITEM pFMI;
PFILEMENUHEADER pFMH;
HMENU hmenuSub;
// DebugMsg(DM_TRACE, "s.fm_dsmbfi:...");
if (!IsMenu(hmenu))
return FALSE;
if (!id)
return FALSE;
pFMH = FileMenu_GetHeader(hmenu);
if (pFMH)
{
hmenuSub = _FindMenuItemByFirstID(hmenu, id, &i);
if (hmenuSub && i)
{
// Delete the submenu.
FileMenu_DeleteAllItems(hmenuSub);
// Delete the item itself.
pFMI = FileMenu_GetItemData(hmenu, i);
// DebugMsg(DM_TRACE, "s.fm_dibc: Deleting %d", i);
DeleteMenu(pFMH->hmenu, i, MF_BYPOSITION);
if (pFMI->pidl)
ILFree(pFMI->pidl);
if (pFMI->psz)
LocalFree((HLOCAL)pFMI->psz);
LocalFree((HLOCAL)pFMI);
DPA_DeletePtr(pFMH->hdpaFMI, i);
return TRUE;
}
}
return FALSE;
}
//----------------------------------------------------------------------------
BOOL WINAPI FileMenu_DeleteSeparator(HMENU hmenu)
{
int i;
PFILEMENUHEADER pFMH;
// DebugMsg(DM_TRACE, "s.fm_ds:...");
if (!IsMenu(hmenu))
return FALSE;
pFMH = FileMenu_GetHeader(hmenu);
if (pFMH)
{
PFILEMENUITEM pFMI = _FindItemByCmd(pFMH, 0, &i);
if (pFMI)
{
// Yep.
// DebugMsg(DM_TRACE, "s.fm_ds: Deleting %d", i);
DeleteMenu(pFMH->hmenu, i, MF_BYPOSITION);
if (pFMI->pidl)
ILFree(pFMI->pidl);
// Seps. shouldn't have any text.
Assert(!pFMI->psz);
//
LocalFree((HLOCAL)pFMI);
DPA_DeletePtr(pFMH->hdpaFMI, i);
return TRUE;
}
}
return FALSE;
}
//----------------------------------------------------------------------------
BOOL WINAPI FileMenu_EnableItemByCmd(HMENU hmenu, UINT id, BOOL fEnable)
{
PFILEMENUHEADER pFMH;
// DebugMsg(DM_TRACE, "s.fm_seebc:...");
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
{
DebugMsg(DM_ERROR, TEXT("s.fm_siebc: Menu is not a filemenu."));
}
return FALSE;
}