/*****************************************************************************\
    FILE:   isf.cpp

    DESCRIPTION:
        This is a base class that implements the default behavior of IShellFolder.
\*****************************************************************************/

#include "priv.h"
#include "isf.h"
#include <shlobj.h>



/*****************************************************************************\
    FUNCTION: IShellFolder::ParseDisplayName

    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::ParseDisplayName(HWND hwnd, LPBC pbcReserved, LPOLESTR pwszDisplayName,
                        ULONG * pchEaten, LPITEMIDLIST * ppidl, ULONG *pdwAttributes)
{
    if (pdwAttributes)
        *pdwAttributes = 0;

    if (ppidl)
        *ppidl = NULL;

    return E_NOTIMPL;
}

/*****************************************************************************\
    FUNCTION: IShellFolder::EnumObjects

    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::EnumObjects(HWND hwndOwner, DWORD grfFlags, IEnumIDList ** ppenumIDList)
{
    if (ppenumIDList)
        *ppenumIDList = NULL;

    return E_NOTIMPL;
}


/*****************************************************************************\
    FUNCTION: IShellFolder::BindToObject

    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::BindToObject(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, LPVOID * ppvObj)
{
    if (ppvObj)
        *ppvObj = NULL;

    return E_NOTIMPL;
}


/*****************************************************************************\
     FUNCTION: IShellFolder::BindToStorage
 
    DESCRIPTION:
         This should be implemented so people can use the File.Open and File.SaveAs
    dialogs with this ShellFolder.
\*****************************************************************************/
HRESULT CBaseFolder::BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, LPVOID * ppvObj)
{
    if (ppvObj)
        *ppvObj = NULL;

    return E_NOTIMPL;
}


/*****************************************************************************\
     FUNCTION: IShellFolder::CompareIDs
 
    DESCRIPTION:
         This should be implemented so people can use the File.Open and File.SaveAs
    dialogs with this ShellFolder.
\*****************************************************************************/
HRESULT CBaseFolder::CompareIDs(LPARAM ici, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
    return E_NOTIMPL;
}


/*****************************************************************************\
     FUNCTION: IShellFolder::CreateViewObject
 
    DESCRIPTION:
         This should be implemented so people can use the File.Open and File.SaveAs
    dialogs with this ShellFolder.
\*****************************************************************************/
HRESULT CBaseFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID * ppvObj)
{
    HRESULT hr = E_NOINTERFACE;

    *ppvObj = NULL;
    if (IsEqualIID(riid, IID_IShellView))
        hr = _CreateShellView(hwndOwner, ppvObj);
    else if (IsEqualIID(riid, IID_IContextMenu))
        hr = _GetUIObjectOf(hwndOwner, 0, NULL, riid, 0, ppvObj, TRUE);
    else
        hr = E_NOINTERFACE;

    return hr;
}


BOOL IsShellIntegration(void)
{
    BOOL fResult = FALSE;
    HINSTANCE hInst = LoadLibrary(TEXT("shell32.dll"));

    if (hInst)
    {
        LPVOID pv = GetProcAddress(hInst, "DllGetVersion");
        if (pv)
            fResult = TRUE;
        FreeLibrary(hInst);
    }
    
    return fResult;
}

HRESULT CBaseFolder::_CreateShellView(HWND hwndOwner, void ** ppvObj, LONG lEvents, FOLDERVIEWMODE fvm, 
                                       IShellFolderViewCB * psfvCallBack, LPCITEMIDLIST pidl, LPFNVIEWCALLBACK pfnCallback)
{
    HRESULT hr;
    IShellFolder * psf;

    hr = this->QueryInterface(IID_IShellFolder, (LPVOID *) &psf);
    if (EVAL(SUCCEEDED(hr)))
    {
        SFV_CREATE sfvCreate =      // SHCreateShellFolderView struct
        {
                sizeof(SFV_CREATE),
                psf,            // psf
                NULL,           // psvOuter
                psfvCallBack    // psfvcb - (IShellFolderViewCB *)
        };

        // SHCreateShellFolderView isn't in the original shell.  We can't rely on the 
        // the Delayload code because it's exported by ordinal and the original
        // shell had a different exports by the same number.
        if (IsShellIntegration())
            hr = _SHCreateShellFolderView(&sfvCreate, (LPSHELLVIEW FAR*)ppvObj);
        else
            hr = E_FAIL;  // Force us to go into the next try.

        // If we aren't running on a machine with Shell Integration, SHCreateShellFolderView will fail.
        if (FAILED(hr))
        {
            CSFV csfv;

            csfv.cbSize = sizeof(csfv);
            csfv.pshf = psf;
            csfv.psvOuter = (IShellView *) psfvCallBack;      // Hack but it works...
            csfv.pidl = pidl;           // This is feed to SFVM_GETNOTIFY so it needs to be a pidlTarget.
            csfv.lEvents = lEvents;
            csfv.pfnCallback = pfnCallback;
            csfv.fvm = fvm;         // vs. FVM_ICON, ...

            hr = SHCreateShellFolderViewEx(&csfv, (LPSHELLVIEW FAR*)ppvObj);
            if (SUCCEEDED(hr))
                psfvCallBack->AddRef();     // We gave them a ref.
        }

        psf->Release();
    }

    return hr;
}


