#include "precomp.h"
#pragma hdrstop

// BUGBUG: from shell32\prop.cpp
STDAPI_(BOOL) ParseSCIDString(LPCTSTR pszString, SHCOLUMNID *pscid, UINT *pidRes);

#define TF_SHELLAUTO TF_CUSTOM1

#define DEFINE_FLOAT_STUFF  // Do this because DATE is being used below

class CFolderItemVerbs;
class CEnumFolderItemVerbs;
class CFolderItemVerb;

class CFolderItemVerbs : public FolderItemVerbs,
                         public CObjectSafety,
                         protected CImpIDispatch,
                         protected CObjectWithSite
{
friend class CEnumFolderItemVerbs;
friend class CFolderItemVerb;

public:
    CFolderItemVerbs(IContextMenu *pcm);
    BOOL Init(void);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IDispatch
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo)
        { return CImpIDispatch::GetTypeInfoCount(pctinfo); }
    STDMETHODIMP GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo **pptinfo)
        { return CImpIDispatch::GetTypeInfo(itinfo, lcid, pptinfo); }
    STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID * rgdispid)
        { return CImpIDispatch::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid); }
    STDMETHODIMP Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, UINT * puArgErr)
        { return CImpIDispatch::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); }

    // FolderItemVerbs
    STDMETHODIMP        get_Application(IDispatch **ppid);
    STDMETHODIMP        get_Parent(IDispatch **ppid);
    STDMETHODIMP        get_Count(long *plCount);
    STDMETHODIMP        Item(VARIANT, FolderItemVerb**);
    STDMETHODIMP        _NewEnum(IUnknown **);

private:
    ~CFolderItemVerbs(void);
    HRESULT _SecurityCheck();
    LONG _cRef;
    HMENU _hmenu;
    IContextMenu *_pcm;
};

class CEnumFolderItemVerbs : public IEnumVARIANT,
                             public CObjectSafety
{
public:
    CEnumFolderItemVerbs(CFolderItemVerbs *psdfiv);
    BOOL Init();

    // IUnknown
    STDMETHODIMP         QueryInterface(REFIID, void **);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IEnumFORMATETC
    STDMETHODIMP Next(ULONG, VARIANT *, ULONG *);
    STDMETHODIMP Skip(ULONG);
    STDMETHODIMP Reset(void);
    STDMETHODIMP Clone(IEnumVARIANT **);

private:
    ~CEnumFolderItemVerbs();
    LONG _cRef;
    int _iCur;
    CFolderItemVerbs *_psdfiv;
};

HRESULT CFolderItemVerb_Create(CFolderItemVerbs *psdfivs, UINT id, FolderItemVerb **pfiv);

class CFolderItemVerb : 
    public FolderItemVerb,
    public CObjectSafety,
    protected CImpIDispatch
{

friend class CFolderItemVerbs;

public:
    CFolderItemVerb(CFolderItemVerbs *psdfivs, UINT id);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IDispatch
    STDMETHODIMP GetTypeInfoCount(UINT * pctinfo)
        { return CImpIDispatch::GetTypeInfoCount(pctinfo); }
    STDMETHODIMP GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo **pptinfo)
        { return CImpIDispatch::GetTypeInfo(itinfo, lcid, pptinfo); }
    STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID * rgdispid)
        { return CImpIDispatch::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid); }
    STDMETHODIMP Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, UINT * puArgErr)
        { return CImpIDispatch::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); }

    // FolderItemVerbs
    STDMETHODIMP get_Application(IDispatch **ppid);
    STDMETHODIMP get_Parent(IDispatch **ppid);
    STDMETHODIMP get_Name(BSTR *pbs);
    STDMETHODIMP DoIt();

private:
    ~CFolderItemVerb(void);

    LONG _cRef;
    CFolderItemVerbs *_psdfivs;
    UINT _id;
};


// in:
//      psdf    folder that contains pidl
//      pidl    pidl retlative to psdf (the item in this folder)

HRESULT CFolderItem_Create(CFolder *psdf, LPCITEMIDLIST pidl, FolderItem **ppid)
{
    *ppid = NULL;

    HRESULT hr = E_OUTOFMEMORY;
    CFolderItem* psdfi = new CFolderItem();
    if (psdfi)
    {
        hr = psdfi->Init(psdf, pidl);
        if (SUCCEEDED(hr))
            hr = psdfi->QueryInterface(IID_PPV_ARG(FolderItem, ppid));
        psdfi->Release();
    }
    return hr;
}

