#include "stdafx.h"
#pragma hdrstop

#include <oleacc.h>     // MSAAMENUINFO stuff
#include <runtask.h>
#include "datautil.h"
#include "idlcomm.h"
#include "stgutil.h"
#include <winnls.h>
#include "filetbl.h"
#include "cdburn.h"
#include "mtpt.h"

#ifndef CMF_DVFILE
#define CMF_DVFILE       0x00010000     // "File" pulldown
#endif

class CSendToMenu : public IContextMenu3, IShellExtInit, IOleWindow
{
public:
    // IUnknown
    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj);
    STDMETHOD_(ULONG,AddRef)(void);
    STDMETHOD_(ULONG,Release)(void);
    
    // IContextMenu
    STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
    STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici);
    STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax);
    
    // IContextMenu2
    STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam);

    // IContextMenu3
    STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *lResult);

    // IShellExtInit
    STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
    
    // IOleWindow
    STDMETHOD(GetWindow)(HWND *phwnd);
    STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode) {return E_NOTIMPL;};

private:    
    CSendToMenu();
    ~CSendToMenu();

    LONG    _cRef;
    HMENU   _hmenu;
    UINT    _idCmdFirst;
    BOOL    _bFirstTime;
    HWND    _hwnd;
    IDataObject *_pdtobj;
    LPITEMIDLIST _pidlLast;

    DWORD _GetKeyState(void);
    HRESULT _DoDragDrop(HWND hwndParent, IDropTarget *pdrop);
    BOOL _CanDrop(IShellFolder *psf, LPCITEMIDLIST pidl);
    HRESULT _MenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl);
    HRESULT _RemovableDrivesMenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl);
    static HRESULT CALLBACK s_MenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl);
    static HRESULT CALLBACK s_RemovableDrivesMenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl);
    
    friend HRESULT CSendToMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut);
};

CSendToMenu::CSendToMenu() : _cRef(1) 
{
    DllAddRef();
}

CSendToMenu::~CSendToMenu()
{
    if (_hmenu)
        FileMenu_DeleteAllItems(_hmenu);
    
    if (_pdtobj)
        _pdtobj->Release();

    ILFree(_pidlLast);
    DllRelease();
}

HRESULT CSendToMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut)
{
    HRESULT hr = E_OUTOFMEMORY;
    CSendToMenu *pstm = new CSendToMenu();
    if (pstm) 
    {
        hr = pstm->QueryInterface(riid, ppvOut);
        pstm->Release();
    }
    return hr;
}

HRESULT CSendToMenu::QueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CSendToMenu, IShellExtInit),                     // IID_IShellExtInit
        QITABENT(CSendToMenu, IOleWindow),                        // IID_IOleWindow
        QITABENT(CSendToMenu, IContextMenu3),                     // IID_IContextMenu3
        QITABENTMULTI(CSendToMenu, IContextMenu2, IContextMenu3), // IID_IContextMenu2
        QITABENTMULTI(CSendToMenu, IContextMenu, IContextMenu3),  // IID_IContextMenu
        { 0 }
    };
    return QISearch(this, qit, riid, ppvObj);
}

ULONG CSendToMenu::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

ULONG CSendToMenu::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;
    delete this;
    return 0;
}

HRESULT CSendToMenu::GetWindow(HWND *phwnd)
{
    HRESULT hr = E_INVALIDARG;

    if (phwnd)
    {
        *phwnd = _hwnd;
        hr = S_OK;
    }

    return hr;
}

HRESULT CSendToMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
    // if they want the default menu only (CMF_DEFAULTONLY) OR 
    // this is being called for a shortcut (CMF_VERBSONLY)
    // we don't want to be on the context menu
    
    if (uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY))
        return S_OK;
    
    UINT idMax = idCmdFirst;
    
    _hmenu = CreatePopupMenu();
    if (_hmenu)
    {
        TCHAR szSendLinkTo[80];
        TCHAR szSendPageTo[80];
        MENUITEMINFO mii;
        
        // add a dummy item so we are identified at WM_INITMENUPOPUP time
        
        LoadString(g_hinst, IDS_SENDLINKTO, szSendLinkTo, ARRAYSIZE(szSendLinkTo));
        LoadString(g_hinst, IDS_SENDPAGETO, szSendPageTo, ARRAYSIZE(szSendPageTo));
        
        mii.cbSize = sizeof(MENUITEMINFO);
        mii.fMask = MIIM_ID | MIIM_TYPE;
        mii.fType = MFT_STRING;
        mii.dwTypeData = szSendLinkTo;
        mii.wID = idCmdFirst + 1;
        
        if (InsertMenuItem(_hmenu, 0, TRUE, &mii))
        {
            _idCmdFirst = idCmdFirst + 1;   // remember this for later
            
            mii.fType = MFT_STRING;
            mii.dwTypeData = szSendLinkTo;
            mii.wID = idCmdFirst;
            mii.fState = MF_DISABLED | MF_GRAYED;
            mii.fMask = MIIM_TYPE | MIIM_SUBMENU | MIIM_ID;
            mii.hSubMenu = _hmenu;
            
            if (InsertMenuItem(hmenu, indexMenu, TRUE, &mii))
            {
                idMax += 0x40;      // reserve space for this many items
                _bFirstTime = TRUE; // fill this at WM_INITMENUPOPUP time
            }
            else
            {
                _hmenu = NULL;
            }
        }
    }
    _hmenu = NULL;
    return ResultFromShort(idMax - idCmdFirst);
}

DWORD CSendToMenu::_GetKeyState(void)
{
    DWORD grfKeyState = MK_LBUTTON; // default

    if (GetAsyncKeyState(VK_CONTROL) < 0)
        grfKeyState |= MK_CONTROL;

    if (GetAsyncKeyState(VK_SHIFT) < 0)
        grfKeyState |= MK_SHIFT;

    if (GetAsyncKeyState(VK_MENU) < 0)
        grfKeyState |= MK_ALT;          // menu's don't really allow this
    
    return grfKeyState;
}

HRESULT CSendToMenu::_DoDragDrop(HWND hwndParent, IDropTarget *pdrop)
{

    DWORD grfKeyState = _GetKeyState();
    if (grfKeyState == MK_LBUTTON)
    {
        // no modifieres, change default to COPY
        grfKeyState = MK_LBUTTON | MK_CONTROL;
        DataObj_SetDWORD(_pdtobj, g_cfPreferredDropEffect, DROPEFFECT_COPY);
    }

    _hwnd = hwndParent;
    IUnknown_SetSite(pdrop, SAFECAST(this, IOleWindow *));  // Let them have access to our HWND.
    HRESULT hr = SHSimulateDrop(pdrop, _pdtobj, grfKeyState, NULL, NULL);
    IUnknown_SetSite(pdrop, NULL);

    if (hr == S_FALSE)
    {
        ShellMessageBox(g_hinst, hwndParent, 
                        MAKEINTRESOURCE(IDS_SENDTO_ERRORMSG),
                        MAKEINTRESOURCE(IDS_CABINET), 
                        MB_OK|MB_ICONEXCLAMATION);
    }                    
    return hr;
}

HRESULT CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
    HRESULT hr;
    
    if (_pdtobj && _pidlLast)
    {
        IDropTarget *pdrop;
        hr = SHGetUIObjectFromFullPIDL(_pidlLast, pici->hwnd, IID_PPV_ARG(IDropTarget, &pdrop));
        if (SUCCEEDED(hr))
        {
            hr = _DoDragDrop(pici->hwnd, pdrop);
            pdrop->Release();
        }
    }
    else
        hr = E_INVALIDARG;
    return hr;
}

HRESULT CSendToMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax)
{
    return E_NOTIMPL;
}

HRESULT CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return HandleMenuMsg2(uMsg, wParam, lParam, NULL);
}

BOOL CSendToMenu::_CanDrop(IShellFolder *psf, LPCITEMIDLIST pidl)
{
    BOOL fCanDrop = FALSE;
    IDropTarget *pdt;
    if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidl, IID_X_PPV_ARG(IDropTarget, 0, &pdt))))
    {
        POINTL pt = {0};
        DWORD dwEffect = DROPEFFECT_COPY;

        // Do a drag enter, if they return no drop effect then we can't drop
        if (SUCCEEDED(pdt->DragEnter(_pdtobj, _GetKeyState(), pt, &dwEffect)))
        {
            if (dwEffect != DROPEFFECT_NONE)
                fCanDrop = TRUE;  // show it!
            pdt->DragLeave();        
        }
        pdt->Release();
    }
    return fCanDrop;
}

HRESULT CSendToMenu::_MenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl)
{
    HRESULT hr = S_OK;
    switch (fmm)
    {
    case FMM_ADD:
        hr = _CanDrop(psf, pidl) ? S_OK : S_FALSE;
        break;

    case FMM_SETLASTPIDL:
        Pidl_Set(&_pidlLast, pidl);
        break;

    default:
        hr = E_FAIL;
    }
    return hr;
}

HRESULT CSendToMenu::_RemovableDrivesMenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl)
{
    HRESULT hr = S_OK;
    switch (fmm)
    {
    case FMM_ADD:
        hr = S_FALSE; // assume we wont show it
        if (_CanDrop(psf, pidl))
        {
            // now we know it's a removable drive.  in general we dont want to display cd-rom drives.
            // we know this is the my computer folder so just get the parsing name, we need it for GetDriveType.
            WCHAR szDrive[MAX_PATH];
            if (SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szDrive, ARRAYSIZE(szDrive))))
            {
                CMountPoint *pmtpt = CMountPoint::GetMountPoint(szDrive);
                if (pmtpt)
                {
                    if (pmtpt->IsCDROM())
                    {
                        // of all cdroms, only the enabled burning folder is okay to put on sendto
                        WCHAR szRecorder[4];
                        if (SUCCEEDED(CDBurn_GetRecorderDriveLetter(szRecorder, ARRAYSIZE(szRecorder))) &&
                            (lstrcmpiW(szRecorder, szDrive) == 0))
                        {
                            hr = S_OK;
                        }
                    }
                    else if (pmtpt->IsFloppy() || pmtpt->IsStrictRemovable() || pmtpt->IsRemovableDevice())
                    {
                        // also put on removable devices.
                        hr = S_OK;
                    }
                    pmtpt->Release();
                }
                else
                {
                    // if this failed it could be a memory condition but its more likely to be that the
                    // parsing name doesnt map to a mountpoint.  in that case fall back to SFGAO_REMOVABLE
                    // to pick up portable audio devices.  if this was because of lowmem its no biggie.
                    if (SHGetAttributes(psf, pidl, SFGAO_REMOVABLE))
                    {
                        hr = S_OK;
                    }
                }
            }
        }
        break;

    case FMM_SETLASTPIDL:
        Pidl_Set(&_pidlLast, pidl);
        break;

    default:
        hr = E_FAIL;
    }
    return hr;
}

HRESULT CSendToMenu::s_MenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl)
{
    return ((CSendToMenu*)lParam)->_MenuCallback(fmm, psf, pidl);
}

HRESULT CSendToMenu::s_RemovableDrivesMenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl)
{
    return ((CSendToMenu*)lParam)->_RemovableDrivesMenuCallback(fmm, psf, pidl);
}

HRESULT GetFolder(int csidl, IShellFolder **ppsf)
{
    LPITEMIDLIST pidl;
    HRESULT hr = SHGetFolderLocation(NULL, csidl, NULL, 0, &pidl);
    if (SUCCEEDED(hr))
    {
        hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidl, ppsf));
        ILFree(pidl);
    }
    return hr;
}

HRESULT CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
{
    HRESULT hr = S_OK;
    LRESULT lRes = 0;

    switch (uMsg)
    {
    case WM_INITMENUPOPUP:
        if (_bFirstTime)
        {
            _bFirstTime = FALSE;
            
            //In case of Shell_MergeMenus
            if (_hmenu == NULL)
                _hmenu = (HMENU)wParam;

            // delete the dummy entry
            DeleteMenu(_hmenu, 0, MF_BYPOSITION);

            FMCOMPOSE fmc = {0};
            if (SUCCEEDED(GetFolder(CSIDL_SENDTO, &fmc.psf)))
            {
                fmc.idCmd = _idCmdFirst;
                fmc.grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
                fmc.pfnCallback = s_MenuCallback;
                fmc.lParam = (LPARAM)this;              // not reference counted
                                    
                FileMenu_Compose(_hmenu, FMCM_REPLACE, &fmc);                    
                fmc.psf->Release();
            }
            if (SUCCEEDED(GetFolder(CSIDL_DRIVES, &fmc.psf)))
            {
                fmc.dwMask = FMC_NOEXPAND;
                fmc.idCmd = _idCmdFirst;
                fmc.grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
                fmc.pfnCallback = s_RemovableDrivesMenuCallback;
                fmc.lParam = (LPARAM)this;              // not reference counted
                                    
                FileMenu_Compose(_hmenu, FMCM_APPEND, &fmc);                    
                fmc.psf->Release();
            }
        }
        else if (_hmenu != (HMENU)wParam)
        {
            // secondary cascade menu
            FileMenu_InitMenuPopup((HMENU)wParam);
        }
        break;
        
    case WM_DRAWITEM:
        {
            DRAWITEMSTRUCT *pdi = (DRAWITEMSTRUCT *)lParam;
            
            if (pdi->CtlType == ODT_MENU && pdi->itemID == _idCmdFirst) 
            {
                lRes = FileMenu_DrawItem(NULL, pdi);
            }
        }
        break;
        
    case WM_MEASUREITEM:
        {
            MEASUREITEMSTRUCT *pmi = (MEASUREITEMSTRUCT *)lParam;
            
            if (pmi->CtlType == ODT_MENU && pmi->itemID == _idCmdFirst) 
            {
                lRes = FileMenu_MeasureItem(NULL, pmi);
            }
        }
        break;

    case WM_MENUCHAR:
        {
            TCHAR ch = (TCHAR)LOWORD(wParam);
            HMENU hmenu = (HMENU)lParam;
            lRes = FileMenu_HandleMenuChar(hmenu, ch);
        }
        break;

    default:
        hr = E_NOTIMPL;
        break;
    }

    if (plResult)
        *plResult = lRes;

    return hr;
}

