915 lines
28 KiB
C++
915 lines
28 KiB
C++
#include "shellprv.h"
|
|
#include "dpa.h"
|
|
#include "datautil.h"
|
|
|
|
typedef enum
|
|
{
|
|
NSWALK_DONTWALK,
|
|
NSWALK_FOLDER,
|
|
NSWALK_ITEM,
|
|
NSWALK_LINK
|
|
} NSWALK_ELEMENT_TYPE;
|
|
|
|
class CNamespaceWalk : public INamespaceWalk
|
|
{
|
|
public:
|
|
CNamespaceWalk();
|
|
|
|
// IUnknown
|
|
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
|
|
STDMETHODIMP_(ULONG) AddRef();
|
|
STDMETHODIMP_(ULONG) Release();
|
|
|
|
// INamespaceWalk
|
|
STDMETHODIMP Walk(IUnknown *punkToWalk, DWORD dwFlags, int cDepth, INamespaceWalkCB *pnswcb);
|
|
STDMETHODIMP GetIDArrayResult(UINT *pcItems, LPITEMIDLIST **pppidl);
|
|
|
|
private:
|
|
~CNamespaceWalk();
|
|
|
|
static int CALLBACK _FreeItems(LPITEMIDLIST pidl, IShellFolder *psf);
|
|
static int CALLBACK _CompareItems(LPITEMIDLIST p1, LPITEMIDLIST p2, IShellFolder *psf);
|
|
HRESULT _EnsureDPA();
|
|
HRESULT _AddItem(IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
HRESULT _AppendFull(LPCITEMIDLIST pidlFull);
|
|
HRESULT _EnumFolder(IShellFolder *psf, LPCITEMIDLIST pidlFirst, CDPA<UNALIGNED ITEMIDLIST> *pdpaItems);
|
|
HRESULT _GetShortcutTarget(IShellFolder *psf, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlTarget);
|
|
BOOL _IsFolderTarget(IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
HRESULT _WalkView(IFolderView *pfv);
|
|
HRESULT _WalkFolder(IShellFolder *psf, LPCITEMIDLIST pidlFirst, int cDepth);
|
|
HRESULT _WalkDataObject(IDataObject *pdtobj);
|
|
HRESULT _WalkParentAndItem(IParentAndItem *ppai);
|
|
HRESULT _WalkIDList(IShellFolder *psfRoot, LPCITEMIDLIST pidl, int cDepth, int cFolderDepthDelta);
|
|
HRESULT _WalkFolderItem(IShellFolder *psf, LPCITEMIDLIST pidl, int cDepth);
|
|
HRESULT _WalkShortcut(IShellFolder *psf, LPCITEMIDLIST pidl, int cDepth, int cFolderDepthDelta);
|
|
|
|
NSWALK_ELEMENT_TYPE _GetType(IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
BOOL _OneImpliesAll(IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
|
|
HRESULT _ProgressDialogQueryCancel();
|
|
void _ProgressDialogBegin();
|
|
void _ProgressDialogUpdate(LPCWSTR pszText);
|
|
void _ProgressDialogEnd();
|
|
|
|
LONG _cRef;
|
|
DWORD _dwFlags;
|
|
int _cDepthMax;
|
|
INamespaceWalkCB *_pnswcb;
|
|
IActionProgressDialog *_papd;
|
|
IActionProgress *_pap;
|
|
#ifdef DEBUG
|
|
TCHAR _szLastFolder[MAX_PATH]; // to track what we failed on
|
|
#endif
|
|
CDPA<UNALIGNED ITEMIDLIST> _dpaItems;
|
|
};
|
|
|
|
|
|
STDAPI CNamespaceWalk_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
|
|
{
|
|
CNamespaceWalk *pnsw = new CNamespaceWalk();
|
|
if (!pnsw)
|
|
return E_OUTOFMEMORY;
|
|
|
|
HRESULT hr = pnsw->QueryInterface(riid, ppv);
|
|
pnsw->Release();
|
|
return hr;
|
|
}
|
|
|
|
CNamespaceWalk::CNamespaceWalk() : _cRef(1)
|
|
{
|
|
_papd = NULL;
|
|
_pap = NULL;
|
|
}
|
|
|
|
CNamespaceWalk::~CNamespaceWalk()
|
|
{
|
|
ASSERT(!_papd);
|
|
ASSERT(!_pap);
|
|
|
|
if ((HDPA)_dpaItems)
|
|
_dpaItems.DestroyCallbackEx(_FreeItems, (IShellFolder *)NULL);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CNamespaceWalk::AddRef()
|
|
{
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CNamespaceWalk::Release()
|
|
{
|
|
if (InterlockedDecrement(&_cRef))
|
|
return _cRef;
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CNamespaceWalk, INamespaceWalk),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
int CALLBACK CNamespaceWalk::_FreeItems(LPITEMIDLIST pidl, IShellFolder *psf)
|
|
{
|
|
ILFree(pidl);
|
|
return 1;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_EnsureDPA()
|
|
{
|
|
return (HDPA)_dpaItems ? S_OK : (_dpaItems.Create(10) ? S_OK : E_OUTOFMEMORY);
|
|
}
|
|
|
|
// consumes pidl in all cases (success and failure)
|
|
|
|
HRESULT CNamespaceWalk::_AppendFull(LPCITEMIDLIST pidlFull)
|
|
{
|
|
HRESULT hr = _ProgressDialogQueryCancel(); // ERROR_CANCELLED -> cancelled
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (NSWF_DONT_ACCUMULATE_RESULT & _dwFlags)
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = _EnsureDPA();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPITEMIDLIST pidlClone;
|
|
hr = SHILClone(pidlFull, &pidlClone);
|
|
if (SUCCEEDED(hr) && (-1 == _dpaItems.AppendPtr(pidlClone)))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
ILFree(pidlClone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_AddItem(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LPITEMIDLIST pidlFull = NULL;
|
|
|
|
if (!(NSWF_DONT_ACCUMULATE_RESULT & _dwFlags))
|
|
{
|
|
hr = SUCCEEDED(SHFullIDListFromFolderAndItem(psf, pidl, &pidlFull)) ? S_OK : S_FALSE;//couldn't get the pidl? Just skip the item
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
hr = _pnswcb ? _pnswcb->FoundItem(psf, pidl) : S_OK;
|
|
if (S_OK == hr)
|
|
{
|
|
hr = _AppendFull(pidlFull);
|
|
}
|
|
ILFree(pidlFull);
|
|
}
|
|
return SUCCEEDED(hr) ? S_OK : hr; // filter out S_FALSE success cases
|
|
}
|
|
|
|
int CALLBACK CNamespaceWalk::_CompareItems(LPITEMIDLIST p1, LPITEMIDLIST p2, IShellFolder *psf)
|
|
{
|
|
HRESULT hr = psf->CompareIDs(0, p1, p2);
|
|
return (short)HRESULT_CODE(hr);
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_EnumFolder(IShellFolder *psf, LPCITEMIDLIST pidlFirst, CDPA<UNALIGNED ITEMIDLIST> *pdpaItems)
|
|
{
|
|
CDPA<UNALIGNED ITEMIDLIST> dpaItems;
|
|
HRESULT hr = dpaItems.Create(16) ? S_OK : E_OUTOFMEMORY;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IEnumIDList *penum;
|
|
if (S_OK == psf->EnumObjects(NULL, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &penum))
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
ULONG c;
|
|
while (SUCCEEDED(hr) && (S_OK == penum->Next(1, &pidl, &c)))
|
|
{
|
|
if (-1 == dpaItems.AppendPtr(pidl))
|
|
{
|
|
ILFree(pidl);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
hr = _ProgressDialogQueryCancel();
|
|
}
|
|
}
|
|
penum->Release();
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
dpaItems.SortEx(_CompareItems, psf);
|
|
|
|
if (pidlFirst && !(NSWF_FLAG_VIEWORDER & _dwFlags))
|
|
{
|
|
// rotate the items array so pidlFirst is first in the list
|
|
// cast for bogus SearchEx decl
|
|
int iMid = dpaItems.SearchEx((LPITEMIDLIST)pidlFirst, 0, _CompareItems, psf, DPAS_SORTED);
|
|
if (-1 != iMid)
|
|
{
|
|
int cItems = dpaItems.GetPtrCount();
|
|
CDPA<UNALIGNED ITEMIDLIST> dpaTemp;
|
|
if (dpaTemp.Create(cItems))
|
|
{
|
|
for (int i = 0; i < cItems; i++)
|
|
{
|
|
dpaTemp.SetPtr(i, dpaItems.GetPtr(iMid++));
|
|
if (iMid >= cItems)
|
|
iMid = 0;
|
|
}
|
|
|
|
for (int i = 0; i < cItems; i++)
|
|
{
|
|
dpaItems.SetPtr(i, dpaTemp.GetPtr(i));
|
|
}
|
|
dpaTemp.Destroy(); // don't free the pidls, just the array
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// pidlFirst not found in the enum, it might be hidden or filters
|
|
// out some way, but make sure this always ends up in the dpa in this case
|
|
LPITEMIDLIST pidlClone = ILClone(pidlFirst);
|
|
if (pidlClone)
|
|
{
|
|
dpaItems.InsertPtr(0, pidlClone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
dpaItems.DestroyCallbackEx(_FreeItems, psf);
|
|
dpaItems = NULL;
|
|
}
|
|
|
|
*pdpaItems = dpaItems;
|
|
return hr;
|
|
}
|
|
|
|
NSWALK_ELEMENT_TYPE CNamespaceWalk::_GetType(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
NSWALK_ELEMENT_TYPE nwet = NSWALK_DONTWALK;
|
|
|
|
DWORD dwAttribs = SHGetAttributes(psf, pidl, SFGAO_FOLDER | SFGAO_STREAM | SFGAO_FILESYSTEM | SFGAO_LINK);
|
|
if ((dwAttribs & SFGAO_FOLDER) && (!(dwAttribs & SFGAO_STREAM) || (NSWF_TRAVERSE_STREAM_JUNCTIONS & _dwFlags)))
|
|
{
|
|
nwet = NSWALK_FOLDER;
|
|
}
|
|
else if ((dwAttribs & SFGAO_LINK) && !(NSWF_DONT_TRAVERSE_LINKS & _dwFlags))
|
|
{
|
|
nwet = NSWALK_LINK;
|
|
}
|
|
else if ((dwAttribs & SFGAO_FILESYSTEM) || !(NSWF_FILESYSTEM_ONLY & _dwFlags))
|
|
{
|
|
nwet = NSWALK_ITEM;
|
|
}
|
|
return nwet;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_WalkIDList(IShellFolder *psfRoot, LPCITEMIDLIST pidl, int cDepth, int cFolderDepthDelta)
|
|
{
|
|
IShellFolder *psf;
|
|
LPCITEMIDLIST pidlLast;
|
|
HRESULT hr = SHBindToFolderIDListParent(psfRoot, pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
switch (_GetType(psf, pidlLast))
|
|
{
|
|
case NSWALK_FOLDER:
|
|
hr = _WalkFolderItem(psf, pidlLast, cDepth + cFolderDepthDelta);
|
|
break;
|
|
|
|
case NSWALK_LINK:
|
|
hr = _WalkShortcut(psf, pidlLast, cDepth, cFolderDepthDelta);
|
|
break;
|
|
|
|
case NSWALK_ITEM:
|
|
hr = _AddItem(psf, pidlLast);
|
|
break;
|
|
}
|
|
psf->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_WalkShortcut(IShellFolder *psf, LPCITEMIDLIST pidl, int cDepth, int cFolderDepthDelta)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// If an error occured trying to resolve a shortcut then we simply skip
|
|
// this shortcut and continue
|
|
|
|
LPITEMIDLIST pidlTarget;
|
|
if (SUCCEEDED(_GetShortcutTarget(psf, pidl, &pidlTarget)))
|
|
{
|
|
hr = _WalkIDList(NULL, pidlTarget, cDepth, cFolderDepthDelta);
|
|
ILFree(pidlTarget);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_GetShortcutTarget(IShellFolder *psf, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlTarget)
|
|
{
|
|
*ppidlTarget = NULL;
|
|
|
|
IShellLink *psl;
|
|
if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidl, IID_PPV_ARG_NULL(IShellLink, &psl))))
|
|
{
|
|
if (S_OK == psl->Resolve(NULL, SLR_UPDATE | SLR_NO_UI))
|
|
{
|
|
psl->GetIDList(ppidlTarget);
|
|
}
|
|
psl->Release();
|
|
}
|
|
|
|
return *ppidlTarget ? S_OK : E_FAIL;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_WalkFolder(IShellFolder *psf, LPCITEMIDLIST pidlFirst, int cDepth)
|
|
{
|
|
if (cDepth > _cDepthMax)
|
|
return S_OK; // done
|
|
|
|
CDPA<UNALIGNED ITEMIDLIST> dpaItems;
|
|
HRESULT hr = _EnumFolder(psf, pidlFirst, &dpaItems);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
UINT cFolders = 0;
|
|
// breadth first traversal, so do the items (non folders) first
|
|
// (this includes shortcuts and those can point to folders)
|
|
|
|
for (int i = 0; (S_OK == hr) && (i < dpaItems.GetPtrCount()); i++)
|
|
{
|
|
switch (_GetType(psf, dpaItems.GetPtr(i)))
|
|
{
|
|
case NSWALK_FOLDER:
|
|
cFolders++;
|
|
break;
|
|
|
|
case NSWALK_LINK:
|
|
hr = _WalkShortcut(psf, dpaItems.GetPtr(i), cDepth, 1);
|
|
break;
|
|
|
|
case NSWALK_ITEM:
|
|
hr = _AddItem(psf, dpaItems.GetPtr(i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// no go deep into the folders
|
|
|
|
if (cFolders)
|
|
{
|
|
for (int i = 0; (S_OK == hr) && (i < dpaItems.GetPtrCount()); i++)
|
|
{
|
|
if (NSWALK_FOLDER == _GetType(psf, dpaItems.GetPtr(i)))
|
|
{
|
|
hr = _WalkFolderItem(psf, dpaItems.GetPtr(i), cDepth + 1);
|
|
}
|
|
}
|
|
}
|
|
dpaItems.DestroyCallbackEx(_FreeItems, psf);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_WalkFolderItem(IShellFolder *psf, LPCITEMIDLIST pidl, int cDepth)
|
|
{
|
|
IShellFolder *psfNew;
|
|
HRESULT hr = psf->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfNew));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
#ifdef DEBUG
|
|
DisplayNameOf(psf, pidl, SHGDN_FORPARSING, _szLastFolder, ARRAYSIZE(_szLastFolder));
|
|
#endif
|
|
hr = _pnswcb ? _pnswcb->EnterFolder(psf, pidl) : S_OK;
|
|
if (S_OK == hr)
|
|
{
|
|
// Update progress dialog; note we only update the progress dialog
|
|
// with the folder names we're currently traversing. Updating on a
|
|
// per filename basis just caused far too much flicker, looked bad.
|
|
if (NSWF_SHOW_PROGRESS & _dwFlags)
|
|
{
|
|
WCHAR sz[MAX_PATH];
|
|
if (SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_NORMAL, sz, ARRAYSIZE(sz))))
|
|
_ProgressDialogUpdate(sz);
|
|
|
|
hr = _ProgressDialogQueryCancel(); // ERROR_CANCELLED -> cancelled
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _WalkFolder(psfNew, NULL, cDepth);
|
|
if (_pnswcb)
|
|
_pnswcb->LeaveFolder(psf, pidl); // ignore result
|
|
}
|
|
}
|
|
hr = SUCCEEDED(hr) ? S_OK : hr; // filter out S_FALSE success cases
|
|
psfNew->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
BOOL CNamespaceWalk::_IsFolderTarget(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
BOOL bIsFolder = FALSE;
|
|
|
|
LPITEMIDLIST pidlTarget;
|
|
if (SUCCEEDED(_GetShortcutTarget(psf, pidl, &pidlTarget)))
|
|
{
|
|
bIsFolder = SHGetAttributes(NULL, pidlTarget, SFGAO_FOLDER);
|
|
ILFree(pidlTarget);
|
|
}
|
|
return bIsFolder;
|
|
}
|
|
|
|
// NSWF_ONE_IMPLIES_ALL applies only when the "one" is not a folder
|
|
// and if it is a shortcut if the target of the shortcut is a file
|
|
|
|
BOOL CNamespaceWalk::_OneImpliesAll(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
BOOL bOneImpliesAll = FALSE;
|
|
|
|
if (NSWF_ONE_IMPLIES_ALL & _dwFlags)
|
|
{
|
|
switch (_GetType(psf, pidl))
|
|
{
|
|
case NSWALK_LINK:
|
|
if (!_IsFolderTarget(psf, pidl))
|
|
{
|
|
bOneImpliesAll = TRUE; // shortcut to non folder, one-implies-all applies
|
|
}
|
|
break;
|
|
|
|
case NSWALK_ITEM:
|
|
bOneImpliesAll = TRUE; // non folder
|
|
break;
|
|
}
|
|
}
|
|
return bOneImpliesAll;
|
|
}
|
|
|
|
// walk an IShellFolderView implementation. this is usually defview (only such impl now)
|
|
// the depth beings at level 0 here
|
|
|
|
HRESULT CNamespaceWalk::_WalkView(IFolderView *pfv)
|
|
{
|
|
IShellFolder2 *psf;
|
|
HRESULT hr = pfv->GetFolder(IID_PPV_ARG(IShellFolder2, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
int uSelectedCount;
|
|
hr = pfv->ItemCount(SVGIO_SELECTION, &uSelectedCount);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// folders explictly selected in the view are level 0
|
|
// folders implictly selected are level 1
|
|
UINT cFolderStartDepth = 0; // assume all folders explictly selected
|
|
|
|
IEnumIDList *penum;
|
|
// prop the NSWF_ flags to the IFolderView SVGIO_ flags
|
|
UINT uFlags = (NSWF_FLAG_VIEWORDER & _dwFlags) ? SVGIO_FLAG_VIEWORDER : 0;
|
|
|
|
if (uSelectedCount > 1)
|
|
{
|
|
hr = pfv->Items(SVGIO_SELECTION | uFlags, IID_PPV_ARG(IEnumIDList, &penum));
|
|
}
|
|
else if (uSelectedCount == 1)
|
|
{
|
|
hr = pfv->Items(SVGIO_SELECTION, IID_PPV_ARG(IEnumIDList, &penum));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
ULONG c;
|
|
if (S_OK == penum->Next(1, &pidl, &c))
|
|
{
|
|
if (_OneImpliesAll(psf, pidl))
|
|
{
|
|
// this implies pidl is not a folder so folders are implictly selected
|
|
// consider them depth 1
|
|
cFolderStartDepth = 1;
|
|
|
|
// one implies all -> release the "one" and grab "all"
|
|
penum->Release();
|
|
hr = pfv->Items(SVGIO_ALLVIEW, IID_PPV_ARG(IEnumIDList, &penum));
|
|
}
|
|
else
|
|
{
|
|
// folder selected, keep this enumerator for below loop
|
|
penum->Reset();
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
}
|
|
else if (uSelectedCount == 0)
|
|
{
|
|
// folders implictly selected, consider them depth 1
|
|
cFolderStartDepth = 1;
|
|
|
|
// get "all" or the selection. in the selection case we know that will be empty
|
|
// given uSelectedCount == 0
|
|
uFlags |= ((NSWF_NONE_IMPLIES_ALL & _dwFlags) ? SVGIO_ALLVIEW : SVGIO_SELECTION);
|
|
hr = pfv->Items(uFlags, IID_PPV_ARG(IEnumIDList, &penum));
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
UINT cFolders = 0;
|
|
LPITEMIDLIST pidl;
|
|
ULONG c;
|
|
|
|
while ((S_OK == hr) && (S_OK == penum->Next(1, &pidl, &c)))
|
|
{
|
|
switch (_GetType(psf, pidl))
|
|
{
|
|
case NSWALK_FOLDER:
|
|
cFolders++;
|
|
break;
|
|
|
|
case NSWALK_LINK:
|
|
hr = _WalkShortcut(psf, pidl, 0, cFolderStartDepth);
|
|
break;
|
|
|
|
case NSWALK_ITEM:
|
|
hr = _AddItem(psf, pidl);
|
|
break;
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
|
|
if (cFolders)
|
|
{
|
|
penum->Reset();
|
|
ULONG c;
|
|
while ((S_OK == hr) && (S_OK == penum->Next(1, &pidl, &c)))
|
|
{
|
|
if (NSWALK_FOLDER == _GetType(psf, pidl))
|
|
{
|
|
hr = _WalkFolderItem(psf, pidl, cFolderStartDepth);
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
penum->Release();
|
|
}
|
|
}
|
|
psf->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT _GetHIDA(IDataObject *pdtobj, BOOL fIgnoreAutoPlay, STGMEDIUM *pmed, LPIDA *ppida)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
if (!fIgnoreAutoPlay)
|
|
{
|
|
IDLData_InitializeClipboardFormats(); // init our registerd formats
|
|
*ppida = DataObj_GetHIDAEx(pdtobj, g_cfAutoPlayHIDA, pmed);
|
|
hr = *ppida ? S_FALSE : E_FAIL;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
*ppida = DataObj_GetHIDA(pdtobj, pmed);
|
|
hr = *ppida ? S_OK : E_FAIL;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_WalkDataObject(IDataObject *pdtobj)
|
|
{
|
|
STGMEDIUM medium = {0};
|
|
LPIDA pida;
|
|
HRESULT hr = _GetHIDA(pdtobj, NSWF_IGNORE_AUTOPLAY_HIDA & _dwFlags, &medium, &pida);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// if we picked up the autoplay hida, then we dont want
|
|
// to do a full traversal
|
|
if (hr == S_FALSE)
|
|
_cDepthMax = 0;
|
|
|
|
IShellFolder *psfRoot;
|
|
hr = SHBindToObjectEx(NULL, HIDA_GetPIDLFolder(pida), NULL, IID_PPV_ARG(IShellFolder, &psfRoot));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
BOOL cFolders = 0;
|
|
|
|
// pass 1, non folders and shortcuts
|
|
for (UINT i = 0; (S_OK == hr) && (i < pida->cidl); i++)
|
|
{
|
|
IShellFolder *psf;
|
|
LPCITEMIDLIST pidlLast;
|
|
hr = SHBindToFolderIDListParent(psfRoot, IDA_GetIDListPtr(pida, i), IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if ((pida->cidl == 1) && _OneImpliesAll(psf, pidlLast))
|
|
{
|
|
// when doing one implies all ignore the view order
|
|
// flag as that should only apply to explictly selected items
|
|
_dwFlags &= ~NSWF_FLAG_VIEWORDER;
|
|
|
|
hr = _WalkFolder(psf, pidlLast, 0);
|
|
}
|
|
else
|
|
{
|
|
switch (_GetType(psf, pidlLast))
|
|
{
|
|
case NSWALK_FOLDER:
|
|
cFolders++;
|
|
break;
|
|
|
|
case NSWALK_LINK:
|
|
hr = _WalkShortcut(psf, pidlLast, 0, 0);
|
|
break;
|
|
|
|
case NSWALK_ITEM:
|
|
hr = _AddItem(psf, pidlLast);
|
|
break;
|
|
}
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
|
|
if (cFolders)
|
|
{
|
|
// pass 2, recurse into folders
|
|
for (UINT i = 0; (S_OK == hr) && (i < pida->cidl); i++)
|
|
{
|
|
IShellFolder *psf;
|
|
LPCITEMIDLIST pidlLast;
|
|
hr = SHBindToFolderIDListParent(psfRoot, IDA_GetIDListPtr(pida, i), IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (NSWALK_FOLDER == _GetType(psf, pidlLast))
|
|
{
|
|
if (ILIsEmpty(pidlLast))
|
|
{
|
|
// in case of desktop folder we just walk the folder
|
|
// because empty pidl is not its child, and there can
|
|
// only be one desktop in the data object so always level 0
|
|
hr = _WalkFolder(psf, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
// all folders that are explictly selected are level 0
|
|
// in the walk. if the folder is in the data object then it is selected
|
|
hr = _WalkFolderItem(psf, pidlLast, 0);
|
|
}
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
psfRoot->Release();
|
|
}
|
|
HIDA_ReleaseStgMedium(pida, &medium);
|
|
}
|
|
else
|
|
{
|
|
// we have to use CF_HDROP instead of HIDA because this
|
|
// data object comes from AutoPlay and that only supports CF_HDROP
|
|
FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
hr = pdtobj->GetData(&fmte, &medium);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
for (int i = 0; SUCCEEDED(hr) && DragQueryFile((HDROP)medium.hGlobal, i, szPath, ARRAYSIZE(szPath)); i++)
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
hr = SHParseDisplayName(szPath, NULL, &pidl, 0, NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// note, no filter being applied here!
|
|
hr = _AppendFull(pidl);
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNamespaceWalk::_WalkParentAndItem(IParentAndItem *ppai)
|
|
{
|
|
LPITEMIDLIST pidlChild;
|
|
IShellFolder *psf;
|
|
HRESULT hr = ppai->GetParentAndItem(NULL, &psf, &pidlChild);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (_OneImpliesAll(psf, pidlChild))
|
|
{
|
|
// a non folder item, this is level 0 of walk
|
|
hr = _WalkFolder(psf, pidlChild, 0);
|
|
}
|
|
else
|
|
{
|
|
// folder or non folder, this is level 0 of walk
|
|
// and level 0 if the item is a folder
|
|
hr = _WalkIDList(psf, pidlChild, 0, 0);
|
|
}
|
|
|
|
psf->Release();
|
|
ILFree(pidlChild);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// punkToWalk can be a...
|
|
// site that gives access to IFolderView (defview)
|
|
// IShellFolder
|
|
// IDataObject
|
|
// IParentAndItem (CLSID_ShellItem usually)
|
|
|
|
STDMETHODIMP CNamespaceWalk::Walk(IUnknown *punkToWalk, DWORD dwFlags, int cDepth, INamespaceWalkCB *pnswcb)
|
|
{
|
|
_dwFlags = dwFlags;
|
|
_cDepthMax = cDepth;
|
|
|
|
if (pnswcb)
|
|
pnswcb->QueryInterface(IID_PPV_ARG(INamespaceWalkCB, &_pnswcb));
|
|
|
|
_ProgressDialogBegin();
|
|
|
|
IFolderView *pfv;
|
|
HRESULT hr = IUnknown_QueryService(punkToWalk, SID_SFolderView, IID_PPV_ARG(IFolderView, &pfv));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _WalkView(pfv);
|
|
pfv->Release();
|
|
}
|
|
else
|
|
{
|
|
IShellFolder *psf;
|
|
hr = punkToWalk->QueryInterface(IID_PPV_ARG(IShellFolder, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _WalkFolder(psf, NULL, 0);
|
|
psf->Release();
|
|
}
|
|
else
|
|
{
|
|
IDataObject *pdtobj;
|
|
hr = punkToWalk->QueryInterface(IID_PPV_ARG(IDataObject, &pdtobj));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _WalkDataObject(pdtobj);
|
|
pdtobj->Release();
|
|
}
|
|
else
|
|
{
|
|
// IShellItem case, get to the things to walk via IParentAndItem
|
|
IParentAndItem *ppai;
|
|
hr = punkToWalk->QueryInterface(IID_PPV_ARG(IParentAndItem, &ppai));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _WalkParentAndItem(ppai);
|
|
ppai->Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_ProgressDialogEnd();
|
|
|
|
if (_pnswcb)
|
|
_pnswcb->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
// caller should use FreeIDListArray() (inline helper in the .h file) to free this array
|
|
|
|
STDMETHODIMP CNamespaceWalk::GetIDArrayResult(UINT *pcItems, LPITEMIDLIST **pppidl)
|
|
{
|
|
HRESULT hr;
|
|
*pppidl = NULL;
|
|
*pcItems = (HDPA)_dpaItems ? _dpaItems.GetPtrCount() : 0;
|
|
if (*pcItems)
|
|
{
|
|
ULONG cb = *pcItems * sizeof(*pppidl);
|
|
*pppidl = (LPITEMIDLIST *)CoTaskMemAlloc(cb);
|
|
if (*pppidl)
|
|
{
|
|
memcpy(*pppidl, _dpaItems.GetPtrPtr(), cb); // transfer ownership of pidls here
|
|
_dpaItems.Destroy(); // don't free the pidls, just the array
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
*pcItems = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CNamespaceWalk::_ProgressDialogBegin()
|
|
{
|
|
ASSERT(!_papd); // Why are we initializing more than once???
|
|
ASSERT(!_pap); // Why are we initializing more than once???
|
|
if (_dwFlags & NSWF_SHOW_PROGRESS)
|
|
{
|
|
if (!_papd)
|
|
{
|
|
HRESULT hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IActionProgressDialog, &_papd));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPWSTR pszTitle = NULL, pszCancel = NULL;
|
|
|
|
// Retrieve dialog text from callback.
|
|
hr = _pnswcb ? _pnswcb->InitializeProgressDialog(&pszTitle, &pszCancel) : S_OK;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _papd->Initialize(SPINITF_MODAL, pszTitle, pszCancel);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _papd->QueryInterface(IID_PPV_ARG(IActionProgress, &_pap));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _pap->Begin(SPACTION_SEARCHING_FILES, SPBEGINF_MARQUEEPROGRESS);
|
|
if (FAILED(hr))
|
|
{
|
|
ATOMICRELEASE(_pap); // Cleanup if necessary.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CoTaskMemFree(pszTitle);
|
|
CoTaskMemFree(pszCancel);
|
|
|
|
// Cleanup if necessary.
|
|
if (FAILED(hr))
|
|
{
|
|
ATOMICRELEASE(_papd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CNamespaceWalk::_ProgressDialogUpdate(LPCWSTR pszText)
|
|
{
|
|
if (_pap)
|
|
_pap->UpdateText(SPTEXT_ACTIONDETAIL, pszText, TRUE);
|
|
}
|
|
|
|
// Note:
|
|
// Returns S_OK if we should continue our walk.
|
|
// Returns ERROR_CANCELLED if we should abort our walk due to user "Cancel".
|
|
//
|
|
HRESULT CNamespaceWalk::_ProgressDialogQueryCancel()
|
|
{
|
|
HRESULT hr = S_OK; // assume we keep going
|
|
|
|
// Check progress dialog to see if user cancelled walk.
|
|
if (_pap)
|
|
{
|
|
BOOL bCancelled;
|
|
hr = _pap->QueryCancel(&bCancelled);
|
|
if (SUCCEEDED(hr) && bCancelled)
|
|
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CNamespaceWalk::_ProgressDialogEnd()
|
|
{
|
|
if (_pap)
|
|
{
|
|
_pap->End();
|
|
ATOMICRELEASE(_pap);
|
|
}
|
|
|
|
if (_papd)
|
|
{
|
|
_papd->Stop();
|
|
ATOMICRELEASE(_papd);
|
|
}
|
|
}
|