STDAPI CFolderItem_CreateInstance(IUnknown *punk, REFIID riid, void **ppv)
{
    HRESULT hr = E_OUTOFMEMORY;
    *ppv = NULL;

    CFolderItem *pfi = new CFolderItem();
    if (pfi)
    {
        hr = pfi->QueryInterface(riid, ppv);
        pfi->Release();
    }

    return hr;
}

// in:
//      pidl    fully qualified pidl
//      hwnd    hacks

HRESULT CFolderItem_CreateFromIDList(HWND hwnd, LPCITEMIDLIST pidl, FolderItem **ppfi)
{
    LPITEMIDLIST pidlParent;
    HRESULT hr = SHILClone(pidl, &pidlParent);
    if (SUCCEEDED(hr))
    {
        ILRemoveLastID(pidlParent);

        CFolder *psdf;
        hr = CFolder_Create2(hwnd, pidlParent, NULL, &psdf);
        if (SUCCEEDED(hr))
        {
            hr = CFolderItem_Create(psdf, ILFindLastID(pidl), ppfi);
            psdf->Release();
        }
        ILFree(pidlParent);
    }
    return hr;
}

CFolderItem::CFolderItem() :
    _cRef(1), _psdf(NULL), _pidl(NULL),
    CImpIDispatch(SDSPATCH_TYPELIB, IID_FolderItem2)
{
    DllAddRef();
}


CFolderItem::~CFolderItem(void)
{
    if (_pidl)
        ILFree(_pidl);
    if (_psdf)
        _psdf->Release();
    DllRelease();
}

HRESULT GetItemFolder(CFolder *psdfRoot, LPCITEMIDLIST pidl, CFolder **ppsdf)
{
    HRESULT hr = S_OK;
    
    ASSERT(psdfRoot);
    
    if (ILFindLastID(pidl) != pidl)
    {
        LPITEMIDLIST pidlIn = ILClone(pidl);

        hr = E_OUTOFMEMORY;
        if (pidlIn && ILRemoveLastID(pidlIn))
        {
            LPITEMIDLIST pidlParent; 
            LPITEMIDLIST pidlFolder = NULL;
            
            hr = psdfRoot->GetCurFolder(&pidlFolder);
            if (SUCCEEDED(hr))
            {
                pidlParent = ILCombine(pidlFolder, pidlIn);
                if (pidlParent)
                {
                    hr = CFolder_Create2(NULL, pidlParent, NULL, ppsdf);
                    ILFree(pidlParent);
                }
                ILFree(pidlFolder);
            }
        }
        ILFree(pidlIn);// ilfree does null check
    }
    else
    {
        *ppsdf = psdfRoot;
        psdfRoot->AddRef();
    }
    return hr;
}

HRESULT CFolderItem::Init(CFolder *psdf, LPCITEMIDLIST pidl)
{    
    HRESULT hr = GetItemFolder(psdf, pidl, &_psdf);

    if (SUCCEEDED(hr))    
    {
        hr = SHILClone(ILFindLastID(pidl), &_pidl);
    }
    return hr;
}

STDMETHODIMP CFolderItem::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = {
        QITABENT(CFolderItem, FolderItem2),
        QITABENTMULTI(CFolderItem, FolderItem, FolderItem2),
        QITABENTMULTI(CFolderItem, IDispatch, FolderItem2),
        QITABENTMULTI(CFolderItem, IPersist, IPersistFolder2),
        QITABENTMULTI(CFolderItem, IPersistFolder, IPersistFolder2),
        QITABENT(CFolderItem, IPersistFolder2),
        QITABENT(CFolderItem, IObjectSafety),
        QITABENT(CFolderItem, IParentAndItem),
        { 0 },
    };
    HRESULT hr = QISearch(this, qit, riid, ppv);
    if (FAILED(hr) && IsEqualGUID(CLSID_ShellFolderItem, riid))
    {
        *ppv = (CFolderItem *)this; // unrefed
        hr = S_OK;
    }
    return hr;
}

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

STDMETHODIMP_(ULONG) CFolderItem::Release(void)
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}