HRESULT CSendToMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
{
    IUnknown_Set((IUnknown **)&_pdtobj, pdtobj);
    return S_OK;
}

#define TARGETMENU
#ifdef TARGETMENU

class CTargetMenu : public IShellExtInit, public IContextMenu3
{
    // IUnknown
    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj);
    STDMETHOD_(ULONG,AddRef)(void);
    STDMETHOD_(ULONG,Release)(void);
    
    // IContextMenu
    STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
    STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici);
    STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax);
    
    // IContextMenu2
    STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam);

    // IContextMenu3
    STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *lResult);

    // IShellExtInit
    STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
    
    HRESULT GetTargetMenu();
    
    ~CTargetMenu();
    CTargetMenu();
    
    LONG _cRef;
    HMENU _hmenu;
    UINT _idCmdFirst;
    BOOL _bFirstTime;
    
    IDataObject *_pdtobj;
    LPITEMIDLIST _pidlTarget;
    IContextMenu *_pcmTarget;
    
    friend HRESULT CTargetMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut);
};

CTargetMenu::CTargetMenu() : _cRef(1) 
{
}

CTargetMenu::~CTargetMenu()
{
    ILFree(_pidlTarget);
    
    if (_pcmTarget)
        _pcmTarget->Release();

    if (_pdtobj)
        _pdtobj->Release();
}

STDMETHODIMP CTargetMenu::QueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CTargetMenu, IShellExtInit),                     // IID_IShellExtInit
        QITABENT(CTargetMenu, IContextMenu3),                     // IID_IContextMenu3
        QITABENTMULTI(CTargetMenu, IContextMenu2, IContextMenu3), // IID_IContextMenu2
        QITABENTMULTI(CTargetMenu, IContextMenu, IContextMenu3),  // IID_IContextMenu
        { 0 }
    };
    return QISearch(this, qit, riid, ppvObj);
}

STDMETHODIMP_(ULONG) CTargetMenu::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CTargetMenu::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;
    delete this;
    return 0;
}

STDMETHODIMP CTargetMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
{
    IUnknown_Set((IUnknown **)&_pdtobj, pdtobj);
    return S_OK;
}

HRESULT CTargetMenu::GetTargetMenu()
{
    HRESULT hr = E_FAIL;
    STGMEDIUM medium;
    LPIDA pida = DataObj_GetHIDA(_pdtobj, &medium);
    if (pida)
    {
        IShellFolder *psf;
        hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, IDA_GetIDListPtr(pida, -1), &psf));
        if (SUCCEEDED(hr))
        {
            IShellLink *psl;
            LPCITEMIDLIST pidl = IDA_GetIDListPtr(pida, 0);
            hr = psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidl, IID_X_PPV_ARG(IShellLink, NULL, &psl));
            if (SUCCEEDED(hr))
            {
                ASSERT(NULL == _pidlTarget);
                hr = psl->GetIDList(&_pidlTarget);
                if (SUCCEEDED(hr) && _pidlTarget) 
                {
                    hr = SHGetUIObjectFromFullPIDL(_pidlTarget, NULL, IID_PPV_ARG(IContextMenu, &_pcmTarget));
                }
                else
                    hr = E_FAIL;
                psl->Release();
            }
            psf->Release();
        }
        HIDA_ReleaseStgMedium(pida, &medium);
    }
    return hr;
}

#define IDS_CONTENTSMENU    3
#define IDS_TARGETMENU      4

#define IDC_OPENCONTAINER   1
#define IDC_TARGET_LAST     1
#define NUM_TARGET_CMDS     0x40

STDMETHODIMP CTargetMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
    if (uFlags & CMF_DEFAULTONLY)
        return S_OK;
    
    UINT idMax = idCmdFirst;
    
    _hmenu = CreatePopupMenu();
    if (_hmenu)
    {
        TCHAR szString[80];
        MENUITEMINFO mii;
        
        // Add an open container menu item...
        lstrcpyn(szString, TEXT("Open Container"), ARRAYSIZE(szString));
        
        mii.cbSize = sizeof(MENUITEMINFO);
        mii.fMask = MIIM_ID | MIIM_TYPE;
        mii.fType = MFT_STRING;
        mii.dwTypeData = szString;
        mii.wID = idCmdFirst + IDC_OPENCONTAINER;
        
        if (InsertMenuItem(_hmenu, 0, TRUE, &mii))
        {
            _idCmdFirst = idCmdFirst;
            
            SetMenuDefaultItem(_hmenu, 0, TRUE);
            InsertMenu(hmenu, 1, MF_BYPOSITION | MF_SEPARATOR, (UINT)-1, NULL);
            
            // Insert our context menu....
            lstrcpyn(szString, TEXT("Target"), ARRAYSIZE(szString));
            
            mii.cbSize = sizeof(MENUITEMINFO);
            mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
            mii.fType = MFT_STRING;
            mii.dwTypeData = szString;
            mii.wID = idCmdFirst;
            mii.fState = MF_DISABLED|MF_GRAYED;
            mii.fMask = MIIM_TYPE | MIIM_SUBMENU;
            mii.hSubMenu = _hmenu;
            
            if (InsertMenuItem(hmenu, indexMenu, TRUE, &mii))
            {
                idMax += NUM_TARGET_CMDS;    // reserve space for this many items
                _bFirstTime = TRUE; // fill this at WM_INITMENUPOPUP time
            }
            else
            {
                DestroyMenu(_hmenu);
                _hmenu = NULL;
            }
        }
    }
    return ResultFromShort(idMax - idCmdFirst);
}

STDMETHODIMP CTargetMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) 
{
    UINT idCmd = LOWORD(lpici->lpVerb);
    
    switch (idCmd) 
    {
    case IDC_OPENCONTAINER:
        SHOpenFolderAndSelectItems(_pidlTarget, 0, NULL, 0);
        break;
        
    default:
        CMINVOKECOMMANDINFO ici = {
            sizeof(ici),
                lpici->fMask,
                lpici->hwnd,
                (LPCSTR)MAKEINTRESOURCE(idCmd - IDC_OPENCONTAINER),
                lpici->lpParameters, 
                lpici->lpDirectory,
                lpici->nShow,
        };
        
        return _pcmTarget->InvokeCommand(&ici);
    }
    return S_OK;
}

HRESULT CTargetMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax)
{
    return E_NOTIMPL;
}

STDMETHODIMP CTargetMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return HandleMenuMsg2(uMsg, wParam, lParam, NULL);
}


STDMETHODIMP CTargetMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
{
    HRESULT hr = S_OK;
    LRESULT lResult = 0;

    switch (uMsg)
    {
    case WM_INITMENUPOPUP:
        if (_hmenu == (HMENU)wParam)
        {
            if (_bFirstTime)
            {
                _bFirstTime = FALSE;
                
                if (SUCCEEDED(GetTargetMenu()))
                {
                    _pcmTarget->QueryContextMenu(_hmenu, IDC_TARGET_LAST, 
                        _idCmdFirst + IDC_TARGET_LAST, 
                        _idCmdFirst - IDC_TARGET_LAST + NUM_TARGET_CMDS, CMF_NOVERBS);
                }
            }
            break;
        }
        
        // fall through... to pass on sub menu WM_INITMENUPOPUPs
        
    case WM_DRAWITEM:
    case WM_MEASUREITEM:
        hr = SHForwardContextMenuMsg(_pcmTarget, uMsg, wParam, lParam, &lResult, TRUE);
        break;

    default:
        hr = E_FAIL;
        break;
    }

    if (plResult)
        *plResult = lResult;

    return hr;
}

HRESULT CTargetMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut)
{
    HRESULT hr = E_OUTOFMEMORY;
    CTargetMenu *ptm = new CTargetMenu();
    if (ptm) 
    {
        hr = ptm->QueryInterface(riid, ppvOut);
        ptm->Release();
    }
    return hr;
}

#endif  // TARGETMENU


#ifdef CONTENT

#define IDC_ITEMFIRST (IDC_PRESSMOD + 10)
#define MENU_TIMEOUT (1000)

#define DPA_LAST    0x7fffffff

class CHIDA
{
public:
    CHIDA(HGLOBAL hIDA, IUnknown *punk)
    {
        _punk = punk;
        _hIDA = hIDA;
        _pIDA = (LPIDA)GlobalLock(hIDA);
    }
    ~CHIDA() 
    {
        GlobalUnlock(_hObj);
        
        if (_punk) 
            _punk->Release();
        else
            GlobalFree(_hIDA);
        
    }
    
    LPCITEMIDLIST operator [](UINT nIndex)
    {
        if (nIndex > _pIDA->cidl)
            return(NULL);
        
        return (LPCITEMIDLIST)(((BYTE *)_pIDA) + _pIDA->aoffset[nIndex]);
    }
    
private:
    HGLOBAL _hIDA;
    LPIDA _pIDA;
    IUnknown *_punk;
};


class CVoidArray
{
public:
    CVoidArray() : _dpa(NULL), _dsa(NULL) {}
    ~CVoidArray()
    {
        if (_dpa)
        {
            DPA_Destroy(_dpa);
        }
        if (_dsa)
        {
            DSA_Destroy(_dsa);
        }
    }
    
    void *operator[](int i);
    
    BOOL Init(UINT uSize, UINT uJump);
    
    BOOL Add(void *pv);
    
    void Sort(PFNDPACOMPARE pfnCompare, LPARAM lParam)
    {
        _pfnCompare = pfnCompare;
        _lParam = lParam;
        
        DPA_Sort(_dpa, ArrayCompare, (LPARAM)this);
    }
    
private:
    static int CALLBACK ArrayCompare(void *pv1, void *pv2, LPARAM lParam);
    
    HDPA _dpa;
    HDSA _dsa;
    
    PFNDPACOMPARE _pfnCompare;
    LPARAM void *;
};


_lParam CVoidArray::operator[](int i)
{
    if (!_dpa || i>=DPA_GetPtrCount(_dpa))
    {
        return(NULL);
    }
    
    return(DSA_GetItemPtr(_dsa, (int)DPA_GetPtr(_dpa, i)));
}


BOOL CVoidArray::Init(UINT uSize, UINT uJump)
{
    _dpa = DPA_Create(uJump);
    _dsa = DSA_Create(uSize, uJump);
    return(_dpa && _dsa);
}


BOOL CVoidArray::Add(void *pv)
{
    int iItem = DSA_InsertItem(_dsa, DPA_LAST, pv);
    if (iItem < 0)
    {
        return(FALSE);
    }
    
    if (DPA_InsertPtr(_dpa, DPA_LAST, (void *)iItem) < 0)
    {
        DSA_DeleteItem(_dsa, iItem);
        return(FALSE);
    }
    
    return(TRUE);
}


int CALLBACK CVoidArray::ArrayCompare(void *pv1, void *pv2, LPARAM lParam)
{
    CVoidArray *pThis = (CVoidArray *)lParam;
    
    return(pThis->_pfnCompare(DSA_GetItemPtr(pThis->_dsa, (int)pv1),
        DSA_GetItemPtr(pThis->_dsa, (int)pv2), pThis->_lParam));
}


class CContentItemData
{
public:
    CContentItemData() : _dwDummy(0) {Empty();}
    ~CContentItemData() {Free();}
    
    void Free();
    void Empty() {_pidl=NULL; _hbm=NULL;}
    
    // Here to work around a Tray menu bug
    DWORD _dwDummy;
    LPITEMIDLIST _pidl;
    HBITMAP _hbm;
};


void CContentItemData::Free()
{
    if (_pidl) ILFree(_pidl);
    if (_hbm) DeleteObject(_hbm);
    Empty();
}


class CContentItemDataArray : public CVoidArray
{
public:
    CContentItemDataArray(LPCITEMIDLIST pidlFolder) : _pidlFolder(pidlFolder) {}
    ~CContentItemDataArray();
    
    CContentItemData * operator[](int i) {return((CContentItemData*)(*(CVoidArray*)this)[i]);}
    