/*****************************************************************************\
     FUNCTION: IShellFolder::GetAttributesOf
 
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::GetAttributesOf(UINT cpidl, LPCITEMIDLIST *apidl, ULONG *rgfInOut)
{
    return E_NOTIMPL;
}


/*****************************************************************************\
     FUNCTION: IShellFolder::GetUIObjectOf
 
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLIST rgpidl[],
                                REFIID riid, UINT * prgfInOut, LPVOID * ppvObj)
{
    return E_NOTIMPL;
}


/*****************************************************************************\
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::_GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLIST rgpidl[],
                                REFIID riid, UINT * prgfInOut, LPVOID * ppvOut, BOOL fFromCreateViewObject)
{
    return GetUIObjectOf(hwndOwner, cidl, rgpidl, riid, prgfInOut, ppvOut);
}


/*****************************************************************************\
     FUNCTION: IShellFolder::GetDisplayNameOf
 
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD shgno, LPSTRRET pStrRet)
{
    return E_NOTIMPL;
}


/*****************************************************************************\
     FUNCTION: IShellFolder::SetNameOf
 
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::SetNameOf(HWND hwndOwner, LPCITEMIDLIST pidl, LPCOLESTR pwszName,
                                DWORD dwReserved, LPITEMIDLIST *ppidlOut)
{
    return E_NOTIMPL;
}


//===========================
// *** IShellFolder2 Interface ***
//===========================

//===========================
// *** IPersist Interface ***
//===========================

/*****************************************************************************\
     FUNCTION: IPersist::GetClassID
 
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::GetClassID(LPCLSID pClassID)
{
    HRESULT hr = E_INVALIDARG;

    if (EVAL(pClassID))
    {
        if (EVAL(m_pClassID))
        {
            *pClassID = *m_pClassID;
            hr = S_OK;
        }
        else
            hr = E_FAIL;
    }

    return hr;
}

//===========================
// *** IPersistFolder Interface ***
//===========================

/*****************************************************************************\
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::Initialize(LPCITEMIDLIST pidl)
{
    ASSERT(!m_pidl);   // Don't reroot us.
    return _Initialize(pidl, NULL, ILGetSize(pidl) - sizeof(pidl->mkid.cb));
}

//===========================
// *** IPersistFolder2 Interface ***
//===========================

/*****************************************************************************\ 
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::GetCurFolder(LPITEMIDLIST *ppidl)
{
    HRESULT hr = E_INVALIDARG;

    if (EVAL(ppidl))
    {
        hr = E_FAIL;

        if (m_pidlRoot)
        {
            *ppidl = ILClone(m_pidlRoot);
        }
        else if (EVAL(m_pidl))
        {
            *ppidl = GetPublicTargetPidlClone();
        }

        if (*ppidl)
            hr = S_OK;
    }

    return hr;
}

//===========================
// *** IPersistFolder3 Interface ***
//===========================
HRESULT GetPidlFromPersistFolderTargetInfo(const PERSIST_FOLDER_TARGET_INFO *ppfti, LPITEMIDLIST * ppidl, BOOL fFree)
{
    HRESULT hr = E_INVALIDARG;

    if (ppidl)
    {
        *ppidl = NULL;
        if (ppfti->pidlTargetFolder)
        {
            *ppidl = (fFree ? ppfti->pidlTargetFolder : ILClone(ppfti->pidlTargetFolder));
            if (*ppidl)
                hr = S_OK;
            else
                hr = E_OUTOFMEMORY;
        }
        else
        {
            if (ppfti->szTargetParsingName[0])
            {
                hr = IEParseDisplayNameWithBCW(CP_ACP, ppfti->szTargetParsingName, NULL, ppidl);
            }

            if (!*ppidl && (-1 != ppfti->csidl))
            {
                hr = SHGetSpecialFolderLocation(NULL, ppfti->csidl, ppidl);
            }
        }
    }
    
    return hr;
}


/*****************************************************************************\
    DESCRIPTION:
\*****************************************************************************/
HRESULT CBaseFolder::InitializeEx(IBindCtx *pbc, LPCITEMIDLIST pidlRoot, const PERSIST_FOLDER_TARGET_INFO *ppfti)
{
    HRESULT hr = E_INVALIDARG;

    if (EVAL(pidlRoot))
    {
        if (ppfti)
        {
            // We are a Folder Shortcut.
            LPITEMIDLIST pidlTarget;

            hr = GetPidlFromPersistFolderTargetInfo(ppfti, &pidlTarget, FALSE);  // Get the real root.           
            TraceMsg(TF_FOLDER_SHRTCUTS, "CBaseFolder::InitializeEx() this=%#08lx, pidlTarget=%#08lx, pidlRoot=%#08lx", this, pidlTarget, pidlRoot);

            AssertMsg((NULL != pidlTarget), TEXT("CBaseFolder::InitializeEx() We are useless without a pidlTarget so watch me go limp."));
            if (pidlTarget)
            {
                hr = _Initialize(pidlTarget, pidlRoot, m_nIDOffsetToPrivate);
                ILFree(pidlTarget);
            }
        }
        else
        {
            // We aren't a folder shortcut.
            hr = Initialize(pidlRoot);
        }
    }

    return hr;
}