//The FolderItem implementation
STDMETHODIMP CFolderItem::get_Application(IDispatch **ppid)
{
    // Let the folder object handle security and reference counting of site, etc
    return _psdf->get_Application(ppid);
}

STDMETHODIMP CFolderItem::get_Parent(IDispatch **ppid)
{
    // Assume that the Folder object is the parent of this object...
    HRESULT hr = _psdf->QueryInterface(IID_PPV_ARG(IDispatch, ppid));
    if (SUCCEEDED(hr) && _dwSafetyOptions)
        hr = MakeSafeForScripting((IUnknown**)ppid);
    return hr;
}

HRESULT CFolderItem::_ItemName(UINT dwFlags, BSTR *pbs)
{
    HRESULT hr;
    STRRET strret;
    if (SUCCEEDED(_psdf->_psf->GetDisplayNameOf(_pidl, dwFlags, &strret)))
    {
        hr = StrRetToBSTR(&strret, _pidl, pbs);
    }
    else
    {
        *pbs = SysAllocString(L"");
        hr = (*pbs) ? S_FALSE : E_OUTOFMEMORY;
    }
    return hr;
}

STDMETHODIMP CFolderItem::get_Name(BSTR *pbs)
{
    *pbs = NULL;
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        hr = _ItemName(SHGDN_INFOLDER, pbs);
    }
    return hr;
}

STDMETHODIMP CFolderItem::put_Name(BSTR bs)
{
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        LPITEMIDLIST pidlOut;
        hr = _psdf->_psf->SetNameOf(_psdf->_hwnd, _pidl, bs, SHGDN_INFOLDER, &pidlOut);
        if (SUCCEEDED(hr))
        {
            ILFree(_pidl);
            _pidl = pidlOut;
        }
    }
    return hr;
}

// BUGBUG: this should validate that the path is a file system
// object (SFGAO_FILESYSTEM)

STDMETHODIMP CFolderItem::get_Path(BSTR *pbs)
{
    *pbs = NULL;
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        hr = _ItemName(SHGDN_FORPARSING, pbs);
    }
    return hr;
}

STDMETHODIMP CFolderItem::get_GetLink(IDispatch **ppid)
{
    *ppid = NULL;
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        hr = CShortcut_CreateIDispatch(_psdf->_hwnd, _psdf->_psf, _pidl,  ppid);
        if (SUCCEEDED(hr) && _dwSafetyOptions)
        {
            hr = MakeSafeForScripting((IUnknown**)ppid);
        }
    }
    return hr;
}

// BUGBUG:: this should return Folder **...
STDMETHODIMP CFolderItem::get_GetFolder(IDispatch **ppid /* Folder **ppf */)
{
    *ppid = NULL;

    // If in Safe mode we fail this one...
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        LPITEMIDLIST pidl;
        hr = SHILCombine(_psdf->_pidl, _pidl, &pidl);
        if (SUCCEEDED(hr))
        {
            hr = CFolder_Create(NULL, pidl, NULL, IID_PPV_ARG(IDispatch, ppid));
            if (SUCCEEDED(hr))
            {
                if (_dwSafetyOptions)
                {
                    // note, this is created without a site so script safety checks will fail
                    hr = MakeSafeForScripting((IUnknown**)ppid);
                }
            }
            ILFree(pidl);
        }
    }
    return hr;
}


HRESULT CFolderItem::_CheckAttribute(ULONG ulAttrIn, VARIANT_BOOL * pb)
{
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        ULONG ulAttr = ulAttrIn;
        hr = _psdf->_psf->GetAttributesOf(1, (LPCITEMIDLIST*)&_pidl, &ulAttr);
        *pb = (SUCCEEDED(hr) && (ulAttr & ulAttrIn)) ? VARIANT_TRUE : VARIANT_FALSE;
    }
    return hr;
}

HRESULT CFolderItem::_GetUIObjectOf(REFIID riid, void **ppv)
{
    return _psdf->_psf->GetUIObjectOf(_psdf->_hwnd, 1, (LPCITEMIDLIST*)&_pidl, riid, NULL, ppv);
}


HRESULT CFolderItem::_SecurityCheck()
{
    if (!_dwSafetyOptions)
        return S_OK;
    
    return _psdf->_SecurityCheck();
}

// allow friend classes to get the IDList for a CFolderItem automation object