    HRESULT Init();
    BOOL Add(CContentItemData *pv) {return(CVoidArray::Add((void *)pv));}
    
private:
    static int CALLBACK DefaultSort(void *pv1, void *pv2, LPARAM lParam);
    
    HRESULT GetShellFolder(IShellFolder **ppsf);
    BOOL IsLocal();
    
    LPCITEMIDLIST CContentItemDataArray;
};


CContentItemDataArray::~CContentItemDataArray()
{
    for (int i=0;; ++i)
    {
        CContentItemData *pID = (*this)[i];
        if (!pID)
            break;
        pID->Free();
    }
}


int CALLBACK CContentItemDataArray::DefaultSort(void *pv1, void *pv2, LPARAM lParam)
{
    IShellFolder *psfFolder = (IShellFolder *)lParam;
    CContentItemData *pID1 = (CContentItemData *)pv1;
    CContentItemData *pID2 = (CContentItemData *)pv2;
    
    HRESULT hRes = psfFolder->CompareIDs(0, pID1->_pidl, pID2->_pidl);
    if (FAILED(hRes))
    {
        return(0);
    }
    
    return((short)ShortFromResult(hRes));
}

HRESULT CContentItemDataArray::GetShellFolder(IShellFolder **ppsf)
{
    IShellFolder *psfDesktop;
    HRESULT hRes = CoCreateInstance(CLSID_ShellDesktop, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellFolder, &psfDesktop));
    if (FAILED(hRes))
    {
        return hRes;
    }
    CEnsureRelease erDesktop(psfDesktop);
    
    return psfDesktop->BindToObject(_pidlFolder, NULL, IID_PPV_ARG(IShellFolder, ppsf));
}

BOOL CContentItemDataArray::IsLocal()
{
    TCHAR szPath[MAX_PATH];
    
    if (!SHGetPathFromIDList(_pidlFolder, szPath))
    {
        return(FALSE);
    }
    
    CharUpper(szPath);
    return(DriveType(szPath[0]-'A') == DRIVE_FIXED);
}


HRESULT CContentItemDataArray::Init()
{
    if (!IsLocal() && !(GetKeyState(VK_SHIFT)&0x8000))
    {
        return(S_FALSE);
    }
    
    if (!CVoidArray::Init(sizeof(CContentItemData), 16))
    {
        return(E_OUTOFMEMORY);
    }
    
    IShellFolder *psfFolder;
    HRESULT hRes = GetShellFolder(&psfFolder);
    if (FAILED(hRes))
    {
        return(hRes);
    }
    CEnsureRelease erFolder(psfFolder);
    
    IEnumIDList *penumFolder;
    hRes = psfFolder->EnumObjects(NULL,
        SHCONTF_FOLDERS|SHCONTF_NONFOLDERS|SHCONTF_INCLUDEHIDDEN, &penumFolder);
    if (S_OK != hRes)
    {
        return FAILED(hRes) ? hRes : E_FAIL; //in case of S_FALSE enumerator is NULL so nothing will be in the menu, just return failure
    }
    CEnsureRelease erEnumFolder(penumFolder);
    
    ULONG cNum;
    
    DWORD dwStart = 0;
    hRes = S_OK;
    
    for (;;)
    {
        CContentItemData cID;
        if (penumFolder->Next(1, &cID._pidl, &cNum)!=S_OK || cNum!=1)
        {
            // Just in case
            cID.Empty();
            break;
        }
        
        if (!dwStart)
        {
            dwStart = GetTickCount();
        }
        else if (!(GetAsyncKeyState(VK_SHIFT)&0x8000)
            && GetTickCount()-dwStart>MENU_TIMEOUT)
        {
            // Only go for 2 seconds after the first Next call
            hRes = S_FALSE;
            break;
        }
        
        CMenuDraw mdItem(_pidlFolder, cID._pidl);
        cID._hbm = mdItem.CreateBitmap(TRUE);
        if (!cID._hbm)
        {
            continue;
        }
        
        if (!Add(&cID))
        {
            break;
        }
        
        // Like a Detach(); Make sure we do not free stuff
        cID.Empty();
    }
    
    Sort(DefaultSort, (LPARAM)psfFolder);
    
    return(hRes);
}


class CContentItemInfo : public tagMENUITEMINFOA
{
public:
    CContentItemInfo(UINT fMsk) {fMask=fMsk; cbSize=sizeof(MENUITEMINFO);}
    ~CContentItemInfo() {}
    
    BOOL GetMenuItemInfo(HMENU hm, int nID, BOOL bByPos)
    {
        return(::GetMenuItemInfo(hm, nID, bByPos, this));
    }
    
    CContentItemData *GetItemData() {return((CContentItemData*)dwItemData);}
    void SetItemData(CContentItemData *pd) {dwItemData=(DWORD)pd; fMask|=MIIM_DATA;}
    
    HBITMAP GetBitmap() {return(fType&MFT_BITMAP ? dwTypeData : NULL);}
    void SetBitmap(HBITMAP hb) {dwTypeData=(LPSTR)hb; fType|=MFT_BITMAP;}
};

#define CXIMAGEGAP 6
#define CYIMAGEGAP 4

class CWindowDC
{
public:
    CWindowDC(HWND hWnd) : _hWnd(hWnd) {_hDC=GetDC(hWnd);}
    ~CWindowDC() {ReleaseDC(_hWnd, _hDC);}
    
    operator HDC() {return(_hDC);}
    
private:
    HDC _hDC;
    HWND _hWnd;
};


class CDCTemp
{
public:
    CDCTemp(HDC hDC) : _hDC(hDC) {}
    ~CDCTemp() {if (_hDC) DeleteDC(_hDC);}
    
    operator HDC() {return(_hDC);}
    
private:
    HDC _hDC;
};


class CRefMenuFont
{
public:
    CRefMenuFont(CMenuDraw *pmd) {_pmd = pmd->InitMenuFont() ? pmd : NULL;}
    ~CRefMenuFont() {if (_pmd) _pmd->ReleaseMenuFont();}
    
    operator BOOL() {return(_pmd != NULL);}
    
private:
    CMenuDraw *_pmd;
};


BOOL CMenuDraw::InitMenuFont()
{
    if (_cRefFont.GetRef())
    {
        _cRefFont.AddRef();
        return(TRUE);
    }
    
    NONCLIENTMETRICS ncm;
    ncm.cbSize = sizeof(ncm);
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm),
        (void far *)(LPNONCLIENTMETRICS)&ncm, FALSE);
    
    _hfMenu = CreateFontIndirect(&ncm.lfMenuFont);
    if (_hfMenu)
    {
        _cRefFont.AddRef();
        return TRUE;
    }
    return FALSE;
}


void CMenuDraw::ReleaseMenuFont()
{
    if (_cRefFont.Release())
    {
        return;
    }
    
    DeleteObject(_hfMenu);
    _hfMenu = NULL;
}


BOOL CMenuDraw::InitStringAndIcon()
{
    if (!_pidlAbs)
    {
        return(FALSE);
    }
    
    if (_pszString)
    {
        return(TRUE);
    }
    
    SHFILEINFO sfi;
    
    if (!SHGetFileInfo((LPCSTR)_pidlAbs, 0, &sfi, sizeof(sfi),
        SHGFI_DISPLAYNAME | SHGFI_ICON | SHGFI_SMALLICON | SHGFI_PIDL))
    {
        return(FALSE);
    }
    
    if (!Str_SetPtr(&_pszString, sfi.szDisplayName))
    {
        DestroyIcon(sfi.hIcon);
    }
    
    _hiItem = sfi.hIcon;
    
    return(TRUE);
}


BOOL CMenuDraw::GetName(LPSTR szName)
{
    if (!InitStringAndIcon())
    {
        return(FALSE);
    }
    
    lstrcpyn(szName, _pszString, MAX_PATH);
    
    return(TRUE);
}


BOOL CMenuDraw::GetExtent(SIZE *pSize, BOOL bFull)
{
    if (!InitStringAndIcon())
    {
        return(FALSE);
    }
    
    CRefMenuFont crFont(this);
    if (!(BOOL)crFont)
    {
        return(FALSE);
    }
    
    HDC hDC = GetDC(NULL);
    HFONT hfOld = (HFONT)SelectObject(hDC, _hfMenu);
    
    BOOL bRet = GetTextExtentPoint32(hDC, _pszString, lstrlen(_pszString), pSize);
    
    if (hfOld)
    {
        SelectObject(hDC, hfOld);
    }
    ReleaseDC(NULL, hDC);
    
    if (bRet)
    {
        int cxIcon = GetSystemMetrics(SM_CXSMICON);
        int cyIcon = GetSystemMetrics(SM_CYSMICON);
        
        pSize->cy = max(pSize->cy, cyIcon);
        
        if (bFull)
        {
            pSize->cx += CXIMAGEGAP + GetSystemMetrics(SM_CXSMICON) + CXIMAGEGAP + CXIMAGEGAP;
            pSize->cy += CYIMAGEGAP;
        }
        else
        {
            pSize->cx = cxIcon;
        }
    }
    
    return(bRet);
}


BOOL CMenuDraw::DrawItem(HDC hDC, RECT *prc, BOOL bFull)
{
    RECT rc = *prc;
    int cxIcon = GetSystemMetrics(SM_CXSMICON);
    int cyIcon = GetSystemMetrics(SM_CYSMICON);
    
    FillRect(hDC, prc, GetSysColorBrush(COLOR_MENU));
    
    if (!InitStringAndIcon())
    {
        return(FALSE);
    }
    
    if (bFull)
    {
        rc.left += CXIMAGEGAP;
    }
    
    DrawIconEx(hDC, rc.left, rc.top + (rc.bottom-rc.top-cyIcon)/2, _hiItem,
        0, 0, 0, NULL, DI_NORMAL);
    
    if (!bFull)
    {
        // All done
        return(TRUE);
    }
    
    CRefMenuFont crFont(this);
    if (!(BOOL)crFont)
    {
        return(FALSE);
    }
    
    rc.left += cxIcon + CXIMAGEGAP;
    HFONT hfOld = SelectObject(hDC, _hfMenu);
    
    COLORREF crOldBk = SetBkColor  (hDC, GetSysColor(COLOR_MENU));
    COLORREF crOldTx = SetTextColor(hDC, GetSysColor(COLOR_MENUTEXT));
    
    DrawText(hDC, _pszString, -1, &rc,
        DT_VCENTER|DT_SINGLELINE|DT_LEFT|DT_NOCLIP);
    
    SetBkColor  (hDC, GetSysColor(crOldBk));
    SetTextColor(hDC, GetSysColor(crOldTx));
    
    if (hfOld)
    {
        SelectObject(hDC, hfOld);
    }
    
    return(TRUE);
}


HBITMAP CMenuDraw::CreateBitmap(BOOL bFull)
{
    SIZE size;
    
    // Reference font here so we do not create it twice
    CRefMenuFont crFont(this);
    if (!(BOOL)crFont)
    {
        return(FALSE);
    }
    
    if (!GetExtent(&size, bFull))
    {
        return(NULL);
    }
    
    CWindowDC wdcScreen(NULL);
    CDCTemp cdcTemp(CreateCompatibleDC(wdcScreen));
    if (!(HDC)cdcTemp)
    {
        return(NULL);
    }
    
    HBITMAP hbmItem = CreateCompatibleBitmap(wdcScreen, size.cx, size.cy);
    if (!hbmItem)
    {
        return(NULL);
    }
    
    HBITMAP hbmOld = (HBITMAP)SelectObject(cdcTemp, hbmItem);
    RECT rc = { 0, 0, size.cx, size.cy };
    BOOL bDrawn = DrawItem(cdcTemp, &rc, bFull);
    
    SelectObject(cdcTemp, hbmOld);
    
    if (!bDrawn)
    {
        DeleteObject(hbmItem);
        hbmItem = NULL;
    }
    
    return(hbmItem);
}


class CContentMenu : public IShellExtInit, public IContextMenu2
{
    CContentMenu();
    ~CContentMenu();
    
    // IUnknown
    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj);
    STDMETHOD_(ULONG,AddRef)(void);
    STDMETHOD_(ULONG,Release)(void);
    
    // IContextMenu
    STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
    STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici);
    STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax);
    
    // IContextMenu2
    STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam);
    
    // IShellExtInit
    STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
    
    static HMENU LoadPopupMenu(UINT id, UINT uSubMenu);
    HRESULT InitMenu();
    
    LPITEMIDLIST _pidlFolder;
    HMENU _hmItems;
    
    friend STDAPI CContentMenu_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi);
    
    class CContentItemDataArray *_pIDs;
};

CContentMenu::CContentMenu() : _pidlFolder(0), _hmItems(NULL), _pIDs(NULL)
{
}

CContentMenu::~CContentMenu()
{
    if (_pidlFolder)
        ILFree(_pidlFolder);
    
    if (_hmItems)
        DestroyMenu(_hmItems);
    
    if (_pIDs)
        delete _pIDs;
}

HRESULT CContentMenu::QueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CContentMenu, IShellExtInit),                     // IID_IShellExtInit
        QITABENT(CContentMenu, IContextMenu3),                     // IID_IContextMenu3
        QITABENTMULTI(CContentMenu, IContextMenu2, IContextMenu3), // IID_IContextMenu2
        QITABENTMULTI(CSendToMenu, IContextMenu, IContextMenu3),  // IID_IContextMenu
        { 0 }
    };
    return QISearch(this, qit, riid, ppvObj);
}

