windows-nt/Source/XPSP1/NT/shell/shell32/item.cpp
2020-09-26 16:20:57 +08:00

1510 lines
38 KiB
C++

#include "shellprv.h"
#include "cowsite.h"
#include "enumidlist.h"
typedef enum
{
MAYBEBOOL_MAYBE = 0,
MAYBEBOOL_TRUE,
MAYBEBOOL_FALSE,
} MAYBEBOOL;
#define _GetBindWindow(p) NULL
class CShellItem : public IShellItem
, public IPersistIDList
, public IParentAndItem
{
public:
CShellItem();
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IShellItem
STDMETHODIMP BindToHandler(IBindCtx *pbc, REFGUID rguidHandler, REFIID riid, void **ppv);
STDMETHODIMP GetParent(IShellItem **ppsi);
STDMETHODIMP GetDisplayName(SIGDN sigdnName, LPOLESTR *ppszName);
STDMETHODIMP GetAttributes(SFGAOF sfgaoMask, SFGAOF *psfgaoFlags);
STDMETHODIMP Compare(IShellItem *psi, SICHINTF hint, int *piOrder);
// IPersist
STDMETHODIMP GetClassID(LPCLSID lpClassID) {*lpClassID = CLSID_ShellItem; return S_OK;}
// IPersistIDList
STDMETHODIMP SetIDList(LPCITEMIDLIST pidl);
STDMETHODIMP GetIDList(LPITEMIDLIST *ppidl);
// IParentAndItem
STDMETHODIMP SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psf, LPCITEMIDLIST pidlChild);
STDMETHODIMP GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidlChild);
#if 0
// IPersistStream
STDMETHODIMP IsDirty(void);
STDMETHODIMP Load(IStream *pStm);
STDMETHODIMP Save(IStream *pStm, BOOL fClearDirty);
STDMETHODIMP GetSizeMax(ULARGE_INTEGER *pcbSize);
// implement or we cant ask for the IShellFolder in GetParentAndItem()
// IMarshal
STDMETHODIMP GetUnmarshalClass(
REFIID riid,
void *pv,
DWORD dwDestContext,
void *pvDestContext,
DWORD mshlflags,
CLSID *pCid);
STDMETHODIMP GetMarshalSizeMax(
REFIID riid,
void *pv,
DWORD dwDestContext,
void *pvDestContext,
DWORD mshlflags,
DWORD *pSize);
STDMETHODIMP MarshalInterface(
IStream *pStm,
REFIID riid,
void *pv,
dwDestContext,
void *pvDestContext,
DWORD mshlflags);
STDMETHODIMP UnmarshalInterface(
IStream *pStm,
REFIID riid,
void **ppv);
STDMETHODIMP ReleaseMarshalData(IStream *pStm);
STDMETHODIMP DisconnectObject(DWORD dwReserved);
#endif // 0
private: // methods
~CShellItem();
void _Reset(void);
// BindToHandler() helpers
HRESULT _BindToParent(REFIID riid, void **ppv);
HRESULT _BindToSelf(REFIID riid, void **ppv);
// GetAttributes() helpers
inline BOOL _IsAttrib(SFGAOF sfgao);
// GetDisplayName() helpers
BOOL _SupportedName(SIGDN sigdnName, SHGDNF *pflags);
HRESULT _FixupName(SIGDN sigdnName, LPOLESTR *ppszName);
void _FixupAttributes(IShellFolder *psf, SFGAOF sfgaoMask);
LONG _cRef;
LPITEMIDLIST _pidlSelf;
LPCITEMIDLIST _pidlChild;
LPITEMIDLIST _pidlParent;
IShellFolder *_psfSelf;
IShellFolder *_psfParent;
BOOL _fInited;
SFGAOF _sfgaoTried;
SFGAOF _sfgaoKnown;
};
CShellItem::CShellItem() : _cRef(1)
{
ASSERT(!_pidlSelf);
ASSERT(!_pidlChild);
ASSERT(!_pidlParent);
ASSERT(!_psfSelf);
ASSERT(!_psfParent);
}
CShellItem::~CShellItem()
{
_Reset();
}
void CShellItem::_Reset(void)
{
ATOMICRELEASE(_psfSelf);
ATOMICRELEASE(_psfParent);
ILFree(_pidlSelf);
ILFree(_pidlParent);
_pidlSelf = NULL;
_pidlParent = NULL;
_pidlChild = NULL; // alias into _pidlParent
}
STDMETHODIMP CShellItem::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CShellItem, IShellItem),
QITABENT(CShellItem, IPersistIDList),
QITABENT(CShellItem, IParentAndItem),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CShellItem::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CShellItem::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
STDMETHODIMP CShellItem::SetIDList(LPCITEMIDLIST pidl)
{
if (!pidl)
{
RIPMSG(0, "Tried to Call SetIDList with a NULL pidl");
return E_INVALIDARG;
}
_Reset();
HRESULT hr = SHILClone(pidl, &_pidlSelf);
if (SUCCEEDED(hr))
{
// possible this item is the desktop in which case
// there is no parent.
if (ILIsEmpty(_pidlSelf))
{
_pidlParent = NULL;
_pidlChild = _pidlSelf;
}
else
{
_pidlParent = ILCloneParent(_pidlSelf);
_pidlChild = ILFindLastID(_pidlSelf);
if (NULL == _pidlParent)
{
hr = E_OUTOFMEMORY;
}
}
}
return hr;
}
STDMETHODIMP CShellItem::GetIDList(LPITEMIDLIST *ppidl)
{
HRESULT hr = E_UNEXPECTED;
if (_pidlSelf)
{
hr = SHILClone(_pidlSelf, ppidl);
}
return hr;
}
HRESULT CShellItem::_BindToParent(REFIID riid, void **ppv)
{
ASSERT(_pidlChild); // we should already have a child setup
if (!_psfParent && _pidlParent && _pidlSelf) // check pidlParent to check in case the item is the desktop
{
HRESULT hr;
LPCITEMIDLIST pidlChild;
hr = SHBindToIDListParent(_pidlSelf, IID_PPV_ARG(IShellFolder, &_psfParent), &pidlChild);
#ifdef DEBUG
if (SUCCEEDED(hr))
{
ASSERT(pidlChild == _pidlChild);
}
#endif // DEBUG
}
if (_psfParent)
{
return _psfParent->QueryInterface(riid, ppv);
}
return E_FAIL;
}
HRESULT CShellItem::_BindToSelf(REFIID riid, void **ppv)
{
HRESULT hr = E_FAIL;
if (!_psfSelf)
{
hr = BindToHandler(NULL, BHID_SFObject, IID_PPV_ARG(IShellFolder, &_psfSelf));
}
if (_psfSelf)
{
hr = _psfSelf->QueryInterface(riid, ppv);
}
return hr;
}
HRESULT _CreateLinkTargetItem(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv)
{
SFGAOF flags = SFGAO_LINK;
if (SUCCEEDED(psi->GetAttributes(flags, &flags)) && (flags & SFGAO_LINK))
{
// this is indeed a link
// get the target and
IShellLink *psl;
HRESULT hr = psi->BindToHandler(pbc, BHID_SFUIObject, IID_PPV_ARG(IShellLink, &psl));
if (SUCCEEDED(hr))
{
DWORD slr = 0;
HWND hwnd = _GetBindWindow(pbc);
if (pbc)
{
BIND_OPTS2 bo;
bo.cbStruct = sizeof(BIND_OPTS2); // Requires size filled in.
if (SUCCEEDED(pbc->GetBindOptions(&bo)))
{
// these are the flags to pass to resolve
slr = bo.dwTrackFlags;
}
}
hr = psl->Resolve(hwnd, slr);
if (S_OK == hr)
{
LPITEMIDLIST pidl;
hr = psl->GetIDList(&pidl);
if (SUCCEEDED(hr))
{
IShellItem *psiTarget;
hr = SHCreateShellItem(NULL, NULL, pidl, &psiTarget);
if (SUCCEEDED(hr))
{
hr = psiTarget->QueryInterface(riid, ppv);
psiTarget->Release();
}
ILFree(pidl);
}
}
else if (SUCCEEDED(hr))
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
psl->Release();
}
return hr;
}
return E_INVALIDARG;
}
BOOL _IsWebfolders(IShellItem *psi);
HRESULT _CreateStorageHelper(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv);
HRESULT _CreateStream(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv);
HRESULT _CreateEnumHelper(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv);
HRESULT _CreateHelperInstance(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv)
{
IItemHandler *pih;
HRESULT hr = SHCoCreateInstance(NULL, &rbhid, NULL, IID_PPV_ARG(IItemHandler, &pih));
if (SUCCEEDED(hr))
{
hr = pih->SetItem(psi);
if (SUCCEEDED(hr))
{
hr = pih->QueryInterface(riid, ppv);
}
pih->Release();
}
return hr;
}
enum
{
BNF_OBJECT = 0x0001,
BNF_UIOBJECT = 0x0002,
BNF_VIEWOBJECT = 0x0004,
BNF_USE_RIID = 0x0008,
BNF_REFLEXIVE = 0x0010,
};
typedef DWORD BNF;
typedef HRESULT (* PFNCREATEHELPER)(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv);
typedef struct
{
const GUID *pbhid;
BNF bnf;
const IID *piid;
PFNCREATEHELPER pfn;
} BINDNONSENSE;
#define BINDHANDLER(bhid, flags, piid, pfn) { &bhid, flags, piid, pfn},
#define SFBINDHANDLER(bhid, flags, piid) BINDHANDLER(bhid, flags, piid, NULL)
#define BINDHELPER(bhid, flags, pfn) BINDHANDLER(bhid, flags, NULL, pfn)
const BINDNONSENSE c_bnList[] =
{
SFBINDHANDLER(BHID_SFObject, BNF_OBJECT | BNF_USE_RIID, NULL)
SFBINDHANDLER(BHID_SFUIObject, BNF_UIOBJECT | BNF_USE_RIID, NULL)
SFBINDHANDLER(BHID_SFViewObject, BNF_VIEWOBJECT | BNF_USE_RIID, NULL)
BINDHELPER(BHID_LinkTargetItem, 0, _CreateLinkTargetItem)
BINDHELPER(BHID_LocalCopyHelper, 0, _CreateHelperInstance)
BINDHELPER(BHID_Storage, BNF_OBJECT | BNF_USE_RIID, _CreateStorageHelper)
BINDHELPER(BHID_Stream, BNF_OBJECT | BNF_USE_RIID, NULL)
BINDHELPER(BHID_StorageEnum, 0, _CreateEnumHelper)
};
HRESULT _GetBindNonsense(const GUID *pbhid, const IID *piid, BINDNONSENSE *pbn)
{
HRESULT hr = MK_E_NOOBJECT;
for (int i = 0; i < ARRAYSIZE(c_bnList); i++)
{
if (IsEqualGUID(*pbhid, *(c_bnList[i].pbhid)))
{
*pbn = c_bnList[i];
hr = S_OK;
if (pbn->bnf & BNF_USE_RIID)
{
pbn->piid = piid;
}
if (pbn->piid && IsEqualGUID(*(pbn->piid), *piid))
pbn->bnf |= BNF_REFLEXIVE;
break;
}
}
return hr;
}
// the SafeBC functions will use the pbc passed in or
// create a new one if necessary. either way, if
// the *ppbc is returned non-NULL then it is ref'd
STDAPI SHSafeRegisterObjectParam(LPCWSTR psz, IUnknown *punk, IBindCtx *pbcIn, IBindCtx **ppbc)
{
IBindCtx *pbc = pbcIn;
if (!pbc)
CreateBindCtx(0, &pbc);
else
pbc->AddRef();
*ppbc = NULL;
HRESULT hr;
if (pbc)
{
hr = pbc->RegisterObjectParam((LPOLESTR)psz, punk);
if (SUCCEEDED(hr))
{
// pass our ref to the caller
*ppbc = pbc;
}
else
{
pbc->Release();
}
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
STDMETHODIMP CShellItem::BindToHandler(IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv)
{
// look up handler for bind flags
// use the flags to determine BTO GUIO BTS CVO
BINDNONSENSE bn = {0};
HRESULT hr = _GetBindNonsense(&rbhid, &riid, &bn);
*ppv = NULL;
if (SUCCEEDED(hr))
{
hr = E_NOINTERFACE;
if (_pidlParent && (bn.bnf & (BNF_OBJECT | BNF_UIOBJECT)))
{
IShellFolder *psf;
if (SUCCEEDED(_BindToParent(IID_PPV_ARG(IShellFolder, &psf))))
{
if (bn.bnf & BNF_OBJECT)
{
hr = psf->BindToObject(_pidlChild, pbc, *(bn.piid), ppv);
}
if (FAILED(hr) && (bn.bnf & BNF_UIOBJECT))
{
HWND hwnd = _GetBindWindow(pbc);
hr = psf->GetUIObjectOf(hwnd, 1, &_pidlChild, *(bn.piid), NULL, ppv);
}
psf->Release();
}
}
// if don't have a parent pidl then we are the desktop.
if (FAILED(hr) && (NULL == _pidlParent) && (bn.bnf & BNF_OBJECT))
{
IShellFolder *psf;
if (SUCCEEDED(SHGetDesktopFolder(&psf)))
{
hr = psf->QueryInterface(riid,ppv);
psf->Release();
}
}
if (FAILED(hr) && (bn.bnf & BNF_VIEWOBJECT))
{
IShellFolder *psf;
if (SUCCEEDED(_BindToSelf(IID_PPV_ARG(IShellFolder, &psf))))
{
HWND hwnd = _GetBindWindow(pbc);
hr = psf->CreateViewObject(hwnd, *(bn.piid), ppv);
psf->Release();
}
}
if (SUCCEEDED(hr))
{
if (!(bn.bnf & BNF_REFLEXIVE))
{
IUnknown *punk = (IUnknown *)*ppv;
hr = punk->QueryInterface(riid, ppv);
punk->Release();
}
// else riid is the same as bn.piid
}
else if (bn.pfn)
{
hr = bn.pfn(this, pbc, rbhid, riid, ppv);
}
}
return hr;
}
STDMETHODIMP CShellItem::GetParent(IShellItem **ppsi)
{
HRESULT hr = MK_E_NOOBJECT;
if (_pidlParent)
{
if (!ILIsEmpty(_pidlSelf))
{
CShellItem *psi = new CShellItem();
if (psi)
{
// may already have the _psf Parent here so be nice
// to have a way to do this in a set.
hr = psi->SetIDList(_pidlParent);
if (SUCCEEDED(hr))
hr = psi->QueryInterface(IID_PPV_ARG(IShellItem, ppsi));
psi->Release();
}
else
hr = E_OUTOFMEMORY;
}
}
return hr;
}
BOOL CShellItem::_IsAttrib(SFGAOF sfgao)
{
HRESULT hr = GetAttributes(sfgao, &sfgao);
return hr == S_OK;
}
#define SHGDNF_MASK 0xFFFF // bottom word
BOOL CShellItem::_SupportedName(SIGDN sigdn, SHGDNF *pflags)
{
*pflags = (sigdn & SHGDNF_MASK);
// block this completely
// to avoid doing any binding at all
if (sigdn == SIGDN_FILESYSPATH && !_IsAttrib(SFGAO_FILESYSTEM))
return FALSE;
return TRUE;
}
HRESULT CShellItem::_FixupName(SIGDN sigdnName, LPOLESTR *ppszName)
{
HRESULT hr = S_OK;
if (sigdnName == SIGDN_URL && !UrlIsW(*ppszName, URLIS_URL))
{
WCHAR sz[MAX_URL_STRING];
DWORD cch = ARRAYSIZE(sz);
if (SUCCEEDED(UrlCreateFromPathW(*ppszName, sz, &cch, 0)))
{
CoTaskMemFree(*ppszName);
hr = SHStrDupW(sz, ppszName);
}
}
return hr;
}
STDMETHODIMP CShellItem::GetDisplayName(SIGDN sigdnName, LPOLESTR *ppszName)
{
SHGDNF flags;
if (_SupportedName(sigdnName, &flags))
{
IShellFolder *psf;
HRESULT hr = _BindToParent(IID_PPV_ARG(IShellFolder, &psf));
if (SUCCEEDED(hr))
{
STRRET str;
hr = IShellFolder_GetDisplayNameOf(psf, _pidlChild, flags, &str, 0);
if (SUCCEEDED(hr))
{
hr = StrRetToStrW(&str, _pidlChild, ppszName);
if (SUCCEEDED(hr) && (int)flags != (int)sigdnName)
{
hr = _FixupName(sigdnName, ppszName);
}
}
psf->Release();
}
return hr;
}
return E_INVALIDARG;
}
void CShellItem::_FixupAttributes(IShellFolder *psf, SFGAOF sfgaoMask)
{
// APPCOMPAT: The following if statement and its associated body is an APP HACK for pagis pro
// folder. Which specifies SFGAO_FOLDER and SFGAO_FILESYSTEM but it doesn't specify SFGAO_STORAGEANCESTOR
// This APP HACK basically checks for this condition and provides SFGAO_STORAGEANCESTOR bit.
if (_sfgaoKnown & SFGAO_FOLDER)
{
if ((!(_sfgaoKnown & SFGAO_FILESYSANCESTOR) && (sfgaoMask & SFGAO_FILESYSANCESTOR))
|| ((_sfgaoKnown & SFGAO_CANMONIKER) && !(_sfgaoKnown & SFGAO_STORAGEANCESTOR) && (sfgaoMask & SFGAO_STORAGEANCESTOR)))
{
OBJCOMPATFLAGS ocf = SHGetObjectCompatFlags(psf, NULL);
if (ocf & OBJCOMPATF_NEEDSFILESYSANCESTOR)
{
_sfgaoKnown |= SFGAO_FILESYSANCESTOR;
}
if (ocf & OBJCOMPATF_NEEDSSTORAGEANCESTOR)
{
// switch SFGAO_CANMONIKER -> SFGAO_STORAGEANCESTOR
_sfgaoKnown |= SFGAO_STORAGEANCESTOR;
_sfgaoKnown &= ~SFGAO_CANMONIKER;
}
}
}
}
STDMETHODIMP CShellItem::GetAttributes(SFGAOF sfgaoMask, SFGAOF *psfgaoFlags)
{
HRESULT hr = S_OK;
// see if we cached this bits before...
if ((sfgaoMask & _sfgaoTried) != sfgaoMask)
{
IShellFolder *psf;
hr = _BindToParent(IID_PPV_ARG(IShellFolder, &psf));
if (SUCCEEDED(hr))
{
// we cache all the bits except VALIDATE
_sfgaoTried |= (sfgaoMask & ~SFGAO_VALIDATE);
SFGAOF sfgao = sfgaoMask;
hr = psf->GetAttributesOf(1, &_pidlChild, &sfgao);
if (SUCCEEDED(hr))
{
// we cache all the bits except VALIDATE
_sfgaoKnown |= (sfgao & ~SFGAO_VALIDATE);
_FixupAttributes(psf, sfgaoMask);
}
psf->Release();
}
}
*psfgaoFlags = _sfgaoKnown & sfgaoMask;
if (SUCCEEDED(hr))
{
// we return S_OK
// only if the bits set match
// exactly the bits requested
if (*psfgaoFlags == sfgaoMask)
hr = S_OK;
else
hr = S_FALSE;
}
return hr;
}
STDMETHODIMP CShellItem::Compare(IShellItem *psi, SICHINTF hint, int *piOrder)
{
*piOrder = 0;
HRESULT hr = IsSameObject(SAFECAST(this, IShellItem *), psi) ? S_OK : E_FAIL;
if (FAILED(hr))
{
IShellFolder *psf;
hr = _BindToParent(IID_PPV_ARG(IShellFolder, &psf));
if (SUCCEEDED(hr))
{
IParentAndItem *pfai;
hr = psi->QueryInterface(IID_PPV_ARG(IParentAndItem, &pfai));
if (SUCCEEDED(hr))
{
IShellFolder *psfOther;
LPITEMIDLIST pidlParent, pidlChild;
hr = pfai->GetParentAndItem(&pidlParent, &psfOther, &pidlChild);
if (SUCCEEDED(hr))
{
if (IsSameObject(psf, psfOther) || ILIsEqual(_pidlParent, pidlParent))
{
hr = psf->CompareIDs(hint & 0xf0000000, _pidlChild, pidlChild);
}
else
{
// these items have a different parent
// compare the absolute pidls
LPITEMIDLIST pidlOther;
hr = SHGetIDListFromUnk(psi, &pidlOther);
if (SUCCEEDED(hr))
{
IShellFolder *psfDesktop;
hr = SHGetDesktopFolder(&psfDesktop);
if (SUCCEEDED(hr))
{
hr = psfDesktop->CompareIDs(hint & 0xf0000000, _pidlSelf, pidlOther);
psfDesktop->Release();
}
ILFree(pidlOther);
}
}
if (SUCCEEDED(hr))
{
*piOrder = ShortFromResult(hr);
if (*piOrder)
hr = S_FALSE;
else
hr = S_OK;
}
psfOther->Release();
ILFree(pidlParent);
ILFree(pidlChild);
}
pfai->Release();
}
psf->Release();
}
}
return hr;
}
// IParentAndItem
STDMETHODIMP CShellItem::SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psfParent, LPCITEMIDLIST pidlChild)
{
// require to have a Parent if making this call. If don't then use SetIDList
if (!pidlParent && !psfParent)
{
RIPMSG(0, "Tried to Call SetParent without a parent");
return E_INVALIDARG;
}
LPITEMIDLIST pidlFree = NULL;
if ((NULL == pidlParent) && psfParent)
{
if (SUCCEEDED(SHGetIDListFromUnk(psfParent, &pidlFree)))
{
pidlParent = pidlFree;
}
}
if (!ILIsEmpty(_ILNext(pidlChild)))
{
// if more than on item in the child pidl don't use the parent IShellFolder*
// could revist and bind from this parent to get a new parent so don't have
// to BindObject through the entire pidl path.
psfParent = NULL;
}
HRESULT hr = E_FAIL;
if (pidlParent)
{
_Reset();
hr = SHILCombine(pidlParent, pidlChild, &_pidlSelf);
if (SUCCEEDED(hr))
{
// setup pidls so _pidlChild is a single item.
if (_pidlParent = ILCloneParent(_pidlSelf))
{
_pidlChild = ILFindLastID(_pidlSelf);
PPUNK_SET(&_psfParent, psfParent);
#ifdef DEBUG
if (psfParent)
{
LPITEMIDLIST pidlD;
if (SUCCEEDED(SHGetIDListFromUnk(psfParent, &pidlD)))
{
ASSERT(ILIsEqual(pidlD, pidlParent));
ILFree(pidlD);
}
}
#endif //DEBUG
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
ILFree(pidlFree); // maybe NULL
return hr;
}
STDMETHODIMP CShellItem::GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidl)
{
if (ppsf)
{
_BindToParent(IID_PPV_ARG(IShellFolder, ppsf));
}
if (ppidlParent)
{
if (_pidlParent)
{
*ppidlParent = ILClone(_pidlParent);
}
else
{
*ppidlParent = NULL;
}
}
if (ppidl)
*ppidl = ILClone(_pidlChild);
HRESULT hr = S_OK;
if ((ppidlParent && !*ppidlParent)
|| (ppsf && !*ppsf)
|| (ppidl && !*ppidl))
{
// this is failure
// but we dont know what failed
if (ppsf && *ppsf)
{
(*ppsf)->Release();
*ppsf = NULL;
}
if (ppidlParent)
{
ILFree(*ppidlParent);
*ppidlParent = NULL;
}
if (ppidl)
{
ILFree(*ppidl);
*ppidl = NULL;
}
hr = E_OUTOFMEMORY;
}
return hr;
}
STDAPI CShellItem_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
CShellItem *psi = new CShellItem();
if (psi)
{
HRESULT hr = psi->QueryInterface(riid, ppv);
psi->Release();
return hr;
}
return E_OUTOFMEMORY;
}
class CShellItemEnum : IEnumShellItems, public CObjectWithSite
{
public:
CShellItemEnum();
STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder,IShellFolder *psf, DWORD dwFlags,UINT cidl,LPCITEMIDLIST *apidl);
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppvOut);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP Next(ULONG celt, IShellItem **rgelt, ULONG *pceltFetched);
STDMETHODIMP Skip(ULONG celt);
STDMETHODIMP Reset();
STDMETHODIMP Clone(IEnumShellItems **ppenum);
private:
virtual ~CShellItemEnum();
HRESULT _EnsureEnum();
LONG _cRef;
DWORD _dwFlags;
IShellFolder *_psf;
IEnumIDList *_penum;
LPITEMIDLIST _pidlFolder;
};
STDMETHODIMP CShellItemEnum::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CShellItemEnum, IEnumShellItems),
QITABENT(CShellItemEnum, IObjectWithSite),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CShellItemEnum::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CShellItemEnum::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
STDMETHODIMP CShellItemEnum::Next(ULONG celt, IShellItem **rgelt, ULONG *pceltFetched)
{
HRESULT hr = _EnsureEnum();
if (FAILED(hr))
return hr;
ULONG uTemp;
if (!pceltFetched)
pceltFetched = &uTemp;
*pceltFetched = 0;
while (celt--)
{
LPITEMIDLIST pidl;
ULONG cFetched;
hr = _penum->Next(1, &pidl, &cFetched);
if (S_OK == hr)
{
hr = SHCreateShellItem(_pidlFolder, _psf, pidl, &rgelt[*pceltFetched]);
if (SUCCEEDED(hr))
(*pceltFetched)++;
ILFree(pidl);
}
if (S_OK != hr)
break;
}
if (SUCCEEDED(hr))
{
hr = *pceltFetched ? S_OK : S_FALSE;
}
else
{
for (UINT i = 0; i < *pceltFetched; i++)
{
ATOMICRELEASE(rgelt[i]);
}
*pceltFetched = 0;
}
return hr;
}
STDMETHODIMP CShellItemEnum::Skip(ULONG celt)
{
HRESULT hr = _EnsureEnum();
if (SUCCEEDED(hr))
hr = _penum->Skip(celt);
return hr;
}
STDMETHODIMP CShellItemEnum::Reset()
{
HRESULT hr = _EnsureEnum();
if (SUCCEEDED(hr))
hr = _penum->Reset();
return hr;
}
STDMETHODIMP CShellItemEnum::Clone(IEnumShellItems **ppenum)
{
return E_NOTIMPL;
}
HRESULT CShellItemEnum::_EnsureEnum()
{
if (_penum)
return S_OK;
HRESULT hr = E_FAIL;
if (_psf)
{
HWND hwnd = NULL;
IUnknown_GetWindow(_punkSite, &hwnd);
// if didn't get an enum in Initialize then enumerate the
// entire folder.
hr = _psf->EnumObjects(hwnd, _dwFlags, &_penum);
}
return hr;
}
CShellItemEnum::CShellItemEnum()
: _cRef(1)
{
ASSERT(NULL == _psf);
ASSERT(NULL == _penum);
ASSERT(NULL == _pidlFolder);
}
STDMETHODIMP CShellItemEnum::Initialize(LPCITEMIDLIST pidlFolder, IShellFolder *psf, DWORD dwFlags, UINT cidl, LPCITEMIDLIST *apidl)
{
HRESULT hr = E_FAIL;
_dwFlags = dwFlags;
_psf = psf;
_psf->AddRef();
if (NULL == _pidlFolder)
{
hr = SHGetIDListFromUnk(_psf, &_pidlFolder);
}
else
{
hr = SHILClone(pidlFolder, &_pidlFolder);
}
if (SUCCEEDED(hr) && cidl)
{
ASSERT(apidl);
// if want to enum with other flags or combos need to implement the filter
ASSERT(_dwFlags == (SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN));
hr = CreateIEnumIDListOnIDLists(apidl, cidl, &_penum);
}
// on error let our destructor do the cleanup
return hr;
}
CShellItemEnum::~CShellItemEnum()
{
ATOMICRELEASE(_penum);
ATOMICRELEASE(_psf);
ILFree(_pidlFolder);
}
HRESULT _CreateShellItemEnum(LPCITEMIDLIST pidlFolder,IShellFolder *psf,IBindCtx *pbc, REFGUID rbhid,
UINT cidl, LPCITEMIDLIST *apidl,
REFIID riid, void **ppv)
{
DWORD dwFlags;
HRESULT hr = E_FAIL;
LPCITEMIDLIST *pidlEnum = NULL;
UINT mycidl = 0;
LPITEMIDLIST *myppidl = NULL;;
if (IsEqualGUID(rbhid, BHID_StorageEnum))
dwFlags = SHCONTF_STORAGE;
else
dwFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN;
CShellItemEnum *psie = new CShellItemEnum();
if (psie)
{
hr = psie->Initialize(pidlFolder, psf, dwFlags, cidl, apidl);
if (SUCCEEDED(hr))
{
hr = psie->QueryInterface(riid, ppv);
}
psie->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
HRESULT _CreateEnumHelper(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv)
{
HRESULT hr = E_FAIL;
IShellFolder *psf;
ASSERT(psi);
if (psi)
{
hr = psi->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARG(IShellFolder, &psf));
if (SUCCEEDED(hr))
{
hr = _CreateShellItemEnum(NULL,psf,pbc,rbhid,0,NULL,riid,ppv);
psf->Release();
}
}
return hr;
}
class CShellItemArray : public IShellItemArray
{
public:
CShellItemArray();
~CShellItemArray();
HRESULT Initialize(LPCITEMIDLIST pidlParent,IShellFolder *psf,UINT cidl,LPCITEMIDLIST *ppidl);
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void) ;
STDMETHODIMP_(ULONG) Release(void);
// IShellItemArray
STDMETHODIMP BindToHandler(
IBindCtx *pbc,
REFGUID rbhid,
REFIID riid,
void **ppvOut);
STDMETHODIMP GetAttributes(
SIATTRIBFLAGS dwAttribFlags,
SFGAOF sfgaoMask,
SFGAOF *psfgaoAttribs);
STDMETHODIMP GetCount(DWORD *pdwNumItems);
STDMETHODIMP GetItemAt(DWORD dwIndex,IShellItem **ppsi);
STDMETHODIMP EnumItems(IEnumShellItems **ppenumShellItems);
private:
HRESULT _CloneIDListArray(UINT cidl, LPCITEMIDLIST *apidl, UINT *pcidl, LPITEMIDLIST **papidl);
IShellFolder *_pshf;
LPITEMIDLIST _pidlParent;
LPITEMIDLIST *_ppidl;
UINT _cidl;
LONG _cRef;
IDataObject *_pdo; // cached data object.
DWORD _dwAttribAndCacheResults;
DWORD _dwAttribAndCacheMask;
DWORD _dwAttribCompatCacheResults;
DWORD _dwAttribCompatCacheMask;
BOOL _fItemPidlsRagged; // set to true if have any rugged pidls.
};
CShellItemArray::CShellItemArray()
{
ASSERT(0 == _cidl);
ASSERT(NULL == _ppidl);
ASSERT(NULL == _pshf);
ASSERT(NULL == _pdo);
_fItemPidlsRagged = TRUE;
_cRef = 1;
}
CShellItemArray::~CShellItemArray()
{
ATOMICRELEASE(_pdo);
ATOMICRELEASE(_pshf);
ILFree(_pidlParent); // may be null
if (NULL != _ppidl)
{
FreeIDListArray(_ppidl,_cidl);
}
}
HRESULT CShellItemArray::Initialize(LPCITEMIDLIST pidlParent, IShellFolder *psf, UINT cidl, LPCITEMIDLIST *ppidl)
{
if ((cidl > 1) && !ppidl || !psf)
{
return E_INVALIDARG;
}
if (pidlParent)
{
_pidlParent = ILClone(pidlParent); // proceed on alloc failure, just won't use.
}
_pshf = psf;
_pshf->AddRef();
HRESULT hr = S_OK;
if (cidl)
{
// if there are items then make a copy
hr = _CloneIDListArray(cidl, ppidl, &_cidl, &_ppidl);
}
// on error rely on destructor to do the cleanup
return hr;
}
// IUnknown
STDMETHODIMP CShellItemArray::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CShellItemArray, IShellItemArray),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CShellItemArray::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CShellItemArray::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
STDMETHODIMP CShellItemArray::BindToHandler(IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppvOut)
{
HRESULT hr = E_FAIL;
if (_pshf)
{
// currently only allow bind to IDataObject and
// cache the result.
if (BHID_DataObject == rbhid)
{
if (NULL == _pdo)
{
_pshf->GetUIObjectOf(NULL, _cidl, (LPCITEMIDLIST *)_ppidl, IID_PPV_ARG_NULL(IDataObject, &_pdo));
}
if (_pdo)
{
hr = _pdo->QueryInterface(riid, ppvOut);
}
}
else
{
hr = E_NOINTERFACE;
}
}
return hr;
}
// This should probably take a flag that does an or'ing of attributes but this
// currrently isn't implemented. Do have comments on what the changes would be.
HRESULT CShellItemArray::GetAttributes(SIATTRIBFLAGS dwAttribFlags, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs)
{
DWORD dwAttrib;
HRESULT hr = E_FAIL;
if (dwAttribFlags > (dwAttribFlags & SIATTRIBFLAGS_MASK))
{
ASSERT(dwAttribFlags <= (dwAttribFlags & SIATTRIBFLAGS_MASK));
return E_INVALIDARG;
}
if (SIATTRIBFLAGS_OR == dwAttribFlags)
{
ASSERT(SIATTRIBFLAGS_OR != dwAttribFlags); // or'ing is currently not implemented.
return E_INVALIDARG;
}
if (_pshf)
{
DWORD dwAttribMask = sfgaoMask;
DWORD *pdwCacheMask = NULL;
DWORD *pdwCacheResults = NULL;
// setup to point to proper Cached values.
switch(dwAttribFlags)
{
case SIATTRIBFLAGS_AND:
pdwCacheMask = &_dwAttribAndCacheMask;
pdwCacheResults = &_dwAttribAndCacheResults;
break;
case SIATTRIBFLAGS_APPCOMPAT:
pdwCacheMask = &_dwAttribCompatCacheMask;
pdwCacheResults = &_dwAttribCompatCacheResults;
break;
default:
ASSERT(0); // i don't know how to handle this flag.
break;
}
dwAttribMask &= ~(*pdwCacheMask); // only ask for the bits we don't already have.
dwAttrib = dwAttribMask;
if (dwAttrib)
{
if (0 == _cidl)
{
dwAttrib = 0;
}
else
{
// if know this is not a ragged pidl and calling with the APPCOMPAT flag
// then calls GetAttributesOf for all the items in one call to the
// shellFolder.
if (!_fItemPidlsRagged && (SIATTRIBFLAGS_APPCOMPAT == dwAttribFlags))
{
hr = _pshf->GetAttributesOf(_cidl, (LPCITEMIDLIST *)_ppidl, &dwAttrib);
}
else
{
LPITEMIDLIST *pCurItem = _ppidl;
UINT itemCount = _cidl;
DWORD dwAttribLoopResult = -1; // set all result bits for and, if going to or set to zero
while (itemCount--)
{
DWORD dwAttribTemp = dwAttrib;
IShellFolder *psfNew;
LPCITEMIDLIST pidlChild;
hr = SHBindToFolderIDListParent(_pshf, *pCurItem, IID_PPV_ARG(IShellFolder, &psfNew), &pidlChild);
if (SUCCEEDED(hr))
{
hr = psfNew->GetAttributesOf(1, &pidlChild, &dwAttribTemp);
psfNew->Release();
}
if (FAILED(hr))
{
break;
}
dwAttribLoopResult &= dwAttribTemp; // could also do an or'ing here
if (0 == dwAttribLoopResult) // if no attribs set and doing an and we can stop.
{
break;
}
++pCurItem;
}
dwAttrib = dwAttribLoopResult; // update the attrib
}
}
}
else
{
hr = S_OK;
}
if (SUCCEEDED(hr))
{
// remember those bits that we just got +
// those that we computed before
*pdwCacheResults = dwAttrib | (*pdwCacheResults & *pdwCacheMask);
// we know these are now valid, keep track of those +
// if they gave us more than we asked for, cache them too
*pdwCacheMask |= dwAttribMask | dwAttrib;
// don't return anything that wasn't asked for. defview code relies on this.
*psfgaoAttribs = (*pdwCacheResults & sfgaoMask);
}
}
return hr;
}
STDMETHODIMP CShellItemArray::GetCount(DWORD *pdwNumItems)
{
*pdwNumItems = _cidl;
return S_OK;
}
// way to get zero based index ShellItem without having to
// go through enumerator overhead.
STDMETHODIMP CShellItemArray::GetItemAt(DWORD dwIndex, IShellItem **ppsi)
{
*ppsi = NULL;
if (dwIndex >= _cidl)
{
return E_FAIL;
}
ASSERT(_ppidl);
LPITEMIDLIST pidl = *(_ppidl + dwIndex);
// if GetItemAt is called a lot may want to
// a) get the pshf pidl to pass to SHCreateshellItem so doesn't have to create each time
// b) see if always asking for first item and is so maybe cache the shellItem
return SHCreateShellItem(NULL, _pshf, pidl, ppsi);
}
STDMETHODIMP CShellItemArray::EnumItems(IEnumShellItems **ppenumShellItems)
{
return _CreateShellItemEnum(_pidlParent, _pshf, NULL, GUID_NULL, _cidl,
(LPCITEMIDLIST *) _ppidl, IID_PPV_ARG(IEnumShellItems, ppenumShellItems));
}
HRESULT CShellItemArray::_CloneIDListArray(UINT cidl, LPCITEMIDLIST *apidl, UINT *pcidl, LPITEMIDLIST **papidl)
{
HRESULT hr;
LPITEMIDLIST *ppidl;
*papidl = NULL;
_fItemPidlsRagged = FALSE;
if (cidl && apidl)
{
ppidl = (LPITEMIDLIST *)LocalAlloc(LPTR, cidl * sizeof(*ppidl));
if (ppidl)
{
LPITEMIDLIST *apidlFrom = (LPITEMIDLIST *) apidl;
LPITEMIDLIST *apidlTo = ppidl;
hr = S_OK;
for (UINT i = 0; i < cidl ; i++)
{
hr = SHILClone(*apidlFrom, apidlTo);
if (FAILED(hr))
{
FreeIDListArray(ppidl, i);
ppidl = NULL;
break;
}
// if more than one item in list then set singeItemPidls to false
if (!ILIsEmpty(_ILNext(*apidlTo)))
{
_fItemPidlsRagged = TRUE;
}
++apidlFrom;
++apidlTo;
}
}
else
hr = E_OUTOFMEMORY;
}
else
{
ppidl = NULL;
hr = S_FALSE; // success by empty
}
if (SUCCEEDED(hr))
{
*papidl = ppidl;
*pcidl = cidl;
}
else
{
_fItemPidlsRagged = TRUE;
}
return hr;
}
SHSTDAPI SHCreateShellItemArray(LPCITEMIDLIST pidlParent, IShellFolder *psf, UINT cidl,
LPCITEMIDLIST *ppidl, IShellItemArray **ppsiItemArray)
{
HRESULT hr = E_OUTOFMEMORY;
CShellItemArray *pItemArray = new CShellItemArray();
if (pItemArray)
{
hr = pItemArray->Initialize(pidlParent, psf, cidl, ppidl);
if (FAILED(hr))
{
pItemArray->Release();
pItemArray = NULL;
}
}
*ppsiItemArray = pItemArray;
return hr;
}