LPCITEMIDLIST CFolderItem::_GetIDListFromVariant(const VARIANT *pv)
{
    LPCITEMIDLIST pidl = NULL;
    if (pv)
    {
        VARIANT v;

        if (pv->vt == (VT_BYREF | VT_VARIANT) && pv->pvarVal)
            v = *pv->pvarVal;
        else
            v = *pv;

        switch (v.vt)
        {
        case VT_DISPATCH | VT_BYREF:
            if (v.ppdispVal == NULL)
                break;

            v.pdispVal = *v.ppdispVal;

            // fall through...

        case VT_DISPATCH:
            CFolderItem *pfi;
            if (v.pdispVal && SUCCEEDED(v.pdispVal->QueryInterface(CLSID_ShellFolderItem, (void **)&pfi)))
            {
                pidl = pfi->_pidl; // alias
            }
            break;
        }
    }
    return pidl;
}


STDMETHODIMP CFolderItem::get_IsLink(VARIANT_BOOL * pb)
{
    return _CheckAttribute(SFGAO_LINK, pb);
}

STDMETHODIMP CFolderItem::get_IsFolder(VARIANT_BOOL * pb)
{
    return _CheckAttribute(SFGAO_FOLDER, pb);
}

STDMETHODIMP CFolderItem::get_IsFileSystem(VARIANT_BOOL * pb)
{
    return _CheckAttribute(SFGAO_FILESYSTEM, pb);
}

STDMETHODIMP CFolderItem::get_IsBrowsable(VARIANT_BOOL * pb)
{
    return _CheckAttribute(SFGAO_BROWSABLE, pb);
}

STDMETHODIMP CFolderItem::get_ModifyDate(DATE *pdt)
{
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        WIN32_FIND_DATA finddata;
        if (SUCCEEDED(SHGetDataFromIDList(_psdf->_psf, _pidl, SHGDFIL_FINDDATA, &finddata, sizeof(finddata))))
        {
            WORD wDosDate, wDosTime;
            FILETIME filetime;
            FileTimeToLocalFileTime(&finddata.ftLastWriteTime, &filetime);
            FileTimeToDosDateTime(&filetime, &wDosDate, &wDosTime);
            DosDateTimeToVariantTime(wDosDate, wDosTime, pdt);
        }
        hr = S_OK;
    }
    return hr;
}

STDMETHODIMP CFolderItem::put_ModifyDate(DATE dt)
{
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        hr = S_FALSE;
        SYSTEMTIME st;
        FILETIME ftLocal, ft;
        BSTR bstrPath;
    
        if (SUCCEEDED(VariantTimeToSystemTime(dt, &st)) && SystemTimeToFileTime(&st, &ftLocal)
                && LocalFileTimeToFileTime(&ftLocal, &ft) && SUCCEEDED(get_Path(&bstrPath)))
        {
            TCHAR szPath[MAX_PATH];
        
            SHUnicodeToTChar(bstrPath, szPath, ARRAYSIZE(szPath));
            SysFreeString(bstrPath);
            HANDLE hFile = CreateFile(szPath, GENERIC_READ | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ,
                    NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OPEN_NO_RECALL, NULL);
            if (hFile != INVALID_HANDLE_VALUE)
            {
                if (SetFileTime(hFile, NULL, NULL, &ft))
                {
                    hr = S_OK;
                }
                CloseHandle(hFile);
            }
        }
    }
    return hr;
}

// BUGBUG:: Should see about getting larger numbers through
STDMETHODIMP CFolderItem::get_Size(LONG *pul)
{
    HRESULT hr =_SecurityCheck();
    if (SUCCEEDED(hr))
    {
        WIN32_FIND_DATA finddata;
        if (SUCCEEDED(SHGetDataFromIDList(_psdf->_psf, _pidl, SHGDFIL_FINDDATA, &finddata, sizeof(finddata))))
        {
            *pul = (LONG)finddata.nFileSizeLow;
        }
        else
        {
            *pul = 0L;
        }
        hr = S_OK; // Scripts don't like error return values
    }
    return hr;
}