ULONG CContentMenu::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

ULONG CContentMenu::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;
    delete this;
    return 0;
}

STDMETHODIMP CContentMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
{
    STGMEDIUM medium;
    LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
    if (pida)
    {
        _pidlFolder = IDA_FullIDList(pida, 0);
        HIDA_ReleaseStgMedium(pida, &medium);
    }
    return _pidlFolder ? S_OK : E_OUTOFMEMORY;
}

STDMETHODIMP CContentMenu::QueryContextMenu(HMENU hmenu,
                                            UINT indexMenu,
                                            UINT idCmdFirst,
                                            UINT idCmdLast,
                                            UINT uFlags)
{
    if (uFlags & CMF_DEFAULTONLY)
        return S_OK;

    HRESULE hr = E_OUTOFMEMORY;
    HMENU hmenuSub =  CreatePopupMenu();
    if (hmenuSub)
    {
        char szTitle[80];
        if (LoadString(g_hinst, IDS_CONTENTSMENU, szTitle, sizeof(szTitle)))
        {
            MENUITEMINFO mii = {0};
            mii.cbSize = sizeof(MENUITEMINFO);
            mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
            mii.fType = MFT_STRING;
            mii.dwTypeData = szTitle;
            mii.wID = idCmdFirst + IDC_PRESSMOD;
            mii.fState = MF_DISABLED|MF_GRAYED;

            UINT idMax = mii.wID + 1;

            if (SUCCEEDED(InitMenu()))
            {
                UINT idM = Shell_MergeMenus(hmenuSub, _hmItems, 0, idCmdFirst, idCmdLast, 0);
                
                if (GetMenuItemCount(hmenuSub) > 0)
                {
                    mii.fMask = MIIM_TYPE | MIIM_SUBMENU;
                    mii.hSubMenu = hmenuSub;

                    idMax = idM;
                }
            }

            if (InsertMenuItem(hmenu, indexMenu, TRUE, &mii))
            {
                if (mii.fMask & MIIM_SUBMENU)
                {
                }

                hr = ResultFromShort(idMax - idCmdFirst);
            }
        }

        if (FAILED(hr))
            DestroyMenu(hmenuSub);
    }

    return hr;
}


STDMETHODIMP CContentMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
{
    if (HIWORD(lpici->lpVerb))
    {
        // Deal with string commands
        return E_INVALIDARG;
    }
    
    UINT uID = (UINT)LOWORD((DWORD)lpici->lpVerb);
    switch (uID)
    {
    case IDC_PRESSMOD:
        ShellMessageBox(g_hinst, lpici->hwnd, MAKEINTRESOURCE(IDS_PRESSMOD),
            MAKEINTRESOURCE(IDS_THISDLL), MB_OK|MB_ICONINFORMATION);
        break;
        
    case IDC_SHIFTMORE:
        ShellMessageBox(g_hinst, lpici->hwnd, MAKEINTRESOURCE(IDS_SHIFTMORE),
            MAKEINTRESOURCE(IDS_THISDLL), MB_OK|MB_ICONINFORMATION);
        break;
        
    default:
        if (_hmItems)
        {
            CContentItemInfo mii(MIIM_DATA);
            
            if (!mii.GetMenuItemInfo(_hmItems, uID, FALSE))
                return E_INVALIDARG;
            
            CContentItemData *pData = mii.GetItemData();
            if (!pData || !pData->_pidl)
                return E_INVALIDARG;
            
            LPITEMIDLIST pidlAbs = ILCombine(_pidlFolder, pData->_pidl);
            if (!pidlAbs)
                return E_OUTOFMEMORY;
            
            SHELLEXECUTEINFO sei;
            
            sei.cbSize = sizeof(sei);
            sei.fMask = SEE_MASK_INVOKEIDLIST;
            sei.lpVerb = NULL;
            sei.hwnd         = lpici->hwnd;
            sei.lpParameters = lpici->lpParameters;
            sei.lpDirectory  = lpici->lpDirectory;
            sei.nShow        = lpici->nShow;
            sei.lpIDList = (void *)pidlAbs;
            
            ShellExecuteEx(&sei);
            
            ILFree(pidlAbs);
            
            break;
        }
        
        return(E_UNEXPECTED);
    }
    
    return S_OK;
}

STDMETHODIMP CContentMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax)
{
    return E_NOTIMPL;
}

STDMETHODIMP CContentMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return E_NOTIMPL;
}

HRESULT CContentMenu::InitMenu()
{
    if (!_pidlFolder)
        return E_UNEXPECTED;
    
    if (_hmItems)
        return S_OK;
    
    if (_pIDs)
        return E_UNEXPECTED;
    
    _pIDs = new CContentItemDataArray(_pidlFolder);
    if (!_pIDs)
        return E_OUTOFMEMORY;
    
    HRESULT hRes = _pIDs->Init();
    if (FAILED(hRes))
        return hRes;
    
    BOOL bGotAll = (hRes == S_OK);
    
    _hmItems = CreatePopupMenu();
    if (!_hmItems)
        return E_OUTOFMEMORY;
    
    UINT cy = 0;
    BOOL bBitmaps = TRUE;
    
    if (!bGotAll)
    {
        HMENU hmenuMerge = LoadPopupMenu(MENU_ITEMCONTEXT, 1);
        if (hmenuMerge)
        {
            Shell_MergeMenus(_hmItems, hmenuMerge, 0, 0, 1000, 0);
            DestroyMenu(hmenuMerge);
        }
        else
            return E_OUTOFMEMORY;
    }
    
    for (int i=0;; ++i)
    {
        CContentItemData * pID = (*_pIDs)[i];
        if (!pID)
        {
            // All done
            break;
        }
        
        UINT id = IDC_ITEMFIRST + i;
        
        BITMAP bm;
        GetObject(pID->_hbm, sizeof(bm), &bm);
        
        cy += bm.bmHeight;
        
        if (i==0 && !bGotAll)
        {
            // Account for the menu item we added above
            cy += cy;
        }
        
        CContentItemInfo mii(MIIM_ID | MIIM_TYPE | MIIM_DATA);
        mii.fType = 0;
        mii.SetItemData(pID);
        mii.wID = id;
        
        if (cy >= (UINT)GetSystemMetrics(SM_CYSCREEN)*4/5)
        {
            // Put in a menu break when we fill 80% the screen
            mii.fType |= MFT_MENUBARBREAK;
            cy = bm.bmHeight;
            // bBitmaps = FALSE;
        }
        
        mii.SetBitmap(pID->_hbm);
        
        if (!InsertMenuItem(_hmItems, DPA_LAST, TRUE, &mii))
        {
            return(E_OUTOFMEMORY);
        }
    }
    
    return(_hmItems ? S_OK: HMENU);
}


HMENU CContentMenu::LoadPopupMenu(UINT id, UINT uSubMenu)
{
    HMENU hmParent = LoadMenu(g_hinst, MAKEINTRESOURCE(id));
    if (!hmParent)
        return(NULL);
    
    HMENU hmPopup = GetSubMenu(hmParent, uSubMenu);
    RemoveMenu(hmParent, uSubMenu, MF_BYPOSITION);
    DestroyMenu(hmParent);
    
    return(hmPopup);
}

// 57D5ECC0-A23F-11CE-AE65-08002B2E1262
DEFINE_GUID(CLSID_ContentMenu, 0x57D5ECC0L, 0xA23F, 0x11CE, 0xAE, 0x65, 0x08, 0x00, 0x2B, 0x2E, 0x12, 0x62);

STDAPI CContentMenu_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
{
    // aggregation checking is handled in class factory
    CContentMenu *pmenu = new CContentMenu();
    if (pmenu) 
    {
        *ppunk = SAFECAST(pmenu, IContextMenu2 *);
        return S_OK;
    }
    
    return E_OUTOFMEMORY;
}

#endif // CONTENT

#define CXIMAGEGAP      6

//
//This is included by shell32/shellprv.h I'm not sure where this is in shdocvw
#define CCH_KEYMAX  64

typedef struct 
{
    // Accessibility info must be first
    MSAAMENUINFO msaa;
    TCHAR chPrefix;
    TCHAR szMenuText[CCH_KEYMAX];
    TCHAR szExt[MAX_PATH];
    TCHAR szClass[CCH_KEYMAX];
    DWORD dwFlags;
    int iImage;
    TCHAR szUserFile[CCH_KEYMAX];
} NEWOBJECTINFO;

typedef struct 
{
    int type;
    void *lpData;
    DWORD cbData;
    HKEY hkeyNew;
} NEWFILEINFO;

typedef struct 
{
    ULONG       cbStruct;
    ULONG       ver;
    SYSTEMTIME  lastupdate;
} SHELLNEW_CACHE_STAMP;

// ShellNew config flags
#define SNCF_DEFAULT    0x0000
#define SNCF_NOEXT      0x0001
#define SNCF_USERFILES  0x0002

#define NEWTYPE_DATA    0x0003
#define NEWTYPE_FILE    0x0004
#define NEWTYPE_NULL    0x0005
#define NEWTYPE_COMMAND 0x0006
#define NEWTYPE_FOLDER  0x0007
#define NEWTYPE_LINK    0x0008

#define NEWITEM_FOLDER  0
#define NEWITEM_LINK    1
#define NEWITEM_MAX     2

class CNewMenu : public CObjectWithSite,
                 public IContextMenu3, 
                 public IShellExtInit
{
    // IUnknown
    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj);
    STDMETHOD_(ULONG,AddRef)(void);
    STDMETHOD_(ULONG,Release)(void);
    
    // IContextMenu
    STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
    STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici);
    STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax);
    
    // IContextMenu2
    STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam);
    
    // IContextMenu3
    STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *lResult);
    
    // IShellExtInit
    STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
    
   
    LONG            _cRef;
    HMENU           _hmenu;
    UINT            _idCmdFirst;
    HIMAGELIST      _himlSystemImageList;
    IDataObject    *_pdtobj;
    LPITEMIDLIST    _pidlFolder;
    POINT           _ptNewItem;     // from the view, point of click
    NEWOBJECTINFO  *_pnoiLast;
    HDPA            _hdpaMenuInfo;
    
    CNewMenu();
    ~CNewMenu();
    
    friend HRESULT CNewMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut);

private:
    //Handle Menu messages submitted to HandleMenuMsg
    BOOL DrawItem(DRAWITEMSTRUCT *lpdi);
    LRESULT MeasureItem(MEASUREITEMSTRUCT *pmi);
    BOOL InitMenuPopup(HMENU hMenu);
    
    //Internal Helpers
    NEWOBJECTINFO *GetItemData(HMENU hmenu, UINT iItem);
    HRESULT RunCommand(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszRun);
    HRESULT CopyTemplate(IStream *pStream, NEWFILEINFO *pnfi);

    // Generates it from the Fragment and _pidlFolder
    BOOL _GeneratePidlFromName(LPTSTR pszName, LPITEMIDLIST* ppidl);
    HRESULT _GetItemName(IUnknown *punkFolder, LPWSTR pszItemName, LPWSTR pszPath, UINT cchPath);

    HRESULT _MatchMenuItem(TCHAR ch, LRESULT* plRes);
    BOOL _InsertNewMenuItem(HMENU hmenu, UINT idCmd, NEWOBJECTINFO *pnoiClone);
    
    HRESULT ConsolidateMenuItems(BOOL bForce);

    HANDLE _hMutex, _hEvent;
};

void GetConfigFlags(HKEY hkey, DWORD * pdwFlags)
{
    TCHAR szTemp[MAX_PATH];
    DWORD cbData = ARRAYSIZE(szTemp);
    
    *pdwFlags = SNCF_DEFAULT;
    
    if (SHQueryValueEx(hkey, TEXT("NoExtension"), 0, NULL, (BYTE *)szTemp, &cbData) == ERROR_SUCCESS) 
    {
        *pdwFlags |= SNCF_NOEXT;
    }
}

