//
// isfmenu.cpp
//
// callback for chevron drop-down menu for isfbands
//

#include "shellprv.h"
#include "isfmenu.h"
#include "legacy.h"
#include "util.h"

// *** IUnknown methods ***
STDMETHODIMP CISFMenuCallback::QueryInterface (REFIID riid, LPVOID * ppvObj)
{
    static const QITAB qit[] = 
    {
        QITABENT(CISFMenuCallback, IShellMenuCallback),
        QITABENT(CISFMenuCallback, IObjectWithSite),
        { 0 },
    };

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

STDMETHODIMP_(ULONG) CISFMenuCallback::AddRef ()
{
    return ++_cRef;
}

STDMETHODIMP_(ULONG) CISFMenuCallback::Release()
{
    ASSERT(_cRef > 0);
    _cRef--;

    if( _cRef > 0)
        return _cRef;

    delete this;
    return 0;
}

BOOL CISFMenuCallback::_IsVisible(LPITEMIDLIST pidl)
{
    if (_poct) {
        VARIANTARG v;

        v.vt = VT_INT_PTR;
        v.byref = pidl;

        HRESULT hr = _poct->Exec(&CGID_ISFBand, ISFBID_ISITEMVISIBLE, 0, &v, NULL);
        return (hr == S_OK);
    }

    return FALSE;
}


HRESULT IUnknown_SeekToZero(IUnknown* punk)
{
    HRESULT hres = E_FAIL;
    IStream* pstm;
    if (punk && SUCCEEDED(punk->QueryInterface(IID_IStream, (void**)&pstm)))
    {
        // We need to seek to the beginning of the stream here. We don't do this in
        // the menubands because it's rude: They should not seek to the beginning
        // because there may be information that needs to be saved after them.
        //Set the seek pointer at the beginning.
        const LARGE_INTEGER li0 = {0};
        hres = pstm->Seek(li0, STREAM_SEEK_SET, NULL);
        pstm->Release();
    }

    return hres;
}

HRESULT CISFMenuCallback::_GetObject(LPSMDATA psmd, REFIID riid, void** ppvObj)
{
    HRESULT hres = S_FALSE;
    *ppvObj = NULL;
    if (IsEqualIID(riid, IID_IStream))
    {
        if (_pidl && psmd->pidlFolder && psmd->pidlItem)
        {
            // Verify that the Cascading menuband is ONLY asking for this folder.
            // because if there is a sub menu, It's going to ask again with the
            // pidl of that folder, which we don't have the Stream for, and we
            // can hose things pretty good if we indescriminatly hand out order streams

            LPITEMIDLIST pidlFull = ILCombine(psmd->pidlFolder, psmd->pidlItem);
            if (pidlFull)
            {
                if (_poct && ILIsEqual(pidlFull, _pidl)) 
                {
                    VARIANTARG v = {0};

                    v.vt = VT_UNKNOWN;
                    hres = _poct->Exec(&CGID_ISFBand, ISFBID_GETORDERSTREAM, 0, NULL, &v);

                    if (SUCCEEDED(hres))
                    {
                        IUnknown_SeekToZero(v.punkVal);

                        hres = v.punkVal->QueryInterface(riid, ppvObj);
                        v.punkVal->Release();
                    }
                }
                ILFree(pidlFull);
            }
        }
    }
    return hres;
}

HRESULT CISFMenuCallback::_SetObject(LPSMDATA psmd, REFIID riid, void** ppvObj)
{
    HRESULT hres = E_FAIL;

    if (IsEqualIID(riid, IID_IStream))
    {
        if (_pidl && psmd->pidlFolder && psmd->pidlItem)
        {
            // Verify that the Cascading menuband is ONLY asking for this folder.
            // because if there is a sub menu, It's going to ask again with the
            // pidl of that folder, which we don't have the Stream for, and we
            // can hose things pretty good if we indescriminatly hand out order streams

            LPITEMIDLIST pidlFull = ILCombine(psmd->pidlFolder, psmd->pidlItem);
            if (pidlFull)
            {
                if (_poct && ILIsEqual(pidlFull, _pidl)) 
                {
                    ASSERT(ppvObj);

                    VARIANTARG v;

                    v.vt = VT_UNKNOWN;
                    v.punkVal = *(IUnknown**)ppvObj;

                    IUnknown_SeekToZero(*(IUnknown**)ppvObj);

                    hres = _poct->Exec(&CGID_ISFBand, ISFBID_SETORDERSTREAM, 0, &v, NULL);
                }
                ILFree(pidlFull);
            }
        }
    }

    return hres;
}


HRESULT CISFMenuCallback::_GetSFInfo(LPSMDATA psmd, PSMINFO psminfo)
{
    // We only want to filter pidls if:
    //  1) It's at the root of the links chevron menu
    //  2) It's _IS_ visible in the links bar. We don't want to show links
    //     in this menu that are visible.
    if (psmd->uIdAncestor == ANCESTORDEFAULT &&
        (psminfo->dwMask & SMIM_FLAGS)       && 
        _IsVisible(psmd->pidlItem))
    {
        // not obscured on the subject isfband; exclude from menu
        psminfo->dwFlags |= SMIF_HIDDEN;
    }

    return S_OK;
}

// *** IShellMenuCallback methods ***
STDMETHODIMP CISFMenuCallback::CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr = S_FALSE;

    switch (uMsg) 
    {
    case SMC_SFEXEC:
        hr = SHNavigateToFavorite(psmd->psf, psmd->pidlItem, _punkSite, SBSP_DEFBROWSER | SBSP_DEFMODE);
        break;

    case SMC_GETSFINFO:
        hr = _GetSFInfo(psmd, (PSMINFO)lParam);
        break;

    case SMC_GETSFOBJECT:
        hr = _GetObject(psmd, *((GUID*)wParam), (void**)lParam);
        break;

    case SMC_SETSFOBJECT:
        hr = _SetObject(psmd, *((GUID*)wParam), (void**)lParam);
        break;

    }

    return hr;
}

// *** IObjectWithSite methods ***
STDMETHODIMP CISFMenuCallback::SetSite(IUnknown* punkSite)
{
    if (punkSite != _punkSite)
        IUnknown_Set(&_punkSite, punkSite);

    return S_OK;
}


HRESULT CISFMenuCallback::Initialize(IUnknown* punk)
{
    HRESULT hr = E_FAIL;

    if (punk)
        hr = punk->QueryInterface(IID_IOleCommandTarget, (PVOID*)&_poct);

    IShellFolderBand* psfb;
    hr = punk->QueryInterface(IID_IShellFolderBand, (PVOID*)&psfb);

    if (SUCCEEDED(hr)) 
    {
        BANDINFOSFB bi;
        bi.dwMask = ISFB_MASK_IDLIST | ISFB_MASK_SHELLFOLDER;

        hr = psfb->GetBandInfoSFB(&bi);
        _pidl = bi.pidl;
        if (bi.psf)
            bi.psf->Release();
        psfb->Release();
    }

    return hr;
}

CISFMenuCallback::CISFMenuCallback() : _cRef(1)
{
}

CISFMenuCallback::~CISFMenuCallback()
{
    ASSERT(_cRef == 0);

    ILFree(_pidl);

    ATOMICRELEASE(_poct);
}