STDMETHODIMP CFolderItem::get_Type(BSTR *pbs)
{
    *pbs = NULL;
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        VARIANT var;
        var.vt = VT_EMPTY;
        if (SUCCEEDED(ExtendedProperty(L"Type", &var)) && (var.vt == VT_BSTR))
        {
            *pbs = SysAllocString(var.bstrVal);
        }
        else
        {
            *pbs = SysAllocString(L"");
        }
        VariantClear(&var);
        hr = *pbs ? S_OK : E_OUTOFMEMORY;
    }
    return hr;
}


STDMETHODIMP CFolderItem::ExtendedProperty(BSTR bstrPropName, VARIANT *pvRet)
{
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        hr = E_FAIL;

        VariantInit(pvRet);

        // currently, MCNL is 80, guidstr is 39, and 6 is the width of an int
        if (StrCmpIW(bstrPropName, L"infotip") == 0)
        {
            // They want the info tip for the item.
            if (_pidl && _psdf)
            {
                TCHAR szInfo[INFOTIPSIZE];
                GetInfoTipHelp(_psdf->_psf, _pidl, szInfo, ARRAYSIZE(szInfo));
                hr = InitVariantFromStr(pvRet, szInfo);
            }
        }
        else if (_psdf->_psf2)
        {
            SHCOLUMNID scid;
            TCHAR szTemp[128];

            SHUnicodeToTChar(bstrPropName, szTemp, ARRAYSIZE(szTemp));

            if (ParseSCIDString(szTemp, &scid, NULL))
            {
                // Note that GetDetailsEx expects an absolute pidl
                hr = _psdf->_psf2->GetDetailsEx(_pidl, &scid, pvRet);
            }
        }
        hr = FAILED(hr) ? S_FALSE : hr;   // Java scripts barf on error values
    }
    return hr;
}


STDMETHODIMP CFolderItem::Verbs(FolderItemVerbs **ppfic)
{
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        hr = S_FALSE;
    
        IContextMenu *pcm;
        if (SUCCEEDED(_GetUIObjectOf(IID_PPV_ARG(IContextMenu, &pcm))))
        {
            hr = CFolderItemVerbs_Create(pcm, ppfic);
            if (SUCCEEDED(hr) && _dwSafetyOptions)
            {
                hr = MakeSafeForScripting((IUnknown**)ppfic);
            
                if (SUCCEEDED(hr))
                {
                    // Set the folder's site to FolderItemVerbs
                    IUnknown_SetSite(*ppfic, _psdf->_punkSite);
                }
            }
            pcm->Release();
        }
    }
    return hr;
}

STDMETHODIMP CFolderItem::InvokeVerbEx(VARIANT vVerb, VARIANT vArgs)
{
    // security check handled by _psdf
    return _psdf->InvokeVerbHelper(vVerb, vArgs, (LPCITEMIDLIST *)&_pidl, 1, _dwSafetyOptions);
}

STDMETHODIMP CFolderItem::InvokeVerb(VARIANT vVerb)
{
    VARIANT vtEmpty = {VT_EMPTY};
    return InvokeVerbEx(vVerb, vtEmpty);
}

STDMETHODIMP CFolderItem::GetClassID(CLSID *pClassID)
{
    *pClassID = CLSID_ShellFolderItem;
    return E_NOTIMPL;
}

// IPersistFolder
// note, this duplicates some of CFolderItem::Init() functionality

STDMETHODIMP CFolderItem::Initialize(LPCITEMIDLIST pidl)
{
    LPITEMIDLIST pidlParent;
    HRESULT hr = SHILClone(pidl, &pidlParent);
    if (SUCCEEDED(hr))
    {
        ASSERT(_pidl == NULL);
        hr = SHILClone(ILFindLastID(pidlParent), &_pidl);
        if (SUCCEEDED(hr))
        {
            // Chop off the filename and get the folder that contains
            // this FolderItem.
            ILRemoveLastID(pidlParent);

            // BUGBUG: we should find some way to get a valid hwnd to
            // pass to CFolder_Create2 instead of NULL.
            ASSERT(_psdf == NULL);
            hr = CFolder_Create2(NULL, pidlParent, NULL, &_psdf);
        }
        ILFree(pidlParent);
    }
    return hr;
}

STDMETHODIMP CFolderItem::GetCurFolder(LPITEMIDLIST *ppidl)
{
    LPITEMIDLIST pidlFolder;
    HRESULT hr = SHGetIDListFromUnk(_psdf->_psf, &pidlFolder);
    if (S_OK == hr)
    {
        hr = SHILCombine(pidlFolder, _pidl, ppidl);
        ILFree(pidlFolder);
    }
    else
        hr = E_FAIL;
    return hr;
}