BOOL GetNewFileInfoForKey(HKEY hkeyExt, NEWFILEINFO *pnfi, DWORD * pdwFlags)
{
    BOOL fRet = FALSE;
    HKEY hKey; // this gets the \\.ext\progid  key
    HKEY hkeyNew;
    TCHAR szProgID[80];
    LONG lSize = sizeof(szProgID);
    
    // open the Newcommand
    if (SHRegQueryValue(hkeyExt, NULL,  szProgID, &lSize) != ERROR_SUCCESS)
    {
        return FALSE;
    }
    
    if (RegOpenKey(hkeyExt, szProgID, &hKey) != ERROR_SUCCESS)
    {
        hKey = hkeyExt;
    }
    
    if (RegOpenKey(hKey, TEXT("ShellNew"), &hkeyNew) == ERROR_SUCCESS)
    {
        DWORD dwType, cbData;
        TCHAR szTemp[MAX_PATH];
        HKEY hkeyConfig;
        
        // Are there any config flags?
        if (pdwFlags)
        {
            
            if (RegOpenKey(hkeyNew, TEXT("Config"), &hkeyConfig) == ERROR_SUCCESS)
            {
                GetConfigFlags(hkeyConfig, pdwFlags);
                RegCloseKey(hkeyConfig);
            }
            else
            {
                *pdwFlags = 0;
            }
        }
        
        if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("FileName"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS) 
            && ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))) 
        {
            fRet = TRUE;
            if (pnfi)
            {
                pnfi->type = NEWTYPE_FILE;
                pnfi->hkeyNew = hkeyNew; // store this away so we can find out which one held the file easily
                ASSERT((LPTSTR*)pnfi->lpData == NULL);
                pnfi->lpData = StrDup(szTemp);
                
                hkeyNew = NULL;
            }
        } 
        else if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("command"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS) 
            && ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))) 
        {
            
            fRet = TRUE;
            if (pnfi)
            {
                pnfi->type = NEWTYPE_COMMAND;
                pnfi->hkeyNew = hkeyNew; // store this away so we can find out which one held the command easily
                ASSERT((LPTSTR*)pnfi->lpData == NULL);
                pnfi->lpData = StrDup(szTemp);
                hkeyNew = NULL;
            }
        } 
        else if ((SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, NULL, &cbData) == ERROR_SUCCESS) && cbData) 
        {
            // yes!  the data for a new file is stored in the registry
            fRet = TRUE;
            // do they want the data?
            if (pnfi)
            {
                pnfi->type = NEWTYPE_DATA;
                pnfi->cbData = cbData;
                pnfi->lpData = (void*)LocalAlloc(LPTR, cbData);
                if (pnfi->lpData)
                {
                    if (dwType == REG_SZ)
                    {
                        //  Get the Unicode data from the registry.
                        LPWSTR pszTemp = (LPWSTR)LocalAlloc(LPTR, cbData);
                        if (pszTemp)
                        {
                            SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, (LPBYTE)pszTemp, &cbData);
                            
                            pnfi->cbData = SHUnicodeToAnsi(pszTemp, (LPSTR)pnfi->lpData, cbData);
                            if (pnfi->cbData == 0)
                            {
                                LocalFree(pnfi->lpData);
                                pnfi->lpData = NULL;
                            }
                            
                            LocalFree(pszTemp);
                        }
                        else
                        {
                            LocalFree(pnfi->lpData);
                            pnfi->lpData = NULL;
                        }
                    }
                    else
                    {
                        SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, (BYTE*)pnfi->lpData, &cbData);
                    }
                }
            }
        }
        else if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("NullFile"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS)) 
        {
            fRet = TRUE;
            if (pnfi)
            {
                pnfi->type = NEWTYPE_NULL;
                pnfi->cbData = 0;
                pnfi->lpData = NULL;
            }
        } 
        
        if (hkeyNew)
            RegCloseKey(hkeyNew);
    }
    
    if (hKey != hkeyExt)
    {
        RegCloseKey(hKey);
    }
    return fRet;
}

BOOL GetNewFileInfoForExtension(NEWOBJECTINFO *pnoi, NEWFILEINFO *pnfi, HKEY* phKey, LPINT piIndex)
{
    TCHAR szValue[80];
    LONG lSize = sizeof(szValue);
    HKEY hkeyNew;
    BOOL fRet = FALSE;;
    
    if (phKey && ((*phKey) == (HKEY)-1))
    {
        // we're done
        return FALSE;
    }
    
    // do the ShellNew key stuff if there's no phKey passed in (which means
    // use the info in pnoi to get THE one) and there's no UserFile specified.
    // 
    // if there IS a UserFile specified, then it's a file, and that szUserFile points to it..
    if (!phKey && !pnoi->szUserFile[0] ||
        (phKey && !*phKey)) 
    {
        // check the new keys under the class id (if any)
        TCHAR szSubKey[128];
        wnsprintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("%s\\CLSID"), pnoi->szClass);
        lSize = sizeof(szValue);
        if (SHRegQueryValue(HKEY_CLASSES_ROOT, szSubKey, szValue, &lSize) == ERROR_SUCCESS)
        {
            wnsprintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("CLSID\\%s"), szValue);
            lSize = sizeof(szValue);
            if (RegOpenKey(HKEY_CLASSES_ROOT, szSubKey, &hkeyNew) == ERROR_SUCCESS)
            {
                fRet = GetNewFileInfoForKey(hkeyNew, pnfi, &pnoi->dwFlags);
                RegCloseKey(hkeyNew);
            }
        }
        
        // otherwise check under the type extension... do the extension, not the type
        // so that multi-ext to 1 type will work right
        if (!fRet && (RegOpenKey(HKEY_CLASSES_ROOT, pnoi->szExt, &hkeyNew) == ERROR_SUCCESS))
        {
            fRet = GetNewFileInfoForKey(hkeyNew, pnfi, &pnoi->dwFlags);
            RegCloseKey(hkeyNew);
        }
        
        if (phKey)
        {
            // if we're iterating, then we've got to open the key now...
            wnsprintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("%s\\%s\\ShellNew\\FileName"), pnoi->szExt, pnoi->szClass);
            if (RegOpenKey(HKEY_CLASSES_ROOT, szSubKey, phKey) == ERROR_SUCCESS)
            {
                *piIndex = 0;
                
                // if we didn't find one of the default ones above, 
                // try it now
                // otherwise just return success or failure on fRet
                if (!fRet)
                {
                    goto Iterate;
                } 
            }
            else
            {
                *phKey = (HKEY)-1;
            }
        }
    }
    else if (!phKey && pnoi->szUserFile[0])
    {
        // there's no key, so just return info about szUserFile
        pnfi->type = NEWTYPE_FILE;
        pnfi->lpData = StrDup(pnoi->szUserFile);
        pnfi->hkeyNew = NULL;
        
        fRet = TRUE;
    }
    else if (phKey)
    {
        DWORD dwSize;
        DWORD dwData;
        DWORD dwType;
        // we're iterating through...
        
Iterate:
        
        dwSize = ARRAYSIZE(pnoi->szUserFile);
        dwData = ARRAYSIZE(pnoi->szMenuText);
        
        if (RegEnumValue(*phKey, *piIndex, pnoi->szUserFile, &dwSize, NULL,
            &dwType, (LPBYTE)pnoi->szMenuText, &dwData) == ERROR_SUCCESS)
        {
            (*piIndex)++;
            // if there's something more than the null..
            if (dwData <= 1)
            { 
                lstrcpyn(pnoi->szMenuText, PathFindFileName(pnoi->szUserFile), ARRAYSIZE(pnoi->szMenuText));
                PathRemoveExtension(pnoi->szMenuText);
            }
            fRet = TRUE;
        }
        else
        {
            RegCloseKey(*phKey);
            *phKey = (HKEY)-1;
            fRet = FALSE;
        }
    }
    
    return fRet;
}

#define SHELLNEW_CONSOLIDATION_MUTEX TEXT("ShellNewConsolidationMutex")
#define SHELLNEW_CONSOLIDATION_EVENT TEXT("ShellNewConsolidationEvent")

CNewMenu::CNewMenu() :
    _cRef(1),
    _hMutex(CreateMutex(NULL, FALSE, SHELLNEW_CONSOLIDATION_MUTEX)),
    _hEvent(CreateEvent(NULL, FALSE, FALSE, SHELLNEW_CONSOLIDATION_EVENT))
{
    DllAddRef();
    ASSERT(_pnoiLast == NULL);
}

CNewMenu::~CNewMenu()
{
    if (_hdpaMenuInfo)
    {
        // we dont own the lifetime of _hmenu, and it gets destroyed before the destructor
        // is called.  thus maintain the lifetime of our NEWOBJECTINFO data in a dpa.
        for (int i = 0; i < DPA_GetPtrCount(_hdpaMenuInfo); i++)
        {
            NEWOBJECTINFO *pNewObjInfo = (NEWOBJECTINFO *)DPA_GetPtr(_hdpaMenuInfo, i);
            LocalFree(pNewObjInfo);
        }
        DPA_Destroy(_hdpaMenuInfo);
    }
    
    ILFree(_pidlFolder);

    if (_pdtobj)
        _pdtobj->Release();

    if (_hMutex)
    {
        CloseHandle(_hMutex);
    }
    if (_hEvent)
    {
        CloseHandle(_hEvent);
    }

    DllRelease();
}

HRESULT CNewMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut)
{
    // aggregation checking is handled in class factory
    *ppvOut = NULL;

    HRESULT hr = E_OUTOFMEMORY;
    CNewMenu * pShellNew = new CNewMenu();
    if (pShellNew) 
    {
        if (!pShellNew->_hMutex || !pShellNew->_hEvent)
        {
            hr = E_OUTOFMEMORY;
        }
        else
        {
            hr = pShellNew->QueryInterface(riid, ppvOut);
        }
        pShellNew->Release();
    }

    return hr;
}

HRESULT CNewMenu::QueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CNewMenu, IShellExtInit),                     // IID_IShellExtInit
        QITABENT(CNewMenu, IContextMenu3),                     // IID_IContextMenu3
        QITABENTMULTI(CNewMenu, IContextMenu2, IContextMenu3), // IID_IContextMenu2
        QITABENTMULTI(CNewMenu, IContextMenu, IContextMenu3),  // IID_IContextMenu
        QITABENT(CNewMenu, IObjectWithSite),                   // IID_IObjectWithSite
        { 0 }
    };
    return QISearch(this, qit, riid, ppvObj);
}

ULONG CNewMenu::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

ULONG CNewMenu::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;
    delete this;
    return 0;
}

HRESULT CNewMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
    // if they want the default menu only (CMF_DEFAULTONLY) OR 
    // this is being called for a shortcut (CMF_VERBSONLY)
    // we don't want to be on the context menu
    MENUITEMINFO mfi = {0};
    
    if (uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY))
        return S_OK;
    
    ConsolidateMenuItems(FALSE);

    _idCmdFirst = idCmdFirst+2;
    TCHAR szNewMenu[80];
    LoadString(g_hinst, IDS_NEWMENU, szNewMenu, ARRAYSIZE(szNewMenu));

    // HACK: I assume that they are querying during a WM_INITMENUPOPUP or equivalent
    GetCursorPos(&_ptNewItem);
    
    _hmenu = CreatePopupMenu();
    mfi.cbSize = sizeof(MENUITEMINFO);
    mfi.fMask = MIIM_ID | MIIM_TYPE;
    mfi.wID = idCmdFirst+1;
    mfi.fType = MFT_STRING;
    mfi.dwTypeData = szNewMenu;
    
    InsertMenuItem(_hmenu, 0, TRUE, &mfi);
    
    ZeroMemory(&mfi, sizeof (mfi));
    mfi.cbSize = sizeof(MENUITEMINFO);
    mfi.fMask = MIIM_ID | MIIM_SUBMENU | MIIM_TYPE | MIIM_DATA;
    mfi.fType = MFT_STRING;
    mfi.wID = idCmdFirst;
    mfi.hSubMenu = _hmenu;
    mfi.dwTypeData = szNewMenu;
    mfi.dwItemData = 0;
    
    InsertMenuItem(hmenu, indexMenu, TRUE, &mfi);

    _hmenu = NULL;
    return ResultFromShort(_idCmdFirst - idCmdFirst + 1);
}

// This is almost the same as ILCreatePidlFromPath, but
// uses only the filename from the full path pszPath and
// the _pidlFolder to generate the pidl. This is used because
// when creating an item in Desktop\My Documents, it used to create a
// full pidl c:\documents and Settings\lamadio\My Documents\New folder
// instead of the pidl desktop\my documents\New Folder.
BOOL CNewMenu::_GeneratePidlFromName(LPTSTR pszFile, LPITEMIDLIST* ppidl)
{
    *ppidl = NULL;  // Out param

    IShellFolder* psf;
    if (SUCCEEDED(SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, _pidlFolder, &psf))))
    {
        LPITEMIDLIST pidlItem;

        if (SUCCEEDED(psf->ParseDisplayName(NULL, NULL, pszFile, NULL, &pidlItem, NULL)))
        {
            *ppidl = ILCombine(_pidlFolder, pidlItem);
            ILFree(pidlItem);
        }

        psf->Release();
    }

    return BOOLFROMPTR(*ppidl);
}

HRESULT CNewMenu::_GetItemName(IUnknown *punkFolder, LPWSTR pszItemName, LPWSTR pszPath, UINT cchPath)
{
    // we need to pick up the name by asking the folder about the item,
    // not by pathappending to the folder's path.
    IShellFolder *psf;
    HRESULT hr = punkFolder->QueryInterface(IID_PPV_ARG(IShellFolder, &psf));
    if (SUCCEEDED(hr))
    {
        LPITEMIDLIST pidlFile;
        hr = psf->ParseDisplayName(NULL, NULL, pszItemName, NULL, &pidlFile, NULL);
        if (SUCCEEDED(hr))
        {
            hr = DisplayNameOf(psf, pidlFile, SHGDN_FORPARSING, pszPath, cchPath);
            ILFree(pidlFile);
        }
        psf->Release();
    }
    return hr;
}

