//---------------------------------------------------------------------------
// 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;
}