// IParentAndItem
STDMETHODIMP CFolderItem::SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psf,  LPCITEMIDLIST pidl)
{
    return E_NOTIMPL;
}

STDMETHODIMP CFolderItem::GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidl)
{
    if (ppidlParent)
    {
        *ppidlParent = _psdf->_pidl ? ILClone(_psdf->_pidl) : NULL;
    }
    
    if (ppsf)
    {
        *ppsf = NULL;
        if (_psdf && _psdf->_psf)
            _psdf->_psf->QueryInterface(IID_PPV_ARG(IShellFolder, ppsf));
    }

    if (ppidl)
        *ppidl = ILClone(_pidl);


    if ((ppidlParent && !*ppidlParent)
    ||  (ppsf && !*ppsf)
    ||  (ppidl && !*ppidl))
    {
        //  this is failure
        //  but we dont know what failed
        if (ppsf && *ppsf)
            (*ppsf)->Release();
        if (ppidlParent)
            ILFree(*ppidlParent);
        if (ppidl)
            ILFree(*ppidl);

        return E_OUTOFMEMORY;
    }
            
    return S_OK;
}

HRESULT CFolderItemVerbs_Create(IContextMenu *pcm, FolderItemVerbs ** ppid)
{
    *ppid = NULL;
    HRESULT hr = E_OUTOFMEMORY;
    CFolderItemVerbs* pfiv = new CFolderItemVerbs(pcm);
    if (pfiv)
    {
        if (pfiv->Init())
            hr = pfiv->QueryInterface(IID_PPV_ARG(FolderItemVerbs, ppid));
        pfiv->Release();
    }
    return hr;
}

CFolderItemVerbs::CFolderItemVerbs(IContextMenu *pcm) :
    _cRef(1), _hmenu(NULL),
    _pcm(pcm), CImpIDispatch(SDSPATCH_TYPELIB, IID_FolderItemVerbs)
{
    DllAddRef();
    _pcm->AddRef();
}

CFolderItemVerbs::~CFolderItemVerbs(void)
{
    DllRelease();

    if (_pcm)
        _pcm->Release();

    if (_hmenu)
        DestroyMenu(_hmenu);
}

BOOL CFolderItemVerbs::Init()
{
    TraceMsg(TF_SHELLAUTO, "CFolderItemVerbs::Init called");

    // Start of only doing default verb...
    if (_pcm)
    {
        _hmenu = CreatePopupMenu();
        if (FAILED(_pcm->QueryContextMenu(_hmenu, 0, CONTEXTMENU_IDCMD_FIRST, CONTEXTMENU_IDCMD_LAST, CMF_CANRENAME|CMF_NORMAL)))
            return FALSE;
    }
    else
        return FALSE;

    // Just for the heck of it, remove junk like sepearators from the menu...
    for (int i = GetMenuItemCount(_hmenu) - 1; i >= 0; i--)
    {
        MENUITEMINFO mii;
        TCHAR szText[80];    // should be big enough for this

        mii.cbSize = sizeof(MENUITEMINFO);
        mii.dwTypeData = szText;
        mii.fMask = MIIM_TYPE | MIIM_ID;
        mii.cch = ARRAYSIZE(szText);
        mii.fType = MFT_SEPARATOR;                // to avoid ramdom result.
        mii.dwItemData = 0;
        GetMenuItemInfo(_hmenu, i, TRUE, &mii);
        if (mii.fType & MFT_SEPARATOR)
            DeleteMenu(_hmenu, i, MF_BYPOSITION);
    }
    return TRUE;
}

STDMETHODIMP CFolderItemVerbs::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = {
        QITABENT(CFolderItemVerbs, FolderItemVerbs),
        QITABENT(CFolderItemVerbs, IObjectSafety),
        QITABENTMULTI(CFolderItemVerbs, IDispatch, FolderItemVerbs),
        QITABENT(CFolderItemVerbs, IObjectWithSite),
        { 0 },
    };
    return QISearch(this, qit, riid, ppv);
}

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

STDMETHODIMP_(ULONG) CFolderItemVerbs::Release(void)
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