const ICIVERBTOIDMAP c_IDMap[] =
{
    { L"NewFolder", "NewFolder", NEWITEM_FOLDER, NEWITEM_FOLDER, },
    { L"link",      "link",      NEWITEM_LINK,   NEWITEM_LINK,   },
};

HRESULT CNewMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
    HRESULT hr = E_FAIL;
    DWORD dwFlags;

    if (IS_INTRESOURCE(pici->lpVerb) && _pnoiLast)
        dwFlags = _pnoiLast->dwFlags;
    else
    {
        UINT uID;
        if (SUCCEEDED(SHMapICIVerbToCmdID(pici, c_IDMap, ARRAYSIZE(c_IDMap), &uID)))
        {
            switch (uID)
            {
                case NEWITEM_FOLDER:
                    dwFlags = NEWTYPE_FOLDER;
                    break;
                case NEWITEM_LINK:
                    dwFlags = NEWTYPE_LINK;
                    break;
                default:
                    ASSERTMSG(0, "should not get what we don't put on the menu");
                    return E_FAIL;
            }
        }
    }
    
    TCHAR szFileSpec[MAX_PATH+80];   // Add some slop incase we overflow
    TCHAR szTemp[MAX_PATH+80];       // Add some slop incase we overflow

    //See if the pidl is folder shortcut and if so get the target path.
    SHGetTargetFolderPath(_pidlFolder, szTemp, ARRAYSIZE(szTemp));
    BOOL fLFN = IsLFNDrive(szTemp);

    NEWFILEINFO nfi;
    DWORD dwErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);

    nfi.lpData = NULL;
    nfi.hkeyNew = NULL;

    switch (dwFlags)
    {
    case NEWTYPE_FOLDER:
        LoadString(g_hinst, fLFN ? IDS_FOLDERLONGPLATE : IDS_FOLDERTEMPLATE, szFileSpec, ARRAYSIZE(szFileSpec));
        break;

    case NEWTYPE_LINK:
        LoadString(g_hinst, IDS_NEWLINKTEMPLATE, szFileSpec, ARRAYSIZE(szFileSpec));
        break;

    default:
        LoadString(g_hinst, IDS_NEWFILEPREFIX, szFileSpec, ARRAYSIZE(szFileSpec));

        //
        // If we are running on a mirrored BiDi localized system,
        // then flip the order of concatenation so that the
        // string is read properly for Arabic. [samera]
        //
        if (IS_BIDI_LOCALIZED_SYSTEM())
        {
            lstrcpyn(szTemp, szFileSpec, ARRAYSIZE(szTemp));
            wnsprintf(szFileSpec, ARRAYSIZE(szFileSpec), TEXT("%s %s"), _pnoiLast->szMenuText, szTemp);
        }
        else
        {
            StrCatBuff(szFileSpec, _pnoiLast->szMenuText, ARRAYSIZE(szFileSpec));
        }
        SHStripMneumonic(szFileSpec);

        if (!(dwFlags & SNCF_NOEXT))
            StrCatBuff(szFileSpec, _pnoiLast->szExt, ARRAYSIZE(szFileSpec));
        break;
    }

    BOOL fCreateStorage = (dwFlags == NEWTYPE_FOLDER);

    //See if the pidl is folder shortcut and if so get the target pidl.
    LPITEMIDLIST pidlTarget;
    hr = SHGetTargetFolderIDList(_pidlFolder, &pidlTarget);
    if (SUCCEEDED(hr))
    {
        IStorage * pStorage;
        hr = StgBindToObject(pidlTarget, STGM_READWRITE, IID_PPV_ARG(IStorage, &pStorage));
        if (SUCCEEDED(hr))
        {
            IStream *pStreamCreated = NULL;
            IStorage *pStorageCreated = NULL;

            STATSTG statstg = { 0 };
            if (fCreateStorage)
            {
                hr = StgMakeUniqueName(pStorage, szFileSpec, IID_PPV_ARG(IStorage, &pStorageCreated));
                if (SUCCEEDED(hr))
                    pStorageCreated->Stat(&statstg, STATFLAG_DEFAULT);
            }
            else
            {
                hr = StgMakeUniqueName(pStorage, szFileSpec, IID_PPV_ARG(IStream, &pStreamCreated));
                if (SUCCEEDED(hr))
                    pStreamCreated->Stat(&statstg, STATFLAG_DEFAULT);
            }

            if (SUCCEEDED(hr))
            {
                switch (dwFlags)
                {
                case NEWTYPE_FOLDER:
                    // we're already done.
                    break;

                case NEWTYPE_LINK:
                    if (statstg.pwcsName)
                    {
                        // Lookup Command in Registry under key HKCR/.lnk/ShellNew/Command
                        TCHAR szCommand[MAX_PATH];
                        DWORD dwLength = sizeof(szCommand);
                        if (ERROR_SUCCESS == SHGetValue(HKEY_CLASSES_ROOT, 
                            TEXT(".lnk\\ShellNew"), TEXT("Command"), NULL, szCommand, &dwLength))
                        {
                            TCHAR szPath[MAX_PATH];
                            hr = _GetItemName(SAFECAST(pStorage, IUnknown*), statstg.pwcsName, szPath, ARRAYSIZE(szPath));
                            if (SUCCEEDED(hr))
                            {
                                hr = RunCommand(pici->hwnd, szPath, szCommand);
                            }
                        }
                    }
                    break;

                default:
                    if (GetNewFileInfoForExtension(_pnoiLast, &nfi, NULL, NULL))
                    {
                        switch (nfi.type) 
                        {
                        case NEWTYPE_FILE:
                            hr = CopyTemplate(pStreamCreated, &nfi);
                            break;

                        case NEWTYPE_NULL:
                            // already created a zero-length file.
                            break;

                        case NEWTYPE_DATA:
                            ULONG ulWritten;
                            hr = pStreamCreated->Write(nfi.lpData, nfi.cbData, &ulWritten);
                            if (SUCCEEDED(hr))
                            {
                                hr = pStreamCreated->Commit(STGC_DEFAULT);
                            }
                            break;

                        case NEWTYPE_COMMAND:
                            if (statstg.pwcsName)
                            {
                                TCHAR szPath[MAX_PATH];
                                hr = _GetItemName(SAFECAST(pStorage, IUnknown*), statstg.pwcsName, szPath, ARRAYSIZE(szPath));
                                if (SUCCEEDED(hr))
                                {
                                    // oops, we already created the stream, but we actually
                                    // just wanted the filename for the RunCommand, so we
                                    // have to delete it first.
                                    ATOMICRELEASE(pStreamCreated);
                                    hr = pStorage->DestroyElement(statstg.pwcsName);
                                    // flush out any notifications from the destroy (the
                                    // destroy causes notifications).
                                    SHChangeNotifyHandleEvents();

                                    if (SUCCEEDED(hr))
                                    {
                                        hr = RunCommand(pici->hwnd, szPath, (LPTSTR)nfi.lpData);
                                        if (hr == S_FALSE)
                                            hr = S_OK;
                                    }
                                }
                            }
                            break;

                        default:
                            hr = E_FAIL;
                            break;
                        }
                    }
                    else
                    {
                        hr = HRESULT_FROM_WIN32(ERROR_BADKEY);
                    }
                    break;
                }

                // these have to be released before _GeneratePidlFromName, since that opens
                // the storage in exclusive mode for other reasons.  but we can't release
                // them earlier because CopyTemplate might need them.
                if (pStorageCreated)
                    pStorageCreated->Release();
                if (pStreamCreated)
                    pStreamCreated->Release();
                if (SUCCEEDED(hr))
                    hr = pStorage->Commit(STGC_DEFAULT);
                pStorage->Release();

                LPITEMIDLIST pidlCreatedItem;
                if (SUCCEEDED(hr) &&
                    _GeneratePidlFromName(statstg.pwcsName, &pidlCreatedItem))
                {
                    SHChangeNotifyHandleEvents();
                    IShellView2 *psv2;
                    if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IShellView2, &psv2))))
                    {
                        DWORD dwFlagsSelFlags = SVSI_SELECT | SVSI_POSITIONITEM;

                        if (!(dwFlags & NEWTYPE_LINK))
                            dwFlagsSelFlags |= SVSI_EDIT;

                        psv2->SelectAndPositionItem(ILFindLastID(pidlCreatedItem), dwFlagsSelFlags, NULL);
                        psv2->Release();
                    }
                    ILFree(pidlCreatedItem);
                }

                CoTaskMemFree(statstg.pwcsName);
            }
            else
            {
                pStorage->Release();
            }
        }

        ILFree(pidlTarget);
    }

    if (nfi.lpData)
        LocalFree((HLOCAL)nfi.lpData);
    
    if (nfi.hkeyNew)
        RegCloseKey(nfi.hkeyNew);

    if (FAILED_AND_NOT_CANCELED(hr) && !(pici->fMask & CMIC_MASK_FLAG_NO_UI))
    {
        TCHAR szTitle[MAX_PATH];

        LoadString(g_hinst, (fCreateStorage ? IDS_DIRCREATEFAILED_TITLE : IDS_FILECREATEFAILED_TITLE), szTitle, ARRAYSIZE(szTitle));
        SHSysErrorMessageBox(pici->hwnd, szTitle, fCreateStorage ? IDS_CANNOTCREATEFOLDER : IDS_CANNOTCREATEFILE,
                HRESULT_CODE(hr), szFileSpec, MB_OK | MB_ICONEXCLAMATION);
    }

    SetErrorMode(dwErrorMode);

    return hr;
}

HRESULT CNewMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax)
{
    switch (uType)
    {
    case GCS_HELPTEXT:
        if (idCmd < NEWITEM_MAX)
        {
            LoadString(g_hinst, (UINT)(IDS_NEWHELP_FIRST + idCmd), (LPTSTR)pszName, cchMax);
            return S_OK;
        }
        break;
    case GCS_HELPTEXTA:
        if (idCmd < NEWITEM_MAX)
        {
            LoadStringA(g_hinst, (UINT)(IDS_NEWHELP_FIRST + idCmd), pszName, cchMax);
            return S_OK;
        }
        break;

    case GCS_VERBW:
    case GCS_VERBA:
        return SHMapCmdIDToVerb(idCmd, c_IDMap, ARRAYSIZE(c_IDMap), pszName, cchMax, (GCS_VERBW == uType));
    }

    return E_NOTIMPL;
}

//Defined in fsmenu.obj
BOOL _MenuCharMatch(LPCTSTR lpsz, TCHAR ch, BOOL fIgnoreAmpersand);

HRESULT CNewMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return HandleMenuMsg2(uMsg,wParam,lParam,NULL);
}

HRESULT CNewMenu::_MatchMenuItem(TCHAR ch, LRESULT* plRes)
{
    // If plRes is NULL we're being called on HandleMenuMsg() which
    // doesn't support returning an LRESULT, which is needed for WM_MENUCHAR...
    if (plRes == NULL)
        return S_FALSE;

    int iLastSelectedItem = -1;
    int iNextMatch = -1;
    BOOL fMoreThanOneMatch = FALSE;
    int c = GetMenuItemCount(_hmenu);

    // Pass 1: Locate the Selected Item
    for (int i = 0; i < c; i++) 
    {
        MENUITEMINFO mii = {0};
        mii.cbSize = sizeof(mii);
        mii.fMask = MIIM_STATE;
        if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii))
        {
            if (mii.fState & MFS_HILITE)
            {
                iLastSelectedItem = i;
                break;
            }
        }
    }

    // Pass 2: Starting from the selected item, locate the first item with the matching name.
    for (i = iLastSelectedItem + 1; i < c; i++) 
    {
        MENUITEMINFO mii = {0};
        mii.cbSize = sizeof(mii);
        mii.fMask = MIIM_DATA | MIIM_STATE;
        if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii))
        {
            NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)mii.dwItemData;
            if (pnoi && _MenuCharMatch(pnoi->szMenuText, ch, FALSE))
            {
                _pnoiLast = pnoi;
                
                if (iNextMatch != -1)
                {
                    fMoreThanOneMatch = TRUE;
                    break;                      // We found all the info we need
                }
                else
                {
                    iNextMatch = i;
                }
            }
        }
    }

    // Pass 3: If we did not find a match, or if there was only one match
    // Search from the first item, to the Selected Item
    if (iNextMatch == -1 || fMoreThanOneMatch == FALSE)
    {
        for (i = 0; i <= iLastSelectedItem; i++) 
        {
            MENUITEMINFO mii = {0};
            mii.cbSize = sizeof(mii);
            mii.fMask = MIIM_DATA | MIIM_STATE;
            if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii))
            {
                NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)mii.dwItemData;
                if (pnoi && _MenuCharMatch(pnoi->szMenuText, ch, FALSE))
                {
                    _pnoiLast = pnoi;
                    if (iNextMatch != -1)
                    {
                        fMoreThanOneMatch = TRUE;
                        break;
                    }
                    else
                    {
                        iNextMatch = i;
                    }
                }
            }
        }
    }

    if (iNextMatch != -1)
    {
        *plRes = MAKELONG(iNextMatch, fMoreThanOneMatch? MNC_SELECT : MNC_EXECUTE);
    }
    else
    {
        *plRes = MAKELONG(0, MNC_IGNORE);
    }

    return S_OK;
}