HRESULT CBaseFolder::GetFolderTargetInfo(PERSIST_FOLDER_TARGET_INFO *ppfti)
{
    HRESULT hr = E_INVALIDARG;

    AssertMsg((NULL != ppfti), TEXT("CBaseFolder::GetFolderTargetInfo() Caller passed an invalid param."));
    if (ppfti)
    {
        ZeroMemory(ppfti, sizeof(*ppfti)); 

        ppfti->pidlTargetFolder = ILClone(m_pidlRoot);
        ppfti->dwAttributes = -1;
        ppfti->csidl = -1;
        hr = S_OK;
    }

    return hr;
}


LPCITEMIDLIST CBaseFolder::GetPrivatePidlReference(void)
{
    return _ILSkip(m_pidl, m_nIDOffsetToPrivate);
}

// This function always needs the InternetExplorer pidl.
LPITEMIDLIST CBaseFolder::GetPublicPidlRootIDClone(void)
{
    LPITEMIDLIST pidlFull = ILClone(m_pidl);
    LPITEMIDLIST pidlPrivStart = _ILSkip(pidlFull, m_nIDOffsetToPrivate);

    // Strip all Private ItemIDs
    while (!ILIsEmpty(pidlPrivStart))
        ILRemoveLastID(pidlPrivStart);

    return pidlFull;
}

LPITEMIDLIST CBaseFolder::CreateFullPrivatePidl(LPCITEMIDLIST pidlPrivateSubPidl)
{
    return ILCombine(GetPrivatePidlReference(), pidlPrivateSubPidl);
}

LPITEMIDLIST CBaseFolder::CreateFullPublicPidlFromRelative(LPCITEMIDLIST pidlPrivateSubPidl)
{
    return ILCombine(GetPublicRootPidlReference(), pidlPrivateSubPidl);
}

LPITEMIDLIST CBaseFolder::CreateFullPublicPidl(LPCITEMIDLIST pidlPrivatePidl)
{
    LPITEMIDLIST pidlRoot = GetPublicPidlRootIDClone();
    LPITEMIDLIST pidlResult = NULL;
    
    if (pidlRoot)
    {
        pidlResult = ILCombine(pidlRoot, pidlPrivatePidl);
        ILFree(pidlRoot);
    }

    return pidlResult;
}

HRESULT CBaseFolder::_Initialize(LPCITEMIDLIST pidlTarget, LPCITEMIDLIST pidlRoot, int nBytesToPrivate)
{
    HRESULT hr = E_INVALIDARG;

    if (pidlTarget)
    {
        ILFree(m_pidl);
        ILFree(m_pidlRoot);    
        m_pidl = ILClone(pidlTarget);
        m_pidlRoot = ILClone(pidlRoot); // This is the Folder Shortcut pidl.  We don't use it outselves.

        if (m_pidl)
        {
            m_nIDOffsetToPrivate = nBytesToPrivate;
            hr = S_OK;
        }
        else
            hr = E_OUTOFMEMORY;
    }

    return hr;
}


/****************************************************\
    Constructor
\****************************************************/
CBaseFolder::CBaseFolder(LPCLSID pClassID) : m_cRef(1)
{
    DllAddRef();

    // This needs to be allocated in Zero Inited Memory.
    // Assert that all Member Variables are inited to Zero.
    ASSERT(!m_pidl);
    ASSERT(!m_nIDOffsetToPrivate);
    ASSERT(!m_pClassID);

    m_pClassID = pClassID;
    ASSERT(pClassID);
}


/****************************************************\
    Destructor
\****************************************************/
CBaseFolder::~CBaseFolder()
{
    Pidl_Set(&m_pidlRoot, NULL);    // Folder Shortcut pidl
    Pidl_Set(&m_pidl, NULL);
    DllRelease();
}


//===========================
// *** IUnknown Interface ***
//===========================

ULONG CBaseFolder::AddRef()
{
    m_cRef++;
    return m_cRef;
}

ULONG CBaseFolder::Release()
{
    ASSERT(m_cRef > 0);
    m_cRef--;

    if (m_cRef > 0)
        return m_cRef;

    delete this;
    return 0;
}

HRESULT CBaseFolder::QueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENTMULTI(CBaseFolder, IShellFolder, IShellFolder2),
        QITABENTMULTI(CBaseFolder, IPersist, IPersistFolder),
        QITABENTMULTI(CBaseFolder, IPersist, IPersistFolder3),
        QITABENTMULTI(CBaseFolder, IPersistFolder, IPersistFolder3),
        QITABENTMULTI(CBaseFolder, IPersistFolder2, IPersistFolder3),
        QITABENT(CBaseFolder, IShellFolder2),
        QITABENT(CBaseFolder, IPersistFolder3),
        QITABENT(CBaseFolder, IObjectWithSite),
        { 0 },
    };

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