STDMETHODIMP CFolderItemVerbs::get_Application(IDispatch **ppid)
{
    *ppid = NULL;
    return E_NOTIMPL;
}

STDMETHODIMP CFolderItemVerbs::get_Parent(IDispatch **ppid)
{
    *ppid = NULL;
    return E_NOTIMPL;
}


HRESULT CFolderItemVerbs::_SecurityCheck()
{
    return (!_dwSafetyOptions || (IsSafePage(_punkSite) == S_OK)) ? S_OK : E_ACCESSDENIED;
}


STDMETHODIMP CFolderItemVerbs::get_Count(long *plCount)
{
    *plCount = 0;
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        *plCount = GetMenuItemCount(_hmenu);
    }
    return hr;
}

STDMETHODIMP CFolderItemVerbs::Item(VARIANT index, FolderItemVerb **ppid)
{
    *ppid = NULL;
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        // This is sortof gross, but if we are passed a pointer to another variant, simply
        // update our copy here...
        if (index.vt == (VT_BYREF | VT_VARIANT) && index.pvarVal)
            index = *index.pvarVal;

        switch (index.vt)
        {
        case VT_ERROR:
            QueryInterface(IID_PPV_ARG(FolderItemVerb, ppid));
            break;

        case VT_I2:
            index.lVal = (long)index.iVal;
            // And fall through...

        case VT_I4:
            if ((index.lVal >= 0) && (index.lVal <= GetMenuItemCount(_hmenu)))
            {
                CFolderItemVerb_Create(this, GetMenuItemID(_hmenu, index.lVal), ppid);
            }

            break;

        default:
            hr = E_NOTIMPL;
        }

        if (*ppid && _dwSafetyOptions)
        {
            hr = MakeSafeForScripting((IUnknown**)ppid);
        }
        else if (hr != E_NOTIMPL)
        {
            hr = S_OK;
        }
    }
    return hr;
}

STDMETHODIMP CFolderItemVerbs::_NewEnum(IUnknown **ppunk)
{
    *ppunk = NULL;
    HRESULT hr = _SecurityCheck();
    if (SUCCEEDED(hr))
    {
        hr = E_OUTOFMEMORY;
        CEnumFolderItemVerbs *pNew = new CEnumFolderItemVerbs(SAFECAST(this, CFolderItemVerbs*));
        if (pNew)
        {
            if (pNew->Init())
                hr = pNew->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
            pNew->Release();
        }
        if (SUCCEEDED(hr) && _dwSafetyOptions)
            hr = MakeSafeForScripting(ppunk);
    }
    return hr;
}

CEnumFolderItemVerbs::CEnumFolderItemVerbs(CFolderItemVerbs *pfiv) :
    _cRef(1), _iCur(0), _psdfiv(pfiv)
{
    _psdfiv->AddRef();
    DllAddRef();
}


CEnumFolderItemVerbs::~CEnumFolderItemVerbs(void)
{
    DllRelease();
    _psdfiv->Release();
}

BOOL CEnumFolderItemVerbs::Init()
{
    return TRUE;    // Currently no initialization needed
}

STDMETHODIMP CEnumFolderItemVerbs::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = {
        QITABENT(CEnumFolderItemVerbs, IEnumVARIANT),
        QITABENT(CEnumFolderItemVerbs, IObjectSafety),
        { 0 },
    };

    return QISearch(this, qit, riid, ppv);
}

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

STDMETHODIMP_(ULONG) CEnumFolderItemVerbs::Release(void)
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

STDMETHODIMP CEnumFolderItemVerbs::Next(ULONG cVar, VARIANT *pVar, ULONG *pulVar)
{
    ULONG cReturn = 0;
    HRESULT hr;

    if (!pulVar)
    {
        if (cVar != 1)
            return E_POINTER;
    }
    else
        *pulVar = 0;

    if (!pVar || _iCur >= GetMenuItemCount(_psdfiv->_hmenu))
        return S_FALSE;

    while (_iCur < GetMenuItemCount(_psdfiv->_hmenu) && cVar > 0)
    {
	    FolderItemVerb *pidv;
        hr = CFolderItemVerb_Create(_psdfiv, GetMenuItemID(_psdfiv->_hmenu, _iCur), &pidv);

        if (SUCCEEDED(hr) && _dwSafetyOptions)
            hr = MakeSafeForScripting((IUnknown**)&pidv);

        _iCur++;
        if (SUCCEEDED(hr))
        {
            pVar->pdispVal = pidv;
            pVar->vt = VT_DISPATCH;
            pVar++;
            cReturn++;
            cVar--;
        }
    }

    if (pulVar)
        *pulVar = cReturn;

    return S_OK;
}