HRESULT CNewMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *plResult)
{
    HRESULT hr = S_OK;
    LRESULT lRes = 0;

    switch (uMsg)
    {
    case WM_INITMENUPOPUP:
        if (_hmenu == NULL)
        {
            _hmenu = (HMENU)wParam;
        }
        
        InitMenuPopup(_hmenu);
        break;
        
    case WM_DRAWITEM:
        DrawItem((DRAWITEMSTRUCT *)lParam);
        break;
        
    case WM_MEASUREITEM:
        lRes = MeasureItem((MEASUREITEMSTRUCT *)lParam);
        break;

    case WM_MENUCHAR:
        hr = _MatchMenuItem((TCHAR)LOWORD(wParam), &lRes);
        break;

    default:
        hr = E_NOTIMPL;
        break;
    }

    if (plResult)
        *plResult = lRes;

    return hr;
}

HRESULT CNewMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
{
    ASSERT(_pidlFolder == NULL);
    _pidlFolder = ILClone(pidlFolder);
   
    IUnknown_Set((IUnknown **)&_pdtobj, pdtobj);
    return S_OK;
}

BOOL CNewMenu::DrawItem(DRAWITEMSTRUCT *lpdi)
{
    BOOL fFlatMenu = FALSE;
    BOOL fFrameRect = FALSE;

    SystemParametersInfo(SPI_GETFLATMENU, 0, (PVOID)&fFlatMenu, 0);

    if ((lpdi->itemAction & ODA_SELECT) || (lpdi->itemAction & ODA_DRAWENTIRE))
    {
        int x, y;
        SIZE sz;
        NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)lpdi->itemData;
        
        // Draw the image (if there is one).
        
        GetTextExtentPoint(lpdi->hDC, pnoi->szMenuText, lstrlen(pnoi->szMenuText), &sz);
        
        if (lpdi->itemState & ODS_SELECTED)
        {
            // REVIEW HACK - keep track of the last selected item.
            _pnoiLast = pnoi;
            if (fFlatMenu)
            {
                fFrameRect = TRUE;
                SetBkColor(lpdi->hDC, GetSysColor(COLOR_MENUHILIGHT));
                SetTextColor(lpdi->hDC, GetSysColor(COLOR_MENUTEXT));
                FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_MENUHILIGHT));
            }
            else
            {
                SetBkColor(lpdi->hDC, GetSysColor(COLOR_HIGHLIGHT));
                SetTextColor(lpdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
                FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_HIGHLIGHT));
            }
        }
        else
        {
            SetTextColor(lpdi->hDC, GetSysColor(COLOR_MENUTEXT));
            FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_MENU));
        }
        
        RECT rc = lpdi->rcItem;
        rc.left += +2*CXIMAGEGAP+g_cxSmIcon;
        
        
        DrawText(lpdi->hDC,pnoi->szMenuText,lstrlen(pnoi->szMenuText),
            &rc,DT_SINGLELINE|DT_VCENTER);
        if (pnoi->iImage != -1)
        {
            x = lpdi->rcItem.left+CXIMAGEGAP;
            y = (lpdi->rcItem.bottom+lpdi->rcItem.top-g_cySmIcon)/2;
            HIMAGELIST himlSmall;
            Shell_GetImageLists(NULL, &himlSmall);
            ImageList_Draw(himlSmall, pnoi->iImage, lpdi->hDC, x, y, ILD_TRANSPARENT);
        } 
        else 
        {
            x = lpdi->rcItem.left+CXIMAGEGAP;
            y = (lpdi->rcItem.bottom+lpdi->rcItem.top-g_cySmIcon)/2;
        }

        if (fFrameRect)
        {
            HBRUSH hbrFill = (HBRUSH)GetSysColorBrush(COLOR_HIGHLIGHT);
            HBRUSH hbrSave = (HBRUSH)SelectObject(lpdi->hDC, hbrFill);
            int x = lpdi->rcItem.left;
            int y = lpdi->rcItem.top;
            int cx = lpdi->rcItem.right - x - 1;
            int cy = lpdi->rcItem.bottom - y - 1;

            PatBlt(lpdi->hDC, x, y, 1, cy, PATCOPY);
            PatBlt(lpdi->hDC, x + 1, y, cx, 1, PATCOPY);
            PatBlt(lpdi->hDC, x, y + cy, cx, 1, PATCOPY);
            PatBlt(lpdi->hDC, x + cx, y + 1, 1, cy, PATCOPY);

            SelectObject(lpdi->hDC, hbrSave);
        }

        return TRUE;
    }

    return FALSE;
}

LRESULT CNewMenu::MeasureItem(MEASUREITEMSTRUCT *pmi)
{
    LRESULT lres = FALSE;
    NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)pmi->itemData;
    if (pnoi)
    {
        // Get the rough height of an item so we can work out when to break the
        // menu. User should really do this for us but that would be useful.
        HDC hdc = GetDC(NULL);
        if (hdc)
        {
            // REVIEW cache out the menu font?
            NONCLIENTMETRICS ncm;
            ncm.cbSize = sizeof(ncm);
            if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE))
            {
                HFONT hfont = CreateFontIndirect(&ncm.lfMenuFont);
                if (hfont)
                {
                    SIZE sz;
                    HFONT hfontOld = (HFONT)SelectObject(hdc, hfont);
                    GetTextExtentPoint(hdc, pnoi->szMenuText, lstrlen(pnoi->szMenuText), &sz);
                    pmi->itemHeight = max (g_cySmIcon+CXIMAGEGAP/2, ncm.iMenuHeight);
                    pmi->itemWidth = g_cxSmIcon + 2*CXIMAGEGAP + sz.cx;
                    //pmi->itemWidth = 2*CXIMAGEGAP + sz.cx;
                    SelectObject(hdc, hfontOld);
                    DeleteObject(hfont);
                    lres = TRUE;
                }
            }
            ReleaseDC(NULL, hdc);
        }
    }
    return lres;
}

BOOL GetClassDisplayName(LPTSTR pszClass,LPTSTR pszDisplayName,DWORD cchDisplayName)
{
    DWORD cch;

    return SUCCEEDED(AssocQueryString(0, ASSOCSTR_COMMAND, pszClass, TEXT("open"), NULL, &cch)) && 
           SUCCEEDED(AssocQueryString(0, ASSOCSTR_FRIENDLYDOCNAME, pszClass, NULL, pszDisplayName, &cchDisplayName));
}

//  New Menu item consolidation worker task
class CNewMenuConsolidator : public CRunnableTask
{
public:
    virtual STDMETHODIMP RunInitRT(void);
    static const GUID _taskid;

    static HRESULT CreateInstance(REFIID riid, void **ppv);

private:
    CNewMenuConsolidator();
    ~CNewMenuConsolidator();

    HANDLE _hMutex, _hEvent;
};


CNewMenuConsolidator::CNewMenuConsolidator() :
    CRunnableTask(RTF_DEFAULT),
    _hMutex(CreateMutex(NULL, FALSE, SHELLNEW_CONSOLIDATION_MUTEX)),
    _hEvent(CreateEvent(NULL, FALSE, FALSE, SHELLNEW_CONSOLIDATION_EVENT))
{
    DllAddRef();
}

CNewMenuConsolidator::~CNewMenuConsolidator()
{
    if (_hMutex)
    {
        CloseHandle(_hMutex);
    }
    if (_hEvent)
    {
        CloseHandle(_hEvent);
    }
    DllRelease();
}

//
// Instance generator.
//
HRESULT CNewMenuConsolidator::CreateInstance(REFIID riid, void **ppv)
{
    HRESULT hr = E_OUTOFMEMORY;
    CNewMenuConsolidator *pnmc = new CNewMenuConsolidator();
    if (pnmc)
    {
        if (!pnmc->_hMutex || !pnmc->_hEvent)
        {
            hr = E_FAIL;
        }
        else
        {
            hr = pnmc->QueryInterface(riid, ppv);
        }
        pnmc->Release();
    }
    return hr;
}



const GUID CNewMenuConsolidator::_taskid = 
    { 0xf87a1f28, 0xc7f, 0x11d2, { 0xbe, 0x1d, 0x0, 0xa0, 0xc9, 0xa8, 0x3d, 0xa1 } };


#define REGSTR_SESSION_SHELLNEW STRREG_DISCARDABLE STRREG_POSTSETUP TEXT("\\ShellNew")
#define REGVAL_SESSION_SHELLNEW_TIMESTAMP TEXT("~reserved~")
#define REGVAL_SESSION_SHELLNEW_LANG TEXT("Language")

#define SHELLNEW_CACHE_CURRENTVERSION  MAKELONG(1, 1)
             
//  Constructs a current New submenu cache stamp.
void CNewMenu_MakeCacheStamp(SHELLNEW_CACHE_STAMP* pStamp)
{
    pStamp->cbStruct = sizeof(*pStamp);
    pStamp->ver = SHELLNEW_CACHE_CURRENTVERSION;
    GetLocalTime(&pStamp->lastupdate);
}

//   Determines whether the New submenu cache needs to be rebuilt.
BOOL CNewMenu_ShouldUpdateCache(SHELLNEW_CACHE_STAMP* pStamp)
{
    //  Correct version?
    return !(sizeof(*pStamp) == pStamp->cbStruct &&
              SHELLNEW_CACHE_CURRENTVERSION == pStamp->ver);
}

//  Gathers up shellnew entries from HKCR into a distinct registry location 
//  for faster enumeration of the New submenu items.
//
//  We'll do a first time cache initialization only if we have to before showing
//  the menu, but will always rebuild the cache following display of the menu.
HRESULT CNewMenu::ConsolidateMenuItems(BOOL bForce)
{
    HKEY          hkeyShellNew = NULL;
    BOOL          bUpdate = TRUE;   // unless we discover otherwise
    HRESULT       hr = S_OK;

    // make sure that a worker thread isnt currently slamming the registry info we're inspecting.
    // if we timeout, then do nothing, since the worker thread is already working on it.
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hMutex, 0))
    {
        //  If we're not being told to unconditionally update the cache and
        //  we validate that we've already established one, then we get out of doing any
        //  work.
        if (!bForce &&
            ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW,
                                           0, KEY_ALL_ACCESS, &hkeyShellNew))
        {
            SHELLNEW_CACHE_STAMP stamp;
            ULONG cbVal = sizeof(stamp);
            if (ERROR_SUCCESS == SHQueryValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_TIMESTAMP, NULL,
                                                  NULL, (LPBYTE)&stamp, &cbVal) &&
                sizeof(stamp) == cbVal)
            {
                bUpdate = CNewMenu_ShouldUpdateCache(&stamp);
            }

            LCID lcid;
            ULONG cblcid = sizeof(lcid);

            if (!bUpdate &&
                ERROR_SUCCESS == SHQueryValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_LANG, NULL,
                                                  NULL, (LPBYTE)&lcid, &cblcid) &&
                sizeof(lcid) == cblcid)
            {
                bUpdate = (GetUserDefaultUILanguage() != lcid); // if the languages are different, then update
            }
            RegCloseKey(hkeyShellNew);
        }

        // end synchronize
        ReleaseMutex(_hMutex);
    
        if (bUpdate)
        {
            IShellTaskScheduler* pScheduler;
            hr = CoCreateInstance(CLSID_SharedTaskScheduler, NULL, CLSCTX_INPROC, 
                                                IID_PPV_ARG(IShellTaskScheduler, &pScheduler));
            if (SUCCEEDED(hr))
            {
                IRunnableTask *pTask;
                hr = CNewMenuConsolidator::CreateInstance(IID_PPV_ARG(IRunnableTask, &pTask));
                if (SUCCEEDED(hr))
                {
                    // the background task will set _hEvent for us when it's done.
                    hr = pScheduler->AddTask(pTask, CNewMenuConsolidator::_taskid, NULL, ITSAT_DEFAULT_PRIORITY);
                    pTask->Release();
                }
                pScheduler->Release();
            }
        }

        if (!bUpdate || FAILED(hr))
        {
            // if the scheduler wont activate the event, we do it ourselves.
            SetEvent(_hEvent);
        }
    }
    
    return hr;
}