STDMETHODIMP CEnumFolderItemVerbs::Skip(ULONG cSkip)
{
    if ((int)(_iCur + cSkip) >= GetMenuItemCount(_psdfiv->_hmenu))
        return S_FALSE;

    _iCur += cSkip;
    return S_OK;
}

STDMETHODIMP CEnumFolderItemVerbs::Reset(void)
{
    _iCur = 0;
    return S_OK;
}

STDMETHODIMP CEnumFolderItemVerbs::Clone(IEnumVARIANT **ppEnum)
{
    *ppEnum = NULL;

    HRESULT hr = E_OUTOFMEMORY;
    CEnumFolderItemVerbs *pNew = new CEnumFolderItemVerbs(_psdfiv);
    if (pNew)
    {
        if (pNew->Init())
            hr = pNew->QueryInterface(IID_PPV_ARG(IEnumVARIANT, ppEnum));
        pNew->Release();
    }

    if (SUCCEEDED(hr) && _dwSafetyOptions)
        hr = MakeSafeForScripting((IUnknown**)ppEnum);

    return hr;
}

HRESULT CFolderItemVerb_Create(CFolderItemVerbs *psdfivs, UINT id, FolderItemVerb **ppid)
{
    *ppid = NULL;

    HRESULT hr = E_OUTOFMEMORY;
    CFolderItemVerb* psdfiv = new CFolderItemVerb(psdfivs, id);
    if (psdfiv)
    {
        hr = psdfiv->QueryInterface(IID_PPV_ARG(FolderItemVerb, ppid));
        psdfiv->Release();
    }

    return hr;
}

CFolderItemVerb::CFolderItemVerb(CFolderItemVerbs *psdfivs, UINT id) :
    _cRef(1), _psdfivs(psdfivs), _id(id),
    CImpIDispatch(SDSPATCH_TYPELIB, IID_FolderItemVerb)
{
    _psdfivs->AddRef();
    DllAddRef();
}

CFolderItemVerb::~CFolderItemVerb(void)
{
    DllRelease();
    _psdfivs->Release();
}

STDMETHODIMP CFolderItemVerb::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = {
        QITABENT(CFolderItemVerb, FolderItemVerb),
        QITABENT(CFolderItemVerb, IObjectSafety),
        QITABENTMULTI(CFolderItemVerb, IDispatch, FolderItemVerb),
        { 0 },
    };
    return QISearch(this, qit, riid, ppv);
}

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

STDMETHODIMP_(ULONG) CFolderItemVerb::Release(void)
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

STDMETHODIMP CFolderItemVerb::get_Application(IDispatch **ppid)
{
    // _psdfivs returns E_NOTIMPL
    return _psdfivs->get_Application(ppid);
}

STDMETHODIMP CFolderItemVerb::get_Parent(IDispatch **ppid)
{
    *ppid = NULL;
    return E_NOTIMPL;
}


STDMETHODIMP CFolderItemVerb::get_Name(BSTR *pbs)
{
    TCHAR szMenuText[MAX_PATH];
    // Warning: did not check security here as could not get here if unsafe...
    GetMenuString(_psdfivs->_hmenu, _id, szMenuText, ARRAYSIZE(szMenuText), MF_BYCOMMAND);
    *pbs = SysAllocStringT(szMenuText);

    return *pbs ? S_OK : E_OUTOFMEMORY;
}


STDMETHODIMP CFolderItemVerb::DoIt()
{
    CMINVOKECOMMANDINFO ici = {
        sizeof(CMINVOKECOMMANDINFO),
        0L,
        NULL,
        NULL,
        NULL, NULL,
        SW_SHOWNORMAL,
    };
    // Warning: did not check security here as could not get here if unsafe...
    ici.lpVerb = (LPSTR)MAKEINTRESOURCE(_id - CONTEXTMENU_IDCMD_FIRST);
    return _psdfivs->_pcm->InvokeCommand(&ici);
}