//  Consolidation worker.
STDMETHODIMP CNewMenuConsolidator::RunInitRT()
{
    ULONG dwErr = ERROR_SUCCESS;

    // the possible owners of the mutex are
    // - nobody, we'll own it
    // - other worker threads like this one
    // - the guy who checks to see if the cached info is in the registry.

    // if there's another worker thread which owns this mutex, then bail, since that one
    // will do all the work we're going to do.
    // if the guy who checks the cached info has it, then bail, since it'll spawn
    // another one of these soon enough.
    // so use a 0 timeout.
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hMutex, 0))
    {
        HKEY  hkeyShellNew = NULL;
        TCHAR szExt[MAX_PATH];
        ULONG dwDisposition;
        //  Delete the existing cache; we'll build it from scratch each time.
        while (ERROR_SUCCESS == (dwErr = RegCreateKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW,
                                                         0, NULL, 0, KEY_ALL_ACCESS, NULL, 
                                                         &hkeyShellNew, &dwDisposition)) &&
                REG_CREATED_NEW_KEY != dwDisposition)
        {
            //  Key already existed, so delete it, and loop to reopen.
            RegCloseKey(hkeyShellNew);
            SHDeleteKey(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW);
            hkeyShellNew = NULL;
        }

        if (ERROR_SUCCESS == dwErr)
        {
            // Enumerate each subkey of HKCR, looking for New menu items.
            for (int i = 0; RegEnumKey(HKEY_CLASSES_ROOT, i, szExt, ARRAYSIZE(szExt)) == ERROR_SUCCESS; i++)
            {
                TCHAR szClass[CCH_KEYMAX];
                TCHAR szDisplayName[CCH_KEYMAX];
                LONG  cbVal = sizeof(szClass);

                // find .ext that have proper class descriptions with them.
                if ((szExt[0] == TEXT('.')) &&
                    SHRegQueryValue(HKEY_CLASSES_ROOT, szExt, szClass, &cbVal) == ERROR_SUCCESS 
                    && (cbVal > 0) 
                    && GetClassDisplayName(szClass, szDisplayName, ARRAYSIZE(szDisplayName)))
                {
                    NEWOBJECTINFO noi = {0};
                    HKEY          hkeyIterate = NULL;
                    int           iIndex = 0;

                    lstrcpyn(noi.szExt, szExt, ARRAYSIZE(noi.szExt));
                    lstrcpyn(noi.szClass, szClass, ARRAYSIZE(noi.szClass));
                    lstrcpyn(noi.szMenuText, szDisplayName, ARRAYSIZE(noi.szMenuText));
                    noi.iImage = -1;

                    //  Retrieve all additional information for the key.
                    while (GetNewFileInfoForExtension(&noi, NULL, &hkeyIterate, &iIndex)) 
                    {
                        //  Stick it in the cache.
                        RegSetValueEx(hkeyShellNew, noi.szMenuText, NULL, REG_BINARY, 
                                       (LPBYTE)&noi, sizeof(noi));
                    }
                }
            }

            //  Stamp the cache.
            SHELLNEW_CACHE_STAMP stamp;
            CNewMenu_MakeCacheStamp(&stamp);
            RegSetValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_TIMESTAMP,
                           NULL, REG_BINARY, (LPBYTE)&stamp, sizeof(stamp));
            LCID lcid = GetUserDefaultUILanguage();

            RegSetValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_LANG,
                           NULL, REG_DWORD, (LPBYTE)&lcid, sizeof(lcid));
        }
        if (NULL != hkeyShellNew)
            RegCloseKey(hkeyShellNew);

        // signal the event so InitMenuPopup can proceed.
        SetEvent(_hEvent);
        ReleaseMutex(_hMutex);
    }

    return HRESULT_FROM_WIN32(dwErr);
}

BOOL CNewMenu::_InsertNewMenuItem(HMENU hmenu, UINT idCmd, NEWOBJECTINFO *pnoiClone)
{
    if (pnoiClone->szMenuText[0])
    {
        NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)LocalAlloc(LPTR, sizeof(NEWOBJECTINFO));
        if (pnoi)
        {
            *pnoi = *pnoiClone;

            pnoi->msaa.dwMSAASignature = MSAA_MENU_SIG;
            if (StrChr(pnoi->szMenuText, TEXT('&')) == NULL)
            {
                pnoi->chPrefix = TEXT('&');
                pnoi->msaa.pszWText = &pnoi->chPrefix;
            }
            else
            {
                pnoi->msaa.pszWText = pnoi->szMenuText;
            }
            pnoi->msaa.cchWText = lstrlen(pnoi->msaa.pszWText);

            MENUITEMINFO mii  = {0};
            mii.cbSize        = sizeof(mii);
            mii.fMask         = MIIM_TYPE | MIIM_DATA | MIIM_ID;
            mii.fType         = MFT_OWNERDRAW;
            mii.fState        = MFS_ENABLED;
            mii.wID           = idCmd;
            mii.dwItemData    = (DWORD_PTR)pnoi;
            mii.dwTypeData    = (LPTSTR)pnoi;

            if (-1 != DPA_AppendPtr(_hdpaMenuInfo, pnoi))
            {
                InsertMenuItem(hmenu, -1, TRUE, &mii);
            }
            else
            {
                LocalFree(pnoi);
                return FALSE;
            }
        }
    }

    return TRUE;
}

//  WM_INITMENUPOPUP handler
BOOL CNewMenu::InitMenuPopup(HMENU hmenu)
{
    UINT iStart = 3;
    NEWOBJECTINFO noi = {0};
    if (GetItemData(hmenu, iStart))  //Position 0 is New Folder, 1 shortcut, 2 sep 
        return FALSE;                //already initialized. No need to do anything
    
    _hdpaMenuInfo = DPA_Create(4);
    if (!_hdpaMenuInfo)
        return FALSE;

    //Remove the place holder.
    DeleteMenu(hmenu,0,MF_BYPOSITION);
    
    //Insert New Folder menu item
    LoadString(g_hinst, IDS_NEWFOLDER, noi.szMenuText, ARRAYSIZE(noi.szMenuText));
    noi.dwFlags = NEWTYPE_FOLDER;
    noi.iImage = Shell_GetCachedImageIndex(TEXT("shell32.dll"), II_FOLDER, 0); //Shange to indicate Folder

    _InsertNewMenuItem(hmenu, _idCmdFirst-NEWITEM_MAX+NEWITEM_FOLDER, &noi);
    
    TCHAR szTemp[MAX_PATH+80];       // Add some slop incase we overflow
    //See if the pidl is folder shortcut and if so get the target path.
    SHGetTargetFolderPath(_pidlFolder, szTemp, ARRAYSIZE(szTemp));
    if (IsLFNDrive(szTemp)) //for short filename servers we don't support anything but new folder
    {
        //Insert New Shortcut menu item
        LoadString(g_hinst, IDS_NEWLINK, noi.szMenuText, ARRAYSIZE(noi.szMenuText));
        noi.iImage = Shell_GetCachedImageIndex(TEXT("shell32.dll"), II_LINK, 0); //Shange to indicate Link
        noi.dwFlags = NEWTYPE_LINK;

        _InsertNewMenuItem(hmenu, _idCmdFirst-NEWITEM_MAX+NEWITEM_LINK, &noi);
    
        //Insert menu item separator
        AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);

        // This may take a while, so put up the hourglass
        DECLAREWAITCURSOR;
        SetWaitCursor();

        //  Retrieve extension menu items from cache:

        //  begin synchronize.
        //
        if (WAIT_OBJECT_0 == WaitForSingleObject(_hEvent, INFINITE))
        {
            HKEY hkeyShellNew;
            if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW, 
                                               0, KEY_ALL_ACCESS, &hkeyShellNew))
            {
                TCHAR szVal[CCH_KEYMAX];
                ULONG cbVal = ARRAYSIZE(szVal);
                ULONG cbData = sizeof(noi);
                ULONG dwType = REG_BINARY;
            
                for (int i = 0; 
                     ERROR_SUCCESS == RegEnumValue(hkeyShellNew, i, szVal, &cbVal, 0,
                                                    &dwType, (LPBYTE)&noi, &cbData);
                     i++)
                {
                    if (lstrcmp(szVal, REGVAL_SESSION_SHELLNEW_TIMESTAMP) != 0 &&
                        sizeof(noi) == cbData && 
                        REG_BINARY == dwType)
                    {
                        SHFILEINFO sfi;
                        _himlSystemImageList = (HIMAGELIST)SHGetFileInfo(noi.szExt, FILE_ATTRIBUTE_NORMAL,
                                                                         &sfi, sizeof(SHFILEINFO), 
                                                                         SHGFI_USEFILEATTRIBUTES | 
                                                                         SHGFI_SYSICONINDEX | 
                                                                         SHGFI_SMALLICON);
                        if (_himlSystemImageList)
                        {
                            //pnoi->himlSmallIcons = sfi.hIcon;
                            noi.iImage = sfi.iIcon;
                        }
                        else
                        {
                            //pnoi->himlSmallIcons = INVALID_HANDLE_VALUE;
                            noi.iImage = -1;
                        }
                    
                        _InsertNewMenuItem(hmenu, _idCmdFirst, &noi);
                    }
                    cbVal = ARRAYSIZE(szVal);
                    cbData = sizeof(noi);
                    dwType = REG_BINARY;
                }

                RegCloseKey(hkeyShellNew);
            }

            //  consolidate menu items following display.
            ConsolidateMenuItems(TRUE);
        }
        ResetWaitCursor();
    }

    return TRUE;
}

NEWOBJECTINFO *CNewMenu::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 (NEWOBJECTINFO *)mii.dwItemData;
    
    return NULL;
}

LPTSTR ProcessArgs(LPTSTR szArgs,...)
{
    LPTSTR szRet;
    va_list ArgList;
    va_start(ArgList,szArgs);
    if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
        szArgs, 0, 0, (LPTSTR)&szRet, 0, &ArgList))
    {
        return NULL;
    }
    va_end(ArgList);
    return szRet;
}


HRESULT CNewMenu::RunCommand(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszRun)
{
    SHELLEXECUTEINFO ei = { 0 };
    TCHAR szCommand[MAX_PATH];
    TCHAR szRun[MAX_PATH];
    
    SHExpandEnvironmentStrings(pszRun, szCommand, ARRAYSIZE(szCommand));
    lstrcpyn(szRun, szCommand, ARRAYSIZE(szRun));
    PathRemoveArgs(szCommand);
    
    //
    //  Mondo hackitude-o-rama.
    //
    //  Win95, IE3, SDK:  %1 - filename
    //
    //  IE4:              %1 - hwnd, %2 = filename
    //
    //  So IE4 broken Win95 compat and broke compat with the SDK.
    //  For IE5 we restore compat with Win95 and the SDK, while
    //  still generating an IE4-style command if we detect that the
    //  registry key owner tested with IE4 rather than following the
    //  instructions in the SDK.
    //
    //  The algorithm is like this:
    //
    //  If we see a "%2", then use %1 - hwnd, %2 - filename
    //  Otherwise, use             %1 - filename, %2 - hwnd
    //

    LPTSTR pszArgs = PathGetArgs(szRun);
    LPTSTR ptszPercent2 = StrStr(pszArgs, TEXT("%2"));
    if (ptszPercent2 && ptszPercent2[2] != TEXT('!'))
    {
        // App wants %1 = hwnd and %2 = filename
        pszArgs = ProcessArgs(pszArgs, (DWORD_PTR)hwnd, pszPath);
    }
    else
    {
        // App wants %2 = hwnd and %1 = filename
        pszArgs = ProcessArgs(pszArgs, pszPath, (DWORD_PTR)hwnd);
    }

    HRESULT hr;
    
    if (pszArgs) 
    {
        HMONITOR hMon = MonitorFromPoint(_ptNewItem, MONITOR_DEFAULTTONEAREST);
        if (hMon)
        {
            ei.fMask |= SEE_MASK_HMONITOR;
            ei.hMonitor = (HANDLE)hMon;
        }
        ei.hwnd            = hwnd;
        ei.lpFile          = szCommand;
        ei.lpParameters    = pszArgs;
        ei.nShow           = SW_SHOWNORMAL;
        ei.cbSize          = sizeof(ei);

        if (ShellExecuteEx(&ei)) 
            hr = S_FALSE;   // Return S_FALSE because ShellExecuteEx is not atomic
        else
            hr = E_FAIL;
        
        LocalFree(pszArgs);
    } 
    else
        hr = E_OUTOFMEMORY;
    
    return hr;
}


HRESULT CNewMenu::CopyTemplate(IStream *pStream, NEWFILEINFO *pnfi)
{
    TCHAR szSrcFolder[MAX_PATH], szSrc[MAX_PATH];

    szSrc[0] = 0;

    // failure here is OK, we will try CSIDL_COMMON_TEMPLATES too.
    if (SHGetSpecialFolderPath(NULL, szSrcFolder, CSIDL_TEMPLATES, FALSE))
    {
        PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData);
        if (!PathFileExistsAndAttributes(szSrc, NULL))
            szSrc[0] = 0;
    }

    if (szSrc[0] == 0)
    {
        if (SHGetSpecialFolderPath(NULL, szSrcFolder, CSIDL_COMMON_TEMPLATES, FALSE))
        {
            PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData);
            if (!PathFileExistsAndAttributes(szSrc, NULL))
                szSrc[0] = 0;
        }
    }

    if (szSrc[0] == 0)
    {
        // work around CSIDL_TEMPLATES not being setup right or
        // templates that are left in the old %windir%\shellnew location

        GetWindowsDirectory(szSrcFolder, ARRAYSIZE(szSrcFolder));
        PathAppend(szSrcFolder, TEXT("ShellNew"));

        // note: if the file spec is fully qualified szSrcFolder is ignored
        PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData);
    }

    //
    //  we just allow a null file to be created when the copy fails.
    //  this is for appcompat with office97.  they fail to copy winword8.doc
    //  anywhere on the system.  on win2k we succeed anyway with an empty file.
    //
    return SUCCEEDED(StgCopyFileToStream(szSrc, pStream)) ? S_OK : S_FALSE;
}