mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3142 lines
98 KiB
3142 lines
98 KiB
#include "shellprv.h"
|
|
#pragma hdrstop
|
|
|
|
#include "bitbuck.h"
|
|
#include "util.h"
|
|
#include "copy.h"
|
|
#include "prop.h" // for COLUMN_INFO
|
|
#include "propsht.h"
|
|
#include "datautil.h"
|
|
#include "vdate.h" // for VDATEINPUTBUF
|
|
#include "views.h"
|
|
#include "defview.h" // for WM_DSV_FSNOTIFY
|
|
#include "fsdata.h"
|
|
#include "idldrop.h"
|
|
#include "clsobj.h"
|
|
#include "basefvcb.h"
|
|
#include "idlcomm.h" // for HIDA
|
|
#include "filefldr.h"
|
|
#include <idhidden.h>
|
|
#include "enumidlist.h"
|
|
#include "contextmenu.h"
|
|
|
|
class CBitBucket;
|
|
class CBitBucketViewCB;
|
|
class CBitBucketEnum;
|
|
class CBitBucketDropTarget;
|
|
class CBitBucketData;
|
|
|
|
typedef struct {
|
|
CBitBucket *pbb;
|
|
HWND hwnd;
|
|
IDataObject *pdtobj;
|
|
IStream *pstmDataObj;
|
|
ULONG_PTR idCmd;
|
|
POINT ptDrop;
|
|
BOOL fSameHwnd;
|
|
BOOL fDragDrop;
|
|
} BBTHREADDATA;
|
|
|
|
|
|
class CBitBucket :
|
|
public IPersistFolder2,
|
|
public IShellFolder2,
|
|
public IContextMenu,
|
|
public IShellPropSheetExt,
|
|
public IShellExtInit
|
|
{
|
|
friend CBitBucketEnum;
|
|
friend CBitBucketViewCB;
|
|
friend CBitBucketDropTarget;
|
|
friend CBitBucketData;
|
|
|
|
public:
|
|
CBitBucket();
|
|
|
|
// IUnknown
|
|
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
|
|
STDMETHOD_(ULONG,AddRef)();
|
|
STDMETHOD_(ULONG,Release)();
|
|
|
|
// IPersist
|
|
STDMETHOD(GetClassID)(CLSID *pclsid);
|
|
|
|
// IPersistFolder
|
|
STDMETHOD(Initialize)(LPCITEMIDLIST pidl);
|
|
|
|
// IPersistFolder2
|
|
STDMETHOD(GetCurFolder)(LPITEMIDLIST *ppidl);
|
|
|
|
// IShellFolder
|
|
STDMETHOD(ParseDisplayName)(HWND hwnd, IBindCtx *pbc, LPOLESTR pszDisplayName, ULONG *pchEaten, LPITEMIDLIST *ppidl, ULONG *pdwAttributes);
|
|
STDMETHOD(EnumObjects)(HWND hwnd, SHCONTF grfFlags, IEnumIDList **ppenumIDList);
|
|
STDMETHOD(BindToObject)(LPCITEMIDLIST pidl, IBindCtx *pbc, REFIID riid, void **ppv);
|
|
STDMETHOD(BindToStorage)(LPCITEMIDLIST pidl, IBindCtx *pbc, REFIID riid, void **ppv);
|
|
STDMETHOD(CompareIDs)(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
|
|
STDMETHOD(CreateViewObject)(HWND hwnd, REFIID riid, void **ppv);
|
|
STDMETHOD(GetAttributesOf)(UINT cidl, LPCITEMIDLIST *apidl, SFGAOF *rgfInOut);
|
|
STDMETHOD(GetUIObjectOf)(HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, REFIID riid, UINT *rgfReserved, void **ppv);
|
|
STDMETHOD(GetDisplayNameOf)(LPCITEMIDLIST pidl, SHGDNF dwFlags, LPSTRRET lpName);
|
|
STDMETHOD(SetNameOf)(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName, SHGDNF dwFlags, LPITEMIDLIST *ppidlOut);
|
|
|
|
// IShellFolder2
|
|
STDMETHOD(GetDefaultSearchGUID)(GUID *pguid);
|
|
STDMETHOD(EnumSearches)(IEnumExtraSearch **ppenum);
|
|
STDMETHOD(GetDefaultColumn)(DWORD dwRes, ULONG *pSort, ULONG *pDisplay);
|
|
STDMETHOD(GetDefaultColumnState)(UINT iColumn, SHCOLSTATEF *pdwFlags);
|
|
STDMETHOD(GetDetailsEx)(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv);
|
|
STDMETHOD(GetDetailsOf)(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *psd);
|
|
STDMETHOD(MapColumnToSCID)(UINT iColumn, SHCOLUMNID *pscid);
|
|
|
|
// IContextMenu
|
|
STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
|
|
STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici);
|
|
STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax);
|
|
|
|
// IShellPropSheetExt
|
|
STDMETHOD(AddPages)(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
|
|
STDMETHOD(ReplacePage)(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplaceWith, LPARAM lParam);
|
|
|
|
// IShellExtInit
|
|
STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdobj, HKEY hkeyProgID);
|
|
|
|
protected:
|
|
LPITEMIDLIST DataEntryToIDList(BBDATAENTRYW *pbbde);
|
|
LPITEMIDLIST PathToIDList(LPCTSTR pszPath);
|
|
HGLOBAL BuildDestSpecs(LPIDA pida);
|
|
|
|
private:
|
|
~CBitBucket();
|
|
|
|
static HRESULT CALLBACK _ItemMenuCallBack(IShellFolder *psf, HWND hwnd,
|
|
IDataObject * pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
static HRESULT CALLBACK _BackgroundMenuCallBack(IShellFolder *psf, HWND hwnd,
|
|
IDataObject * pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
static UINT CALLBACK _GlobalSettingsCalback(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp);
|
|
static BOOL_PTR CALLBACK _FilePropDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
static BOOL_PTR CALLBACK _DriveDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
static BOOL_PTR CALLBACK _GlobalPropDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
static DWORD CALLBACK _DispatchThreadProc(void *pv);
|
|
static BOOL CALLBACK _AddPagesCallback(HPROPSHEETPAGE psp, LPARAM lParam);
|
|
static DWORD WINAPI _DropThreadInit(BBTHREADDATA *pbbtd);
|
|
static void _GlobalPropOnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify);
|
|
static CBitBucket *_FromFolder(IShellFolder *psf);
|
|
|
|
HRESULT _LaunchThread(HWND hwnd, IDataObject *pdtobj, WPARAM idCmd);
|
|
void _GetDeletedFileTime(LPCITEMIDLIST pidl, FILETIME *pft);
|
|
DWORD _GetDeletedSize(LPCITEMIDLIST pidl);
|
|
void _FileProperties(IDataObject *pdtobj);
|
|
void _DefaultProperties();
|
|
int _CompareOriginal(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
|
|
int _DriveIDFromIDList(LPCITEMIDLIST pidl);
|
|
HRESULT _FolderFromIDList(LPCITEMIDLIST pidl, REFIID riid, void **ppv);
|
|
HRESULT _FolderFromDrive(int idDrive, REFIID riid, void **ppv);
|
|
HRESULT _InitBindCtx();
|
|
BOOL _MapColIndex(UINT *piColumn);
|
|
PUBBDATAENTRYA _IsValid(LPCITEMIDLIST pidl);
|
|
HRESULT _OriginalPath(LPCITEMIDLIST pidl, TCHAR *pszOrig, UINT cch);
|
|
HRESULT _OriginalDirectory(LPCITEMIDLIST pidl, TCHAR *pszOrig, UINT cch);
|
|
|
|
void _RestoreFileList(HWND hwnd, IDataObject * pdtobj);
|
|
void _NukeFileList(HWND hwnd, IDataObject * pdtobj);
|
|
int _DataObjToFileOpString(IDataObject *pdtobj, LPTSTR *ppszSrc, LPTSTR *ppszDest);
|
|
void _GetDriveDisplayName(int idDrive, LPTSTR pszName, UINT cchSize);
|
|
HRESULT _Compare(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
|
|
LPITEMIDLIST _DriveInfoToIDList(int idDrive, int iIndex);
|
|
DWORD _IsFolder(LPCITEMIDLIST pidl);
|
|
UINT _SizeColumn();
|
|
|
|
LONG _cRef;
|
|
LPITEMIDLIST _pidl;
|
|
UINT _uiColumnSize;
|
|
|
|
IUnknown *_rgFolders[MAX_BITBUCKETS];
|
|
};
|
|
|
|
class CBitBucketViewCB : public CBaseShellFolderViewCB
|
|
{
|
|
public:
|
|
STDMETHODIMP RealMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
|
|
private:
|
|
friend HRESULT Create_CBitBucketViewCB(CBitBucket* psf, IShellFolderViewCB **ppsfvcb);
|
|
CBitBucketViewCB(CBitBucket *pbbf) : CBaseShellFolderViewCB(pbbf->_pidl, 0), _pbbf(pbbf)
|
|
{
|
|
ZeroMemory(&_fssci, sizeof(_fssci));
|
|
_pbbf->AddRef();
|
|
}
|
|
~CBitBucketViewCB()
|
|
{
|
|
_pbbf->Release();
|
|
}
|
|
|
|
HRESULT _HandleFSNotify(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
|
|
|
|
HRESULT OnBACKGROUNDENUM(DWORD pv)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnGetCCHMax(DWORD pv, LPCITEMIDLIST wP, UINT *lP)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnSelChange(DWORD pv, UINT wPl, UINT wPh, SFVM_SELCHANGE_DATA*lP)
|
|
{
|
|
ViewSelChange(_pbbf, lP, &_fssci);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnFSNotify(DWORD pv, LPCITEMIDLIST *ppidl, LPARAM lP)
|
|
{
|
|
return _HandleFSNotify((LONG)lP, ppidl[0], ppidl[1]);
|
|
}
|
|
|
|
HRESULT OnUpdateStatusBar(DWORD pv, BOOL wP)
|
|
{
|
|
return ViewUpdateStatusBar(_punkSite, _pidl, &_fssci);
|
|
}
|
|
|
|
HRESULT OnWindowCreated(DWORD pv, HWND hwnd)
|
|
{
|
|
for (int i = 0; i < MAX_BITBUCKETS; i++)
|
|
{
|
|
SHChangeNotifyEntry fsne = {0};
|
|
|
|
ASSERT(FALSE == fsne.fRecursive);
|
|
|
|
// make it if it's there so that we'll get any events
|
|
if (MakeBitBucket(i))
|
|
{
|
|
fsne.pidl = g_pBitBucket[i]->pidl;
|
|
UINT u = SHChangeNotifyRegister(hwnd, SHCNRF_NewDelivery | SHCNRF_ShellLevel | SHCNRF_InterruptLevel,
|
|
SHCNE_DISKEVENTS, WM_DSV_FSNOTIFY, 1, &fsne);
|
|
}
|
|
}
|
|
|
|
// _fssci.szDrive[0] == '\0' // no drive specific stuff
|
|
InitializeStatus(_punkSite);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnInsertDeleteItem(int iMul, LPCITEMIDLIST pidl)
|
|
{
|
|
ViewInsertDeleteItem(_pbbf, &_fssci, pidl, iMul);
|
|
//since recycle bin doesn't receive shcne_xxx
|
|
//defview doesn't update status bar
|
|
OnUpdateStatusBar(0, FALSE);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnWindowDestroy(DWORD pv, HWND hwnd)
|
|
{
|
|
SHChangeNotifyDeregisterWindow(hwnd); // deregister all
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnSize(DWORD pv, UINT cx, UINT cy)
|
|
{
|
|
ResizeStatus(_punkSite, cx);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnEnumeratedItems(DWORD pv, UINT celt, LPCITEMIDLIST *rgpidl)
|
|
{
|
|
_cItems = celt;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnDefViewMode(DWORD pv, FOLDERVIEWMODE*lP)
|
|
{
|
|
if (_cItems < DEFVIEW_FVM_MANY_CUTOFF)
|
|
*lP = FVM_TILE;
|
|
else
|
|
*lP = FVM_ICON;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT OnGetHelpTopic(DWORD pv, SFVM_HELPTOPIC_DATA *phtd)
|
|
{
|
|
if (IsOS(OS_ANYSERVER))
|
|
{
|
|
StrCpyW(phtd->wszHelpFile, L"recycle.chm > windefault");
|
|
}
|
|
else
|
|
{
|
|
StrCpyW(phtd->wszHelpTopic, L"hcp://services/subsite?node=Unmapped/Recycle_Bin");
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
FSSELCHANGEINFO _fssci;
|
|
CBitBucket *_pbbf;
|
|
UINT _cItems;
|
|
|
|
// Web View implementation
|
|
HRESULT OnGetWebViewLayout(DWORD pv, UINT uViewMode, SFVM_WEBVIEW_LAYOUT_DATA* pData);
|
|
HRESULT OnGetWebViewContent(DWORD pv, SFVM_WEBVIEW_CONTENT_DATA* pData);
|
|
HRESULT OnGetWebViewTasks(DWORD pv, SFVM_WEBVIEW_TASKSECTION_DATA* pTasks);
|
|
public:
|
|
static HRESULT _OnEmptyRecycleBin(IUnknown* pv,IShellItemArray *psiItemArray, IBindCtx *pbc);
|
|
static HRESULT _OnRestore(IUnknown* pv,IShellItemArray *psiItemArray, IBindCtx *pbc);
|
|
static HRESULT _HaveDeletedItems(IUnknown* pv,IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState);
|
|
};
|
|
|
|
HRESULT Create_CBitBucketViewCB(CBitBucket* psf, IShellFolderViewCB **ppsfvcb)
|
|
{
|
|
HRESULT hr;
|
|
CBitBucketViewCB* psfvcb = new CBitBucketViewCB(psf);
|
|
if (psfvcb)
|
|
{
|
|
*ppsfvcb = SAFECAST(psfvcb, IShellFolderViewCB*);
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
*ppsfvcb = NULL;
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CBitBucketViewCB::OnGetWebViewLayout(DWORD pv, UINT uViewMode, SFVM_WEBVIEW_LAYOUT_DATA* pData)
|
|
{
|
|
ZeroMemory(pData, sizeof(*pData));
|
|
pData->dwLayout = SFVMWVL_NORMAL;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CBitBucketViewCB::_OnEmptyRecycleBin(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc)
|
|
{
|
|
CBitBucketViewCB* pThis = (CBitBucketViewCB*)(void*)pv;
|
|
|
|
HRESULT hr = SHInvokeCommandOnPidl(pThis->_hwndMain, NULL, pThis->_pidl, 0, "empty");
|
|
|
|
if (S_FALSE == hr)
|
|
MessageBeep(0); // let the user know the click was processed, but nothing was there to delete
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CBitBucketViewCB::_OnRestore(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc)
|
|
{
|
|
IDataObject *pdo;
|
|
CBitBucketViewCB* pThis = (CBitBucketViewCB*)(void*)pv;
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!psiItemArray)
|
|
{
|
|
hr = E_FAIL;
|
|
|
|
IFolderView* pfv;
|
|
if (pThis->_punkSite && SUCCEEDED(pThis->_punkSite->QueryInterface(IID_PPV_ARG(IFolderView, &pfv))))
|
|
{
|
|
hr = pfv->Items(SVGIO_ALLVIEW, IID_PPV_ARG(IDataObject, &pdo));
|
|
pfv->Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = psiItemArray->BindToHandler(NULL,BHID_DataObject,IID_PPV_ARG(IDataObject,&pdo));
|
|
}
|
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SHInvokeCommandOnDataObject(pThis->_hwndMain, NULL, pdo, 0, "undelete");
|
|
ATOMICRELEASE(pdo);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CBitBucketViewCB::_HaveDeletedItems(IUnknown* /*pv*/,IShellItemArray * /* psiItemArray */, BOOL /*fOkToBeSlow*/, UISTATE* puisState)
|
|
{
|
|
*puisState = IsRecycleBinEmpty() ? UIS_DISABLED : UIS_ENABLED;
|
|
return S_OK;
|
|
}
|
|
|
|
const WVTASKITEM c_BitBucketTaskHeader = WVTI_HEADER(L"shell32.dll", IDS_HEADER_BITBUCKET, IDS_HEADER_BITBUCKET_TT);
|
|
const WVTASKITEM c_BitBucketTaskList[] =
|
|
{
|
|
WVTI_ENTRY_ALL(CLSID_NULL, L"shell32.dll", IDS_TASK_EMPTYRECYCLEBIN, IDS_TASK_EMPTYRECYCLEBIN_TT, IDI_TASK_EMPTYRECYCLEBIN, CBitBucketViewCB::_HaveDeletedItems, CBitBucketViewCB::_OnEmptyRecycleBin),
|
|
WVTI_ENTRY_ALL_TITLE(CLSID_NULL, L"shell32.dll", IDS_TASK_RESTORE_ALL, IDS_TASK_RESTORE_ITEM, IDS_TASK_RESTORE_ITEM, IDS_TASK_RESTORE_ITEMS, IDS_TASK_RESTORE_TT, IDI_TASK_RESTOREITEMS, CBitBucketViewCB::_HaveDeletedItems, CBitBucketViewCB::_OnRestore),
|
|
};
|
|
|
|
HRESULT CBitBucketViewCB::OnGetWebViewContent(DWORD pv, SFVM_WEBVIEW_CONTENT_DATA* pData)
|
|
{
|
|
ZeroMemory(pData, sizeof(*pData));
|
|
|
|
Create_IUIElement(&c_BitBucketTaskHeader, &(pData->pFolderTaskHeader));
|
|
|
|
LPCTSTR rgcsidl[] = { MAKEINTRESOURCE(CSIDL_DESKTOP), MAKEINTRESOURCE(CSIDL_PERSONAL), MAKEINTRESOURCE(CSIDL_DRIVES), MAKEINTRESOURCE(CSIDL_NETWORK) };
|
|
CreateIEnumIDListOnCSIDLs(NULL, rgcsidl, ARRAYSIZE(rgcsidl), &(pData->penumOtherPlaces));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CBitBucketViewCB::OnGetWebViewTasks(DWORD pv, SFVM_WEBVIEW_TASKSECTION_DATA* pTasks)
|
|
{
|
|
ZeroMemory(pTasks, sizeof(*pTasks));
|
|
|
|
pTasks->dwUpdateFlags = SFVMWVTSDF_CONTENTSCHANGE;
|
|
Create_IEnumUICommand((IUnknown*)(void*)this, c_BitBucketTaskList, ARRAYSIZE(c_BitBucketTaskList), &pTasks->penumFolderTasks);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
|
|
HRESULT CBitBucketViewCB::_HandleFSNotify(LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
// pidls must be child of drives or network
|
|
// (actually only drives work for right now)
|
|
// that way we won't get duplicate events
|
|
if ((!ILIsParent((LPCITEMIDLIST)&c_idlDrives, pidl1, FALSE) && !ILIsParent((LPCITEMIDLIST)&c_idlNet, pidl1, FALSE)) ||
|
|
(pidl2 && !ILIsParent((LPCITEMIDLIST)&c_idlDrives, pidl2, FALSE) && !ILIsParent((LPCITEMIDLIST)&c_idlNet, pidl2, FALSE)))
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
SHGetPathFromIDList(pidl1, szPath);
|
|
LPTSTR pszFileName = PathFindFileName(szPath);
|
|
|
|
if (!lstrcmpi(pszFileName, c_szInfo2) ||
|
|
!lstrcmpi(pszFileName, c_szInfo) ||
|
|
!lstrcmpi(pszFileName, c_szDesktopIni))
|
|
{
|
|
// we ignore changes to these files because they mean we were simply doing bookeeping
|
|
// (eg updating the info file, re-creating the desktop.ini, etc)
|
|
return S_FALSE;
|
|
}
|
|
|
|
switch (lEvent)
|
|
{
|
|
case SHCNE_RENAMEFOLDER:
|
|
case SHCNE_RENAMEITEM:
|
|
{
|
|
// if the rename's target is in a bitbucket, then do a create.
|
|
// otherwise, return S_OK..
|
|
|
|
int idDrive = DriveIDFromBBPath(szPath);
|
|
|
|
if (MakeBitBucket(idDrive) && ILIsParent(g_pBitBucket[idDrive]->pidl, pidl1, TRUE))
|
|
{
|
|
hr = _HandleFSNotify((lEvent == SHCNE_RENAMEITEM) ? SHCNE_DELETE : SHCNE_RMDIR, pidl1, NULL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHCNE_CREATE:
|
|
case SHCNE_MKDIR:
|
|
{
|
|
LPITEMIDLIST pidl = _pbbf->PathToIDList(szPath);
|
|
if (pidl)
|
|
{
|
|
ShellFolderView_AddObject(_hwndMain, pidl);
|
|
hr = S_FALSE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHCNE_DELETE:
|
|
case SHCNE_RMDIR:
|
|
// if this was a delete into the recycle bin, pidl2 will exist
|
|
if (pidl2)
|
|
{
|
|
hr = _HandleFSNotify((lEvent == SHCNE_DELETE) ? SHCNE_CREATE : SHCNE_MKDIR, pidl2, NULL);
|
|
}
|
|
else
|
|
{
|
|
ShellFolderView_RemoveObject(_hwndMain, ILFindLastID(pidl1));
|
|
hr = S_FALSE;
|
|
}
|
|
break;
|
|
|
|
case SHCNE_UPDATEDIR:
|
|
// we recieved an updatedir, which means we probably had more than 10 fsnotify events come in,
|
|
// so we just refresh our brains out.
|
|
ShellFolderView_RefreshAll(_hwndMain);
|
|
break;
|
|
|
|
default:
|
|
hr = S_FALSE; // didn't handle this message
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
STDMETHODIMP CBitBucketViewCB::RealMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
HANDLE_MSG(0, SFVM_GETHELPTOPIC, OnGetHelpTopic);
|
|
HANDLE_MSG(0, SFVM_GETCCHMAX, OnGetCCHMax);
|
|
HANDLE_MSG(0, SFVM_SELCHANGE, OnSelChange);
|
|
HANDLE_MSG(0, SFVM_FSNOTIFY, OnFSNotify);
|
|
HANDLE_MSG(0, SFVM_UPDATESTATUSBAR, OnUpdateStatusBar);
|
|
HANDLE_MSG(0, SFVM_WINDOWCREATED, OnWindowCreated);
|
|
HANDLE_MSG(1 , SFVM_INSERTITEM, OnInsertDeleteItem);
|
|
HANDLE_MSG(-1, SFVM_DELETEITEM, OnInsertDeleteItem);
|
|
HANDLE_MSG(0, SFVM_WINDOWDESTROY, OnWindowDestroy);
|
|
HANDLE_MSG(0, SFVM_ENUMERATEDITEMS, OnEnumeratedItems);
|
|
HANDLE_MSG(0, SFVM_DEFVIEWMODE, OnDefViewMode);
|
|
HANDLE_MSG(0, SFVM_SIZE, OnSize);
|
|
HANDLE_MSG(0, SFVM_BACKGROUNDENUM, OnBACKGROUNDENUM);
|
|
HANDLE_MSG(0, SFVM_GETWEBVIEWLAYOUT, OnGetWebViewLayout);
|
|
HANDLE_MSG(0, SFVM_GETWEBVIEWCONTENT, OnGetWebViewContent);
|
|
HANDLE_MSG(0, SFVM_GETWEBVIEWTASKS, OnGetWebViewTasks);
|
|
|
|
default:
|
|
return E_FAIL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
typedef struct _bbpropsheetinfo
|
|
{
|
|
PROPSHEETPAGE psp;
|
|
|
|
int idDrive;
|
|
|
|
BOOL fNukeOnDelete;
|
|
BOOL fOriginalNukeOnDelete;
|
|
|
|
int iPercent;
|
|
int iOriginalPercent;
|
|
|
|
// the following two fields are valid only for the "global" tab, where they represent the state
|
|
// of the "Configure drives independently" / "Use one setting for all drives" checkbox
|
|
BOOL fUseGlobalSettings;
|
|
BOOL fOriginalUseGlobalSettings;
|
|
|
|
// the following fields are for policy overrides
|
|
BOOL fPolicyNukeOnDelete;
|
|
BOOL fPolicyPercent;
|
|
|
|
// this is a pointer to the global property sheet page after it has been copied somewhere by the
|
|
// CreatePropertySheetPage(), we use this to get to the global state of the % slider and fNukeOnDelete
|
|
// from the other tabs
|
|
struct _bbpropsheetinfo* pGlobal;
|
|
} BBPROPSHEETINFO;
|
|
|
|
|
|
const static DWORD aBitBucketPropHelpIDs[] = { // Context Help IDs
|
|
IDD_ATTR_GROUPBOX, IDH_COMM_GROUPBOX,
|
|
IDC_INDEPENDENT, IDH_RECYCLE_CONFIG_INDEP,
|
|
IDC_GLOBAL, IDH_RECYCLE_CONFIG_ALL,
|
|
IDC_DISKSIZE, IDH_RECYCLE_DRIVE_SIZE,
|
|
IDC_DISKSIZEDATA, IDH_RECYCLE_DRIVE_SIZE,
|
|
IDC_BYTESIZE, IDH_RECYCLE_BIN_SIZE,
|
|
IDC_BYTESIZEDATA, IDH_RECYCLE_BIN_SIZE,
|
|
IDC_NUKEONDELETE, IDH_RECYCLE_PURGE_ON_DEL,
|
|
IDC_BBSIZE, IDH_RECYCLE_MAX_SIZE,
|
|
IDC_BBSIZETEXT, IDH_RECYCLE_MAX_SIZE,
|
|
IDC_CONFIRMDELETE, IDH_DELETE_CONFIRM_DLG,
|
|
IDC_TEXT, NO_HELP,
|
|
0, 0
|
|
};
|
|
|
|
const static DWORD aBitBucketHelpIDs[] = { // Context Help IDs
|
|
IDD_LINE_1, NO_HELP,
|
|
IDD_LINE_2, NO_HELP,
|
|
IDD_ITEMICON, IDH_FPROP_GEN_ICON,
|
|
IDD_NAME, IDH_FPROP_GEN_NAME,
|
|
IDD_FILETYPE_TXT, IDH_FPROP_GEN_TYPE,
|
|
IDD_FILETYPE, IDH_FPROP_GEN_TYPE,
|
|
IDD_FILESIZE_TXT, IDH_FPROP_GEN_SIZE,
|
|
IDD_FILESIZE, IDH_FPROP_GEN_SIZE,
|
|
IDD_LOCATION_TXT, IDH_FCAB_DELFILEPROP_LOCATION,
|
|
IDD_LOCATION, IDH_FCAB_DELFILEPROP_LOCATION,
|
|
IDD_DELETED_TXT, IDH_FCAB_DELFILEPROP_DELETED,
|
|
IDD_DELETED, IDH_FCAB_DELFILEPROP_DELETED,
|
|
IDD_CREATED_TXT, IDH_FPROP_GEN_DATE_CREATED,
|
|
IDD_CREATED, IDH_FPROP_GEN_DATE_CREATED,
|
|
IDD_READONLY, IDH_FCAB_DELFILEPROP_READONLY,
|
|
IDD_HIDDEN, IDH_FCAB_DELFILEPROP_HIDDEN,
|
|
IDD_ARCHIVE, IDH_FCAB_DELFILEPROP_ARCHIVE,
|
|
IDD_ATTR_GROUPBOX, IDH_COMM_GROUPBOX,
|
|
0, 0
|
|
};
|
|
|
|
CBitBucket::CBitBucket() : _cRef(1), _pidl(NULL), _uiColumnSize(-1)
|
|
{
|
|
}
|
|
|
|
CBitBucket::~CBitBucket()
|
|
{
|
|
for (int i = 0; i < ARRAYSIZE(_rgFolders); i++)
|
|
{
|
|
if (_rgFolders[i])
|
|
_rgFolders[i]->Release();
|
|
}
|
|
|
|
ILFree(_pidl);
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CBitBucket, IPersistFolder2), // IID_IPersistFolder2
|
|
QITABENTMULTI(CBitBucket, IPersistFolder, IPersistFolder2), // IID_IPersistFolder
|
|
QITABENT(CBitBucket, IShellFolder2), // IID_IShellFolder2
|
|
QITABENTMULTI(CBitBucket, IShellFolder, IShellFolder2), // IID_IShellFolder
|
|
QITABENT(CBitBucket, IContextMenu), // IID_IContextMenu
|
|
QITABENT(CBitBucket, IShellPropSheetExt), // IID_IShellPropSheetExt
|
|
QITABENT(CBitBucket, IShellExtInit), // IID_IShellExtInit
|
|
{ 0 },
|
|
};
|
|
HRESULT hr = QISearch(this, qit, riid, ppv);
|
|
if (FAILED(hr) && riid == CLSID_RecycleBin)
|
|
{
|
|
*ppv = this; // not ref counted
|
|
hr = S_OK;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CBitBucket::AddRef()
|
|
{
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CBitBucket::Release()
|
|
{
|
|
if (InterlockedDecrement(&_cRef))
|
|
return _cRef;
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
#pragma pack(1)
|
|
typedef struct {
|
|
HIDDENITEMID hid;
|
|
BBDATAENTRYA bbde;
|
|
} HIDDENRECYCLEBINDATA;
|
|
#pragma pack()
|
|
|
|
typedef HIDDENRECYCLEBINDATA UNALIGNED *PUHIDDENRECYCLEBINDATA;
|
|
#define HRBD_CURRENTVERSION 0
|
|
|
|
PUBBDATAENTRYA CBitBucket::_IsValid(LPCITEMIDLIST pidl)
|
|
{
|
|
if (pidl)
|
|
{
|
|
PUHIDDENRECYCLEBINDATA phrbd = (PUHIDDENRECYCLEBINDATA)ILFindHiddenID(pidl, IDLHID_RECYCLEBINDATA);
|
|
if (phrbd && phrbd->hid.wVersion >= HRBD_CURRENTVERSION)
|
|
return &phrbd->bbde;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
HRESULT CBitBucket::_OriginalPath(LPCITEMIDLIST pidl, TCHAR *pszOrig, UINT cch)
|
|
{
|
|
ASSERT(pidl == ILFindLastID(pidl));
|
|
|
|
*pszOrig = 0;
|
|
HRESULT hr;
|
|
PUBBDATAENTRYA pbbde = _IsValid(pidl);
|
|
if (pbbde)
|
|
{
|
|
if (!ILGetHiddenString(pidl, IDLHID_RECYCLEBINORIGINAL, pszOrig, cch))
|
|
{
|
|
SHAnsiToTChar(pbbde->szOriginal, pszOrig, cch);
|
|
}
|
|
hr = *pszOrig ? S_OK : S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
ASSERTMSG(pbbde != NULL, "_OriginalPath: caller needs to call _IsValid on the pidl passed to us!");
|
|
hr = E_FAIL;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CBitBucket::_OriginalDirectory(LPCITEMIDLIST pidl, TCHAR *pszOrig, UINT cch)
|
|
{
|
|
HRESULT hr = _OriginalPath(pidl, pszOrig, cch);
|
|
if (SUCCEEDED(hr))
|
|
PathRemoveFileSpec(pszOrig);
|
|
return hr;
|
|
}
|
|
|
|
// subclass member function to support CF_HDROP and CF_NETRESOURCE
|
|
// in:
|
|
// hida bitbucket id array
|
|
//
|
|
// out:
|
|
// HGLOBAL with double NULL terminated string list of destination names
|
|
//
|
|
|
|
HGLOBAL CBitBucket::BuildDestSpecs(LPIDA pida)
|
|
{
|
|
LPCITEMIDLIST pidl;
|
|
TCHAR szTemp[MAX_PATH];
|
|
UINT cbAlloc = sizeof(TCHAR); // for double NULL termination
|
|
|
|
for (UINT i = 0; pidl = IDA_GetIDListPtr(pida, i); i++)
|
|
{
|
|
_OriginalPath(pidl, szTemp, ARRAYSIZE(szTemp));
|
|
|
|
cbAlloc += lstrlen(PathFindFileName(szTemp)) * sizeof(TCHAR) + sizeof(TCHAR);
|
|
}
|
|
LPTSTR pszRet = (LPTSTR) LocalAlloc(LPTR, cbAlloc);
|
|
if (pszRet)
|
|
{
|
|
LPTSTR pszDest = pszRet;
|
|
for (i = 0; pidl = IDA_GetIDListPtr(pida, i); i++)
|
|
{
|
|
_OriginalPath(pidl, szTemp, ARRAYSIZE(szTemp));
|
|
lstrcpy(pszDest, PathFindFileName(szTemp));
|
|
pszDest += lstrlen(pszDest) + 1;
|
|
|
|
ASSERT((ULONG_PTR)((LPBYTE)pszDest - (LPBYTE)pszRet) < cbAlloc);
|
|
ASSERT(*(pszDest) == 0); // zero init alloc
|
|
}
|
|
ASSERT((LPTSTR)((LPBYTE)pszRet + cbAlloc - sizeof(TCHAR)) == pszDest);
|
|
ASSERT(*pszDest == 0); // zero init alloc
|
|
}
|
|
return pszRet;
|
|
}
|
|
|
|
class CBitBucketData : public CFSIDLData
|
|
{
|
|
public:
|
|
CBitBucketData(CBitBucket *pbbf, UINT cidl, LPCITEMIDLIST apidl[]): CFSIDLData(pbbf->_pidl, cidl, apidl, NULL), _pbbf(pbbf)
|
|
{
|
|
_pbbf->AddRef();
|
|
}
|
|
|
|
// IDataObject methods overwrite
|
|
STDMETHODIMP GetData(FORMATETC *pFmtEtc, STGMEDIUM *pstm);
|
|
STDMETHODIMP QueryGetData(FORMATETC *pFmtEtc);
|
|
|
|
private:
|
|
~CBitBucketData()
|
|
{
|
|
_pbbf->Release();
|
|
}
|
|
|
|
CBitBucket *_pbbf;
|
|
};
|
|
|
|
STDMETHODIMP CBitBucketData::QueryGetData(FORMATETC * pformatetc)
|
|
{
|
|
ASSERT(g_cfFileNameMap);
|
|
|
|
if (pformatetc->cfFormat == g_cfFileNameMap && (pformatetc->tymed & TYMED_HGLOBAL))
|
|
{
|
|
return S_OK; // same as S_OK
|
|
}
|
|
return CFSIDLData::QueryGetData(pformatetc);
|
|
}
|
|
|
|
STDMETHODIMP CBitBucketData::GetData(FORMATETC * pformatetcIn, STGMEDIUM * pmedium)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
ASSERT(g_cfFileNameMap);
|
|
|
|
if (pformatetcIn->cfFormat == g_cfFileNameMap && (pformatetcIn->tymed & TYMED_HGLOBAL))
|
|
{
|
|
STGMEDIUM medium;
|
|
|
|
LPIDA pida = DataObj_GetHIDA(this, &medium);
|
|
if (medium.hGlobal)
|
|
{
|
|
pmedium->hGlobal = _pbbf->BuildDestSpecs(pida);
|
|
pmedium->tymed = TYMED_HGLOBAL;
|
|
pmedium->pUnkForRelease = NULL;
|
|
|
|
HIDA_ReleaseStgMedium(pida, &medium);
|
|
|
|
hr = pmedium->hGlobal ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = CFSIDLData::GetData(pformatetcIn, pmedium);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// We need to be able to compare the names of two bbpidls. Since either of
|
|
// them could be a unicode name, we might have to convert both to unicode.
|
|
//
|
|
int CBitBucket::_CompareOriginal(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
|
{
|
|
TCHAR szOrig1[MAX_PATH], szOrig2[MAX_PATH];
|
|
|
|
if (SUCCEEDED(_OriginalPath(pidl1, szOrig1, ARRAYSIZE(szOrig1))) &&
|
|
SUCCEEDED(_OriginalPath(pidl2, szOrig2, ARRAYSIZE(szOrig2))))
|
|
{
|
|
PathRemoveFileSpec(szOrig1);
|
|
PathRemoveFileSpec(szOrig2);
|
|
return lstrcmpi(szOrig1,szOrig2);
|
|
}
|
|
|
|
return -1; // failure, say 2 > 1
|
|
}
|
|
|
|
// we are cheating here passing pidl1 and pidl2 to one folder when
|
|
// the could have come from different folders. but since these are
|
|
// file system we can get away with this, see findfldr.cpp for the
|
|
// code to deal with this in the general case
|
|
|
|
HRESULT CBitBucket::_Compare(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
|
{
|
|
IShellFolder *psf;
|
|
HRESULT hr = _FolderFromIDList(pidl1, IID_PPV_ARG(IShellFolder, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->CompareIDs(lParam, pidl1, pidl2);
|
|
psf->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
enum
|
|
{
|
|
ICOL_NAME = 0,
|
|
ICOL_ORIGINAL = 1,
|
|
ICOL_DATEDELETED = 2,
|
|
};
|
|
|
|
const COLUMN_INFO c_bb_cols[] =
|
|
{
|
|
DEFINE_COL_STR_ENTRY(SCID_NAME, 30, IDS_NAME_COL),
|
|
DEFINE_COL_STR_ENTRY(SCID_DELETEDFROM, 30, IDS_DELETEDFROM_COL),
|
|
DEFINE_COL_DATE_ENTRY(SCID_DATEDELETED, IDS_DATEDELETED_COL),
|
|
};
|
|
|
|
STDMETHODIMP CBitBucket::CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
ASSERT(pidl1 == ILFindLastID(pidl1));
|
|
|
|
UINT iColumn = ((DWORD)lParam & SHCIDS_COLUMNMASK);
|
|
|
|
PUBBDATAENTRYA pbbde1 = _IsValid(pidl1); // both may be NULL for pure FS pidl
|
|
PUBBDATAENTRYA pbbde2 = _IsValid(pidl2); // generated by change notify
|
|
|
|
if (_MapColIndex(&iColumn))
|
|
{
|
|
switch (iColumn)
|
|
{
|
|
case ICOL_NAME:
|
|
// compare the real filenames first, if they are different,
|
|
// try comparing the display name
|
|
hr = _Compare(lParam, pidl1, pidl2);
|
|
if (0 == hr)
|
|
return hr; // fs pidl comapre says they are the same
|
|
else
|
|
{
|
|
TCHAR sz1[MAX_PATH], sz2[MAX_PATH];
|
|
DisplayNameOf(this, pidl1, SHGDN_INFOLDER, sz1, ARRAYSIZE(sz1));
|
|
DisplayNameOf(this, pidl2, SHGDN_INFOLDER, sz2, ARRAYSIZE(sz2));
|
|
int iRes = StrCmpLogicalRestricted(sz1, sz2);
|
|
if (iRes)
|
|
return ResultFromShort(iRes);
|
|
|
|
if (pbbde1 && pbbde2)
|
|
return ResultFromShort(pbbde1->idDrive - pbbde2->idDrive);
|
|
}
|
|
break;
|
|
|
|
case ICOL_ORIGINAL:
|
|
{
|
|
int iRes = _CompareOriginal(pidl1, pidl2);
|
|
if (iRes)
|
|
return ResultFromShort(iRes);
|
|
}
|
|
break;
|
|
|
|
case ICOL_DATEDELETED:
|
|
{
|
|
FILETIME ft1, ft2;
|
|
_GetDeletedFileTime(pidl1, &ft1);
|
|
_GetDeletedFileTime(pidl2, &ft2);
|
|
int iRes = CompareFileTime(&ft1, &ft2);
|
|
if (iRes)
|
|
return ResultFromShort(iRes);
|
|
}
|
|
break;
|
|
}
|
|
lParam &= ~SHCIDS_COLUMNMASK; // fall thorugh to sort on name...
|
|
}
|
|
else if (pbbde1 && pbbde2 && (_SizeColumn() == iColumn))
|
|
{
|
|
if (pbbde1->dwSize < pbbde2->dwSize)
|
|
return ResultFromShort(-1);
|
|
if (pbbde1->dwSize > pbbde2->dwSize)
|
|
return ResultFromShort(1);
|
|
lParam &= ~SHCIDS_COLUMNMASK; // fall thorugh to sort on name...
|
|
}
|
|
else
|
|
{
|
|
lParam = (lParam & ~SHCIDS_COLUMNMASK) | iColumn;
|
|
}
|
|
|
|
return _Compare(lParam, pidl1, pidl2);
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetAttributesOf(UINT cidl, LPCITEMIDLIST *apidl, ULONG *rgfOut)
|
|
{
|
|
HRESULT hr;
|
|
if (IsSelf(cidl, apidl))
|
|
{
|
|
// asking about folder as a whole
|
|
*rgfOut = SFGAO_FOLDER | SFGAO_DROPTARGET | SFGAO_HASPROPSHEET;
|
|
|
|
if (SHRestricted(REST_BITBUCKNOPROP))
|
|
{
|
|
*rgfOut &= ~SFGAO_HASPROPSHEET;
|
|
}
|
|
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
IShellFolder *psf;
|
|
hr = _FolderFromIDList(apidl[0], IID_PPV_ARG(IShellFolder, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->GetAttributesOf(cidl, apidl, rgfOut);
|
|
psf->Release();
|
|
// only allow these attributes to be returned
|
|
*rgfOut &= (SFGAO_CANMOVE | SFGAO_CANDELETE | SFGAO_HASPROPSHEET | SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM | SFGAO_LINK);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
int CBitBucket::_DataObjToFileOpString(IDataObject *pdtobj, LPTSTR *ppszSrc, LPTSTR *ppszDest)
|
|
{
|
|
int cItems = 0;
|
|
STGMEDIUM medium;
|
|
LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
|
|
|
|
*ppszSrc = NULL;
|
|
*ppszDest = NULL;
|
|
|
|
if (pida)
|
|
{
|
|
cItems = pida->cidl;
|
|
|
|
// start with null terminated strings
|
|
int cchSrc = 1, cchDest = 1;
|
|
|
|
LPTSTR pszSrc = (LPTSTR)LocalAlloc(LPTR, cchSrc * sizeof(TCHAR));
|
|
LPTSTR pszDest = (LPTSTR)LocalAlloc(LPTR, cchDest * sizeof(TCHAR));
|
|
|
|
if (!pszSrc || !pszDest)
|
|
{
|
|
// skip the loop and fail
|
|
cItems = 0;
|
|
}
|
|
|
|
for (int i = 0 ; i < cItems; i++)
|
|
{
|
|
LPITEMIDLIST pidl = IDA_FullIDList(pida, i);
|
|
if (pidl)
|
|
{
|
|
TCHAR szTemp[MAX_PATH];
|
|
|
|
// src
|
|
SHGetPathFromIDList(pidl, szTemp);
|
|
|
|
// Done with this already. Free now in case we exit early.
|
|
ILFree(pidl);
|
|
|
|
int cchSrcFile = lstrlen(szTemp) + 1;
|
|
LPTSTR psz = (LPTSTR)LocalReAlloc((HLOCAL)pszSrc, (cchSrc + cchSrcFile) * sizeof(TCHAR), LMEM_MOVEABLE | LMEM_ZEROINIT);
|
|
if (psz)
|
|
{
|
|
pszSrc = psz;
|
|
lstrcpy(pszSrc + cchSrc - 1, szTemp);
|
|
cchSrc += cchSrcFile;
|
|
}
|
|
else
|
|
{
|
|
cItems = 0;
|
|
break;
|
|
}
|
|
|
|
// dest
|
|
_OriginalPath(IDA_GetIDListPtr(pida, i), szTemp, ARRAYSIZE(szTemp));
|
|
|
|
int cchDestFile = lstrlen(szTemp) + 1;
|
|
psz = (LPTSTR)LocalReAlloc((HLOCAL)pszDest, (cchDest + cchDestFile) * sizeof(TCHAR), LMEM_MOVEABLE | LMEM_ZEROINIT);
|
|
if (psz)
|
|
{
|
|
pszDest = psz;
|
|
lstrcpy(pszDest + cchDest - 1, szTemp);
|
|
cchDest += cchDestFile;
|
|
}
|
|
else
|
|
{
|
|
// out of memory!
|
|
cItems = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == cItems)
|
|
{
|
|
// ok to pass NULL here
|
|
LocalFree((HLOCAL)pszSrc);
|
|
LocalFree((HLOCAL)pszDest);
|
|
}
|
|
else
|
|
{
|
|
*ppszSrc = pszSrc;
|
|
*ppszDest = pszDest;
|
|
}
|
|
|
|
HIDA_ReleaseStgMedium(pida, &medium);
|
|
}
|
|
return cItems;
|
|
}
|
|
|
|
|
|
//
|
|
// restores the list of files in the IDataObject
|
|
//
|
|
void CBitBucket::_RestoreFileList(HWND hwnd, IDataObject * pdtobj)
|
|
{
|
|
LPTSTR pszSrc, pszDest;
|
|
|
|
if (_DataObjToFileOpString(pdtobj, &pszSrc, &pszDest))
|
|
{
|
|
// now do the actual restore.
|
|
SHFILEOPSTRUCT sFileOp = { hwnd, FO_MOVE, pszSrc, pszDest,
|
|
FOF_MULTIDESTFILES | FOF_SIMPLEPROGRESS | FOF_NOCONFIRMMKDIR,
|
|
FALSE, NULL, MAKEINTRESOURCE(IDS_BB_RESTORINGFILES)};
|
|
|
|
DECLAREWAITCURSOR;
|
|
|
|
SetWaitCursor();
|
|
|
|
if (SHFileOperation(&sFileOp) == ERROR_SUCCESS)
|
|
{
|
|
SHChangeNotifyHandleEvents();
|
|
BBCheckRestoredFiles(pszSrc);
|
|
}
|
|
|
|
LocalFree((HLOCAL)pszSrc);
|
|
LocalFree((HLOCAL)pszDest);
|
|
|
|
ResetWaitCursor();
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// nukes the list of files in the IDataObject
|
|
//
|
|
void CBitBucket::_NukeFileList(HWND hwnd, IDataObject * pdtobj)
|
|
{
|
|
LPTSTR pszSrc, pszDest;
|
|
int nFiles = _DataObjToFileOpString(pdtobj, &pszSrc, &pszDest);
|
|
if (nFiles)
|
|
{
|
|
// now do the actual nuke.
|
|
WIN32_FIND_DATA fd;
|
|
CONFIRM_DATA cd = {CONFIRM_DELETE_FILE | CONFIRM_DELETE_FOLDER | CONFIRM_PROGRAM_FILE | CONFIRM_MULTIPLE, 0};
|
|
SHFILEOPSTRUCT sFileOp = { hwnd, FO_DELETE, pszSrc, NULL, FOF_NOCONFIRMATION | FOF_SIMPLEPROGRESS,
|
|
FALSE, NULL, MAKEINTRESOURCE(IDS_BB_DELETINGWASTEBASKETFILES)};
|
|
|
|
DECLAREWAITCURSOR;
|
|
|
|
SetWaitCursor();
|
|
|
|
fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
if (ConfirmFileOp(hwnd, NULL, &cd, nFiles, 0, CONFIRM_DELETE_FILE | CONFIRM_WASTEBASKET_PURGE, pszDest, &fd, NULL, &fd, NULL) == IDYES)
|
|
{
|
|
SHFileOperation(&sFileOp);
|
|
|
|
SHChangeNotifyHandleEvents();
|
|
|
|
// update the icon if there are objects left in the list
|
|
int iItems = (int) ShellFolderView_GetObjectCount(hwnd);
|
|
UpdateIcon(iItems);
|
|
}
|
|
|
|
LocalFree((HLOCAL)pszSrc);
|
|
LocalFree((HLOCAL)pszDest);
|
|
|
|
ResetWaitCursor();
|
|
}
|
|
}
|
|
|
|
void EnableTrackbarAndFamily(HWND hDlg, BOOL f)
|
|
{
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BBSIZE), f);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BBSIZETEXT), f);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_TEXT), f);
|
|
}
|
|
|
|
void CBitBucket::_GlobalPropOnCommand(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
|
|
{
|
|
BBPROPSHEETINFO *ppsi = (BBPROPSHEETINFO *)GetWindowLongPtr(hDlg, DWLP_USER);
|
|
BOOL fNukeOnDelete;
|
|
|
|
switch (id)
|
|
{
|
|
case IDC_GLOBAL:
|
|
case IDC_INDEPENDENT:
|
|
fNukeOnDelete = IsDlgButtonChecked(hDlg, IDC_NUKEONDELETE);
|
|
|
|
ppsi->fUseGlobalSettings = (IsDlgButtonChecked(hDlg, IDC_GLOBAL) == BST_CHECKED) ? TRUE : FALSE;
|
|
EnableWindow(GetDlgItem(hDlg, IDC_NUKEONDELETE), ppsi->fUseGlobalSettings && !ppsi->fPolicyNukeOnDelete);
|
|
EnableTrackbarAndFamily(hDlg, ppsi->fUseGlobalSettings && !fNukeOnDelete && !ppsi->fPolicyPercent);
|
|
|
|
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
|
|
break;
|
|
|
|
case IDC_NUKEONDELETE:
|
|
fNukeOnDelete = IsDlgButtonChecked(hDlg, IDC_NUKEONDELETE);
|
|
if (fNukeOnDelete)
|
|
{
|
|
// In order to help protect users, when they turn on "Remove files immedately" we also
|
|
// check the "show delete confimation" box automatically for them. Thus, they will have
|
|
// to explicitly uncheck it if they do not want confimation that their files will be nuked.
|
|
CheckDlgButton(hDlg, IDC_CONFIRMDELETE, BST_CHECKED);
|
|
}
|
|
|
|
ppsi->fNukeOnDelete = fNukeOnDelete;
|
|
EnableTrackbarAndFamily(hDlg, !fNukeOnDelete && !ppsi->fPolicyPercent);
|
|
// fall through
|
|
case IDC_CONFIRMDELETE:
|
|
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0L);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RelayMessageToChildren(HWND hwnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
for (HWND hwndChild = GetWindow(hwnd, GW_CHILD); hwndChild != NULL; hwndChild = GetWindow(hwndChild, GW_HWNDNEXT))
|
|
{
|
|
SendMessage(hwndChild, uMessage, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
//
|
|
// This is the dlg proc for the "Global" tab on the recycle bin
|
|
//
|
|
BOOL_PTR CALLBACK CBitBucket::_GlobalPropDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
BBPROPSHEETINFO *ppsi = (BBPROPSHEETINFO *)GetWindowLongPtr(hDlg, DWLP_USER);
|
|
|
|
switch (uMsg)
|
|
{
|
|
HANDLE_MSG(hDlg, WM_COMMAND, _GlobalPropOnCommand);
|
|
|
|
case WM_INITDIALOG:
|
|
{
|
|
HWND hwndTrack = GetDlgItem(hDlg, IDC_BBSIZE);
|
|
SHELLSTATE ss;
|
|
|
|
// make sure the info we have is current
|
|
RefreshAllBBDriveSettings();
|
|
|
|
ppsi = (BBPROPSHEETINFO *)lParam;
|
|
SetWindowLongPtr(hDlg, DWLP_USER, lParam);
|
|
|
|
SendMessage(hwndTrack, TBM_SETTICFREQ, 10, 0);
|
|
SendMessage(hwndTrack, TBM_SETRANGE, FALSE, MAKELONG(0, 100));
|
|
SendMessage(hwndTrack, TBM_SETPOS, TRUE, ppsi->iOriginalPercent);
|
|
|
|
EnableWindow(GetDlgItem(hDlg, IDC_NUKEONDELETE), ppsi->fUseGlobalSettings && !ppsi->fPolicyNukeOnDelete);
|
|
EnableTrackbarAndFamily(hDlg, ppsi->fUseGlobalSettings && !ppsi->fNukeOnDelete && !ppsi->fPolicyPercent);
|
|
CheckDlgButton(hDlg, IDC_NUKEONDELETE, ppsi->fNukeOnDelete);
|
|
CheckRadioButton(hDlg, IDC_INDEPENDENT, IDC_GLOBAL, ppsi->fUseGlobalSettings ? IDC_GLOBAL : IDC_INDEPENDENT);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_INDEPENDENT), !ppsi->fPolicyNukeOnDelete);
|
|
|
|
SHGetSetSettings(&ss, SSF_NOCONFIRMRECYCLE, FALSE);
|
|
CheckDlgButton(hDlg, IDC_CONFIRMDELETE, !ss.fNoConfirmRecycle);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_CONFIRMDELETE), !SHRestricted(REST_BITBUCKCONFIRMDELETE));
|
|
}
|
|
// fall through to set iGlobalPercent
|
|
case WM_HSCROLL:
|
|
{
|
|
TCHAR szPercent[20];
|
|
HWND hwndTrack = GetDlgItem(hDlg, IDC_BBSIZE);
|
|
|
|
ppsi->iPercent = (int) SendMessage(hwndTrack, TBM_GETPOS, 0, 0);
|
|
wsprintf(szPercent, TEXT("%d%%"), ppsi->iPercent);
|
|
SetDlgItemText(hDlg, IDC_BBSIZETEXT, szPercent);
|
|
|
|
if (ppsi->iPercent != ppsi->iOriginalPercent)
|
|
{
|
|
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0L);
|
|
|
|
if (ppsi->iPercent == 0)
|
|
{
|
|
// In order to help protect users, when they set the % slider to zero we also
|
|
// check the "show delete confimation" box automatically for them. Thus, they will have
|
|
// to explicitly uncheck it if they do not want confimation that their files will be nuked.
|
|
CheckDlgButton(hDlg, IDC_CONFIRMDELETE, BST_CHECKED);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
case WM_HELP:
|
|
WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, NULL,
|
|
HELP_WM_HELP, (ULONG_PTR)(LPTSTR) aBitBucketPropHelpIDs);
|
|
return TRUE;
|
|
|
|
case WM_CONTEXTMENU:
|
|
WinHelp((HWND) wParam, NULL, HELP_CONTEXTMENU,
|
|
(ULONG_PTR)(void *) aBitBucketPropHelpIDs);
|
|
return TRUE;
|
|
|
|
case WM_WININICHANGE:
|
|
case WM_SYSCOLORCHANGE:
|
|
case WM_DISPLAYCHANGE:
|
|
RelayMessageToChildren(hDlg, uMsg, wParam, lParam);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
CheckCompactAndPurge();
|
|
SHUpdateRecycleBinIcon();
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
switch (((NMHDR *)lParam)->code)
|
|
{
|
|
case PSN_APPLY:
|
|
{
|
|
SHELLSTATE ss;
|
|
|
|
ss.fNoConfirmRecycle = !IsDlgButtonChecked(hDlg, IDC_CONFIRMDELETE);
|
|
SHGetSetSettings(&ss, SSF_NOCONFIRMRECYCLE, TRUE);
|
|
|
|
ppsi->fNukeOnDelete = (IsDlgButtonChecked(hDlg, IDC_NUKEONDELETE) == BST_CHECKED) ? TRUE : FALSE;
|
|
ppsi->fUseGlobalSettings = (IsDlgButtonChecked(hDlg, IDC_INDEPENDENT) == BST_CHECKED) ? FALSE : TRUE;
|
|
|
|
// if anything on the global tab changed, update all the drives
|
|
if (ppsi->fUseGlobalSettings != ppsi->fOriginalUseGlobalSettings ||
|
|
ppsi->fNukeOnDelete != ppsi->fOriginalNukeOnDelete ||
|
|
ppsi->iPercent != ppsi->iOriginalPercent)
|
|
{
|
|
// NOTE: We get a PSN_APPLY after all the drive tabs. This has to be this way so that
|
|
// if global settings change, then the global tab will re-apply all the most current settings
|
|
// bassed on the global variables that get set above.
|
|
|
|
// this sets the new global settings in the registry
|
|
if (!PersistGlobalSettings(ppsi->fUseGlobalSettings, ppsi->fNukeOnDelete, ppsi->iPercent))
|
|
{
|
|
// we failed, so show the error dialog and bail
|
|
ShellMessageBox(HINST_THISDLL,
|
|
hDlg,
|
|
MAKEINTRESOURCE(IDS_BB_CANNOTCHANGESETTINGS),
|
|
MAKEINTRESOURCE(IDS_WASTEBASKET),
|
|
MB_OK | MB_ICONEXCLAMATION);
|
|
|
|
SetDlgMsgResult(hDlg, WM_NOTIFY, PSNRET_INVALID_NOCHANGEPAGE);
|
|
return TRUE;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_BITBUCKETS; i++)
|
|
{
|
|
if (MakeBitBucket(i))
|
|
{
|
|
BOOL bPurge = TRUE;
|
|
|
|
// we need to purge all the drives in this case
|
|
RegSetValueEx(g_pBitBucket[i]->hkeyPerUser, TEXT("NeedToPurge"), 0, REG_DWORD, (LPBYTE)&bPurge, sizeof(bPurge));
|
|
|
|
RefreshBBDriveSettings(i);
|
|
}
|
|
}
|
|
|
|
ppsi->fOriginalUseGlobalSettings = ppsi->fUseGlobalSettings;
|
|
ppsi->fOriginalNukeOnDelete = ppsi->fNukeOnDelete;
|
|
ppsi->iOriginalPercent = ppsi->iPercent;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
SetDlgMsgResult(hDlg, WM_NOTIFY, 0);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL_PTR CALLBACK CBitBucket::_DriveDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
BBPROPSHEETINFO *ppsi = (BBPROPSHEETINFO *)GetWindowLongPtr(hDlg, DWLP_USER);
|
|
TCHAR szDiskSpace[40];
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
HWND hwndTrack = GetDlgItem(hDlg, IDC_BBSIZE);
|
|
|
|
ppsi = (BBPROPSHEETINFO *)lParam;
|
|
SetWindowLongPtr(hDlg, DWLP_USER, lParam);
|
|
|
|
SendMessage(hwndTrack, TBM_SETTICFREQ, 10, 0);
|
|
SendMessage(hwndTrack, TBM_SETRANGE, FALSE, MAKELONG(0, 100));
|
|
SendMessage(hwndTrack, TBM_SETPOS, TRUE, ppsi->iPercent);
|
|
CheckDlgButton(hDlg, IDC_NUKEONDELETE, ppsi->fNukeOnDelete);
|
|
|
|
// set the disk space info
|
|
StrFormatByteSize64(g_pBitBucket[ppsi->idDrive]->qwDiskSize, szDiskSpace, ARRAYSIZE(szDiskSpace));
|
|
SetDlgItemText(hDlg, IDC_DISKSIZEDATA, szDiskSpace);
|
|
wParam = 0;
|
|
}
|
|
// fall through
|
|
|
|
case WM_HSCROLL:
|
|
{
|
|
ULARGE_INTEGER ulBucketSize;
|
|
HWND hwndTrack = GetDlgItem(hDlg, IDC_BBSIZE);
|
|
ppsi->iPercent = (int)SendMessage(hwndTrack, TBM_GETPOS, 0, 0);
|
|
|
|
wsprintf(szDiskSpace, TEXT("%d%%"), ppsi->iPercent);
|
|
SetDlgItemText(hDlg, IDC_BBSIZETEXT, szDiskSpace);
|
|
|
|
if (ppsi->iPercent != ppsi->iOriginalPercent)
|
|
{
|
|
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
|
|
}
|
|
|
|
// we peg the max size of the recycle bin to 4 gig
|
|
ulBucketSize.QuadPart = (ppsi->pGlobal->fUseGlobalSettings ? ppsi->pGlobal->iPercent : ppsi->iPercent) * (g_pBitBucket[ppsi->idDrive]->qwDiskSize / 100);
|
|
StrFormatByteSize64(ulBucketSize.HighPart ? (DWORD)-1 : ulBucketSize.LowPart, szDiskSpace, ARRAYSIZE(szDiskSpace));
|
|
SetDlgItemText(hDlg, IDC_BYTESIZEDATA, szDiskSpace);
|
|
return TRUE;
|
|
}
|
|
|
|
case WM_HELP:
|
|
WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, NULL,
|
|
HELP_WM_HELP, (ULONG_PTR)(LPTSTR) aBitBucketPropHelpIDs);
|
|
return TRUE;
|
|
|
|
case WM_CONTEXTMENU:
|
|
WinHelp((HWND) wParam, NULL, HELP_CONTEXTMENU,
|
|
(ULONG_PTR)(void *) aBitBucketPropHelpIDs);
|
|
return TRUE;
|
|
|
|
case WM_COMMAND:
|
|
{
|
|
WORD wCommandID = GET_WM_COMMAND_ID(wParam, lParam);
|
|
if (wCommandID == IDC_NUKEONDELETE)
|
|
{
|
|
SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0L);
|
|
EnableTrackbarAndFamily(hDlg, !IsDlgButtonChecked(hDlg, IDC_NUKEONDELETE) && !ppsi->fPolicyPercent);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BYTESIZE), !IsDlgButtonChecked(hDlg, IDC_NUKEONDELETE));
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BYTESIZEDATA), !IsDlgButtonChecked(hDlg, IDC_NUKEONDELETE));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
switch (((NMHDR *)lParam)->code)
|
|
{
|
|
case PSN_APPLY:
|
|
{
|
|
ppsi->fNukeOnDelete = (IsDlgButtonChecked(hDlg, IDC_NUKEONDELETE) == BST_CHECKED) ? TRUE : FALSE;
|
|
|
|
// update the info in the registry
|
|
if (!PersistBBDriveSettings(ppsi->idDrive, ppsi->iPercent, ppsi->fNukeOnDelete))
|
|
{
|
|
// we failed, so show the error dialog and bail
|
|
ShellMessageBox(HINST_THISDLL, hDlg,
|
|
MAKEINTRESOURCE(IDS_BB_CANNOTCHANGESETTINGS),
|
|
MAKEINTRESOURCE(IDS_WASTEBASKET),
|
|
MB_OK | MB_ICONEXCLAMATION);
|
|
|
|
SetDlgMsgResult(hDlg, WM_NOTIFY, PSNRET_INVALID_NOCHANGEPAGE);
|
|
return TRUE;
|
|
}
|
|
|
|
// only purge this drive if the user set the slider to a smaller value
|
|
if (ppsi->iPercent < ppsi->iOriginalPercent)
|
|
{
|
|
BOOL bPurge = TRUE;
|
|
|
|
// since this drive just shrunk, we need to purge the files in it
|
|
RegSetValueEx(g_pBitBucket[ppsi->idDrive]->hkeyPerUser, TEXT("NeedToPurge"), 0, REG_DWORD, (LPBYTE)&bPurge, sizeof(bPurge));
|
|
}
|
|
|
|
ppsi->iOriginalPercent = ppsi->iPercent;
|
|
ppsi->fOriginalNukeOnDelete = ppsi->fNukeOnDelete;
|
|
|
|
// update the g_pBitBucket[] for this drive
|
|
|
|
// NOTE: We get a PSN_APPLY before the global tab does. This has to be this way so that
|
|
// if global settings change, then the global tab will re-apply all the most current settings
|
|
// bassed on the global variables that get set in his tab.
|
|
RefreshBBDriveSettings(ppsi->idDrive);
|
|
}
|
|
break;
|
|
|
|
case PSN_SETACTIVE:
|
|
{
|
|
BOOL fNukeOnDelete;
|
|
fNukeOnDelete = ppsi->pGlobal->fUseGlobalSettings ? ppsi->pGlobal->fNukeOnDelete :
|
|
IsDlgButtonChecked(hDlg, IDC_NUKEONDELETE);
|
|
|
|
EnableWindow(GetDlgItem(hDlg, IDC_NUKEONDELETE), !ppsi->pGlobal->fUseGlobalSettings && !ppsi->fPolicyNukeOnDelete);
|
|
EnableTrackbarAndFamily(hDlg, !ppsi->pGlobal->fUseGlobalSettings && !fNukeOnDelete && !ppsi->fPolicyPercent);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BYTESIZE), !fNukeOnDelete);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BYTESIZEDATA), !fNukeOnDelete);
|
|
|
|
// send this to make sure that the "space reserved" field is accurate when using global settings
|
|
SendMessage(hDlg, WM_HSCROLL, 0, 0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
SetDlgMsgResult(hDlg, WM_NOTIFY, 0);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
PROPSHEETPAGE psp;
|
|
LPITEMIDLIST pidl;
|
|
FILETIME ftDeleted;
|
|
DWORD dwSize;
|
|
TCHAR szOriginal[MAX_PATH];
|
|
} BBFILEPROPINFO;
|
|
|
|
|
|
// property sheet page for a file/folder in the bitbucket
|
|
|
|
BOOL_PTR CALLBACK CBitBucket::_FilePropDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
BBFILEPROPINFO * pbbfpi = (BBFILEPROPINFO *)GetWindowLongPtr(hDlg, DWLP_USER);
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
pbbfpi = (BBFILEPROPINFO *)lParam;
|
|
SetWindowLongPtr(hDlg, DWLP_USER, lParam);
|
|
|
|
SHFILEINFO sfi = {0};
|
|
SHGetFileInfo((LPTSTR)pbbfpi->pidl, 0, &sfi, sizeof(sfi),
|
|
SHGFI_PIDL | SHGFI_TYPENAME | SHGFI_ICON | SHGFI_LARGEICON | SHGFI_ADDOVERLAYS | SHGFI_DISPLAYNAME);
|
|
|
|
// icon
|
|
ReplaceDlgIcon(hDlg, IDD_ITEMICON, sfi.hIcon);
|
|
|
|
// Type
|
|
SetDlgItemText(hDlg, IDD_FILETYPE, sfi.szTypeName);
|
|
|
|
TCHAR szTemp[MAX_PATH];
|
|
StrCpyN(szTemp, pbbfpi->szOriginal, ARRAYSIZE(szTemp));
|
|
PathRemoveExtension(szTemp);
|
|
SetDlgItemText(hDlg, IDD_NAME, PathFindFileName(szTemp));
|
|
|
|
// origin
|
|
PathRemoveFileSpec(szTemp);
|
|
SetDlgItemText(hDlg, IDD_LOCATION, PathFindFileName(szTemp));
|
|
|
|
// deleted time
|
|
SetDateTimeText(hDlg, IDD_DELETED, &pbbfpi->ftDeleted);
|
|
|
|
// Size
|
|
StrFormatByteSize64(pbbfpi->dwSize, szTemp, ARRAYSIZE(szTemp));
|
|
SetDlgItemText(hDlg, IDD_FILESIZE, szTemp);
|
|
|
|
if (SHGetPathFromIDList(pbbfpi->pidl, szTemp))
|
|
{
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hfind = FindFirstFile(szTemp, &fd);
|
|
if (hfind != INVALID_HANDLE_VALUE)
|
|
{
|
|
SetDateTimeText(hDlg, IDD_CREATED, &fd.ftCreationTime);
|
|
FindClose(hfind);
|
|
|
|
// We don't allow user to change compression attribute on a deleted file
|
|
// but we do show the current compressed state
|
|
TCHAR szRoot[MAX_PATH], szFSName[12];
|
|
|
|
// If file's volume doesn't support compression, don't show
|
|
// "Compressed" checkbox.
|
|
// If compression is supported, show the checkbox and check/uncheck
|
|
// it to indicate compression state of the file.
|
|
lstrcpy(szRoot, szTemp);
|
|
PathStripToRoot(szRoot);
|
|
PathAddBackslash(szRoot); // for UNC (MyDocs) case
|
|
|
|
DWORD dwVolumeFlags;
|
|
if (GetVolumeInformation(szRoot, NULL, 0, NULL, NULL, &dwVolumeFlags, szFSName, ARRAYSIZE(szFSName)))
|
|
{
|
|
if (dwVolumeFlags & FS_FILE_COMPRESSION)
|
|
{
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
|
|
CheckDlgButton(hDlg, IDD_COMPRESS, 1);
|
|
ShowWindow(GetDlgItem(hDlg, IDD_COMPRESS), SW_SHOW);
|
|
}
|
|
|
|
if (dwVolumeFlags & FS_FILE_ENCRYPTION)
|
|
{
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)
|
|
CheckDlgButton(hDlg, IDD_ENCRYPT, 1);
|
|
ShowWindow(GetDlgItem(hDlg, IDD_ENCRYPT), SW_SHOW);
|
|
}
|
|
}
|
|
|
|
// file attributes
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
|
|
CheckDlgButton(hDlg, IDD_READONLY, 1);
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
|
|
CheckDlgButton(hDlg, IDD_ARCHIVE, 1);
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
|
|
CheckDlgButton(hDlg, IDD_HIDDEN, 1);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_WININICHANGE:
|
|
case WM_SYSCOLORCHANGE:
|
|
case WM_DISPLAYCHANGE:
|
|
RelayMessageToChildren(hDlg, uMsg, wParam, lParam);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
ReplaceDlgIcon(hDlg, IDD_ITEMICON, NULL);
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
{
|
|
UINT id = GET_WM_COMMAND_ID(wParam, lParam);
|
|
switch (id)
|
|
{
|
|
case IDD_RESTORE:
|
|
if (S_OK == SHInvokeCommandOnPidl(hDlg, NULL, pbbfpi->pidl, 0, "undelete"))
|
|
{
|
|
// We succeeded, so disable the button (invoking again will fail)
|
|
EnableWindow(GetDlgItem(hDlg, IDD_RESTORE), FALSE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
switch (((NMHDR *)lParam)->code)
|
|
{
|
|
case PSN_APPLY:
|
|
case PSN_SETACTIVE:
|
|
case PSN_KILLACTIVE:
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_HELP:
|
|
WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, NULL,
|
|
HELP_WM_HELP, (ULONG_PTR)(LPTSTR) aBitBucketHelpIDs);
|
|
return TRUE;
|
|
|
|
case WM_CONTEXTMENU:
|
|
WinHelp((HWND) wParam, NULL, HELP_CONTEXTMENU,
|
|
(ULONG_PTR)(void *) aBitBucketHelpIDs);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void CBitBucket::_GetDriveDisplayName(int idDrive, LPTSTR pszName, UINT cchSize)
|
|
{
|
|
TCHAR szDrive[MAX_PATH];
|
|
|
|
DriveIDToBBRoot(idDrive, szDrive);
|
|
|
|
SHFILEINFO sfi;
|
|
if (SHGetFileInfo(szDrive, FILE_ATTRIBUTE_DIRECTORY, &sfi, sizeof(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_DISPLAYNAME))
|
|
{
|
|
lstrcpyn(pszName, sfi.szDisplayName, cchSize);
|
|
}
|
|
|
|
// If SERVERDRIVE, attempt to overwrite the default display name with the display
|
|
// name for the mydocs folder on the desktop, since SERVERDRIVE==mydocs for now
|
|
if (idDrive == SERVERDRIVE)
|
|
{
|
|
GetMyDocumentsDisplayName(pszName, cchSize);
|
|
}
|
|
}
|
|
|
|
BOOL CALLBACK CBitBucket::_AddPagesCallback(HPROPSHEETPAGE psp, LPARAM lParam)
|
|
{
|
|
LPPROPSHEETHEADER ppsh = (LPPROPSHEETHEADER)lParam;
|
|
ppsh->phpage[ppsh->nPages++] = psp;
|
|
return TRUE;
|
|
}
|
|
|
|
// properties for recycle bin
|
|
void CBitBucket::_DefaultProperties()
|
|
{
|
|
UNIQUESTUBINFO usi;
|
|
if (EnsureUniqueStub(_pidl, STUBCLASS_PROPSHEET, NULL, &usi))
|
|
{
|
|
HPROPSHEETPAGE ahpage[MAXPROPPAGES];
|
|
PROPSHEETHEADER psh = {0};
|
|
|
|
psh.dwSize = sizeof(psh);
|
|
psh.dwFlags = PSH_PROPTITLE;
|
|
psh.hInstance = HINST_THISDLL;
|
|
psh.phpage = ahpage;
|
|
|
|
AddPages(_AddPagesCallback, (LPARAM)&psh);
|
|
|
|
psh.pszCaption = MAKEINTRESOURCE(IDS_WASTEBASKET);
|
|
|
|
psh.hwndParent = usi.hwndStub;
|
|
PropertySheet(&psh);
|
|
FreeUniqueStub(&usi);
|
|
}
|
|
}
|
|
|
|
// deals with alignment and pidl validation for you
|
|
void CBitBucket::_GetDeletedFileTime(LPCITEMIDLIST pidl, FILETIME *pft)
|
|
{
|
|
ZeroMemory(pft, sizeof(*pft));
|
|
PUBBDATAENTRYA pbbde = _IsValid(pidl);
|
|
if (pbbde)
|
|
*pft = pbbde->ft;
|
|
}
|
|
|
|
DWORD CBitBucket::_GetDeletedSize(LPCITEMIDLIST pidl)
|
|
{
|
|
PUBBDATAENTRYA pbbde = _IsValid(pidl);
|
|
return pbbde ? pbbde->dwSize : 0;
|
|
}
|
|
|
|
// recycled items properties
|
|
// note: we only show the proeprties for the first file if there is a multiple selection
|
|
void CBitBucket::_FileProperties(IDataObject *pdtobj)
|
|
{
|
|
STGMEDIUM medium;
|
|
LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
|
|
if (pida)
|
|
{
|
|
BBFILEPROPINFO bbfpi = {0};
|
|
bbfpi.pidl = IDA_FullIDList(pida, 0);
|
|
if (bbfpi.pidl)
|
|
{
|
|
UNIQUESTUBINFO usi;
|
|
if (EnsureUniqueStub(bbfpi.pidl, STUBCLASS_PROPSHEET, NULL, &usi))
|
|
{
|
|
HPROPSHEETPAGE ahpage[MAXPROPPAGES];
|
|
TCHAR szTitle[80];
|
|
|
|
bbfpi.psp.dwSize = sizeof(bbfpi);
|
|
bbfpi.psp.hInstance = HINST_THISDLL;
|
|
bbfpi.psp.pszTemplate = MAKEINTRESOURCE(DLG_DELETEDFILEPROP);
|
|
bbfpi.psp.pfnDlgProc = _FilePropDlgProc;
|
|
bbfpi.psp.pszTitle = szTitle;
|
|
|
|
_OriginalPath(IDA_GetIDListPtr(pida, 0), bbfpi.szOriginal, ARRAYSIZE(bbfpi.szOriginal));
|
|
bbfpi.dwSize = _GetDeletedSize(IDA_GetIDListPtr(pida, 0));
|
|
|
|
_GetDeletedFileTime(IDA_GetIDListPtr(pida, 0), &bbfpi.ftDeleted);
|
|
|
|
lstrcpyn(szTitle, PathFindFileName(bbfpi.szOriginal), ARRAYSIZE(szTitle));
|
|
PathRemoveExtension(szTitle);
|
|
|
|
PROPSHEETHEADER psh = {0};
|
|
psh.dwSize = sizeof(psh);
|
|
psh.dwFlags = PSH_PROPTITLE;
|
|
psh.hInstance = HINST_THISDLL;
|
|
psh.phpage = ahpage;
|
|
|
|
psh.phpage[0] = CreatePropertySheetPage(&bbfpi.psp);
|
|
if (psh.phpage[0])
|
|
{
|
|
psh.nPages = 1;
|
|
psh.pszCaption = szTitle;
|
|
|
|
psh.hwndParent = usi.hwndStub;
|
|
PropertySheet(&psh);
|
|
}
|
|
|
|
FreeUniqueStub(&usi);
|
|
}
|
|
ILFree(bbfpi.pidl);
|
|
}
|
|
HIDA_ReleaseStgMedium(pida, &medium);
|
|
}
|
|
return;
|
|
}
|
|
|
|
DWORD WINAPI CBitBucket::_DropThreadInit(BBTHREADDATA *pbbtd)
|
|
{
|
|
STGMEDIUM medium;
|
|
FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
if (SUCCEEDED(pbbtd->pdtobj->GetData(&fmte, &medium)))
|
|
{
|
|
// call delete here so that files will be moved in
|
|
// their respective bins, not necessarily this one.
|
|
DRAGINFO di;
|
|
|
|
di.uSize = sizeof(DRAGINFO);
|
|
|
|
if (DragQueryInfo((HDROP) medium.hGlobal, &di))
|
|
{
|
|
// Since BBWillRecycle() can return true even when the file will NOT be
|
|
// recycled (eg the file will be nuked), we want to warn the user when we
|
|
// are going to nuke something that they initiall thought that it would
|
|
// be recycled
|
|
UINT fOptions = SD_WARNONNUKE;
|
|
|
|
if (!BBWillRecycle(di.lpFileList, NULL) ||
|
|
(di.lpFileList && (di.lpFileList[lstrlen(di.lpFileList)+1] == 0)
|
|
&& PathIsShortcutToProgram(di.lpFileList)))
|
|
fOptions = SD_USERCONFIRMATION;
|
|
|
|
if (IsFileInBitBucket(di.lpFileList))
|
|
{
|
|
LPITEMIDLIST *ppidl = NULL;
|
|
int cidl = CreateMoveCopyList((HDROP)medium.hGlobal, NULL, &ppidl);
|
|
if (ppidl)
|
|
{
|
|
// Bug#163533 (edwardp 8/15/00) Change this to use PositionItems.
|
|
PositionItems_DontUse(pbbtd->hwnd, cidl, ppidl, pbbtd->pdtobj, &pbbtd->ptDrop, pbbtd->fDragDrop, FALSE);
|
|
FreeIDListArray(ppidl, cidl);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TransferDelete(pbbtd->hwnd, (HDROP) medium.hGlobal, fOptions);
|
|
}
|
|
|
|
SHChangeNotifyHandleEvents();
|
|
SHFree(di.lpFileList);
|
|
}
|
|
ReleaseStgMedium(&medium);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
DWORD CALLBACK CBitBucket::_DispatchThreadProc(void *pv)
|
|
{
|
|
BBTHREADDATA *pbbtd = (BBTHREADDATA *)pv;
|
|
|
|
if (pbbtd->pstmDataObj)
|
|
{
|
|
CoGetInterfaceAndReleaseStream(pbbtd->pstmDataObj, IID_PPV_ARG(IDataObject, &pbbtd->pdtobj));
|
|
pbbtd->pstmDataObj = NULL; // this is dead
|
|
}
|
|
|
|
switch (pbbtd->idCmd)
|
|
{
|
|
case DFM_CMD_MOVE:
|
|
if (pbbtd->pdtobj)
|
|
_DropThreadInit(pbbtd);
|
|
break;
|
|
|
|
case DFM_CMD_PROPERTIES:
|
|
case FSIDM_PROPERTIESBG:
|
|
if (pbbtd->pdtobj)
|
|
pbbtd->pbb->_FileProperties(pbbtd->pdtobj);
|
|
else
|
|
pbbtd->pbb->_DefaultProperties(); // no data object for the background
|
|
break;
|
|
|
|
case DFM_CMD_DELETE:
|
|
if (pbbtd->pdtobj)
|
|
pbbtd->pbb->_NukeFileList(pbbtd->hwnd, pbbtd->pdtobj);
|
|
break;
|
|
|
|
case FSIDM_RESTORE:
|
|
if (pbbtd->pdtobj)
|
|
pbbtd->pbb->_RestoreFileList(pbbtd->hwnd, pbbtd->pdtobj);
|
|
break;
|
|
}
|
|
|
|
if (pbbtd->pdtobj)
|
|
pbbtd->pdtobj->Release();
|
|
|
|
pbbtd->pbb->Release();
|
|
|
|
LocalFree((HLOCAL)pbbtd);
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CBitBucket::_LaunchThread(HWND hwnd, IDataObject *pdtobj, WPARAM idCmd)
|
|
{
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
BBTHREADDATA *pbbtd = (BBTHREADDATA *)LocalAlloc(LPTR, sizeof(*pbbtd));
|
|
if (pbbtd)
|
|
{
|
|
pbbtd->hwnd = hwnd;
|
|
pbbtd->idCmd = idCmd;
|
|
pbbtd->pbb = this;
|
|
pbbtd->pbb->AddRef();
|
|
|
|
if (idCmd == DFM_CMD_MOVE)
|
|
pbbtd->fDragDrop = (BOOL)ShellFolderView_GetDropPoint(hwnd, &pbbtd->ptDrop);
|
|
|
|
if (pdtobj)
|
|
CoMarshalInterThreadInterfaceInStream(IID_IDataObject, (IUnknown *)pdtobj, &pbbtd->pstmDataObj);
|
|
|
|
if (SHCreateThread(_DispatchThreadProc, pbbtd, CTF_COINIT, NULL))
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
if (pbbtd->pstmDataObj)
|
|
pbbtd->pstmDataObj->Release();
|
|
|
|
pbbtd->pbb->Release();
|
|
LocalFree((HLOCAL)pbbtd);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT GetVerb(UINT_PTR idCmd, LPSTR pszName, UINT cchMax, BOOL bUnicode)
|
|
{
|
|
HRESULT hr;
|
|
LPCTSTR psz;
|
|
|
|
switch (idCmd)
|
|
{
|
|
case FSIDM_RESTORE:
|
|
psz = TEXT("undelete");
|
|
break;
|
|
|
|
case FSIDM_PURGEALL:
|
|
psz = TEXT("empty");
|
|
break;
|
|
|
|
default:
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
if (bUnicode)
|
|
hr = SHTCharToUnicode(psz, (LPWSTR)pszName, cchMax);
|
|
else
|
|
hr = SHTCharToAnsi(psz, (LPSTR)pszName, cchMax);
|
|
|
|
return hr;
|
|
}
|
|
|
|
CBitBucket *CBitBucket::_FromFolder(IShellFolder *psf)
|
|
{
|
|
CBitBucket *pbbf = NULL;
|
|
if (psf)
|
|
psf->QueryInterface(CLSID_RecycleBin, (void **)&pbbf);
|
|
return pbbf;
|
|
}
|
|
|
|
// item context menu callback
|
|
HRESULT CALLBACK CBitBucket::_ItemMenuCallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj,
|
|
UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CBitBucket *pbbf = _FromFolder(psf);
|
|
HRESULT hr = S_OK; // assume no error
|
|
|
|
switch (uMsg)
|
|
{
|
|
case DFM_MERGECONTEXTMENU:
|
|
CDefFolderMenu_MergeMenu(HINST_THISDLL, POPUP_BITBUCKET_ITEM, 0, (QCMINFO *)lParam);
|
|
hr = S_OK;
|
|
break;
|
|
|
|
case DFM_GETDEFSTATICID:
|
|
*(WPARAM *)lParam = DFM_CMD_PROPERTIES;
|
|
hr = S_OK;
|
|
break;
|
|
|
|
case DFM_MAPCOMMANDNAME:
|
|
if (lstrcmpi((LPCTSTR)lParam, TEXT("undelete")) == 0)
|
|
{
|
|
*(UINT_PTR *)wParam = FSIDM_RESTORE;
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL; // command not found
|
|
}
|
|
break;
|
|
|
|
case DFM_INVOKECOMMAND:
|
|
switch (wParam)
|
|
{
|
|
case FSIDM_RESTORE:
|
|
case DFM_CMD_DELETE:
|
|
case DFM_CMD_PROPERTIES:
|
|
hr = pbbf->_LaunchThread(hwnd, pdtobj, wParam);
|
|
break;
|
|
|
|
default:
|
|
hr = S_FALSE;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case DFM_GETHELPTEXT:
|
|
LoadStringA(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPSTR)lParam, HIWORD(wParam));;
|
|
break;
|
|
|
|
case DFM_GETHELPTEXTW:
|
|
LoadStringW(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPWSTR)lParam, HIWORD(wParam));;
|
|
break;
|
|
|
|
case DFM_GETVERBA:
|
|
case DFM_GETVERBW:
|
|
hr = GetVerb((UINT_PTR)(LOWORD(wParam)), (LPSTR)lParam, (UINT)(HIWORD(wParam)), uMsg == DFM_GETVERBW);
|
|
break;
|
|
|
|
default:
|
|
hr = E_NOTIMPL;
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
class CBitBucketDropTarget : public CIDLDropTarget
|
|
{
|
|
public:
|
|
CBitBucketDropTarget(HWND hwnd, CBitBucket *pbbf) : CIDLDropTarget(hwnd), _pbbf(pbbf)
|
|
{
|
|
_pbbf->AddRef();
|
|
}
|
|
|
|
// IDropTarget (override base class)
|
|
STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
|
|
STDMETHODIMP Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
|
|
|
|
private:
|
|
~CBitBucketDropTarget()
|
|
{
|
|
_pbbf->Release();
|
|
}
|
|
|
|
CBitBucket *_pbbf;
|
|
};
|
|
|
|
//
|
|
// This function puts DROPEFFECT_LINK in *pdwEffect, only if the data object
|
|
// contains one or more net resource.
|
|
//
|
|
STDMETHODIMP CBitBucketDropTarget::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
TraceMsg(TF_BITBUCKET, "Bitbucket: CBitBucketDropTarget::DragEnter");
|
|
|
|
// Call the base class first
|
|
CIDLDropTarget::DragEnter(pDataObj, grfKeyState, pt, pdwEffect);
|
|
|
|
// we don't really care what is in the data object, as long as move
|
|
// is supported by the source we say you can move it to the wastbasket
|
|
// in the case of files we will do the regular recycle bin stuff, if
|
|
// it is not files we will just say it is moved and let the source delete it
|
|
*pdwEffect &= DROPEFFECT_MOVE;
|
|
|
|
m_dwEffectLastReturned = *pdwEffect;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// This function creates a connection to a dropped net resource object.
|
|
STDMETHODIMP CBitBucketDropTarget::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
|
|
{
|
|
BOOL fWebFoldersHack = FALSE;
|
|
HRESULT hr;
|
|
|
|
// only move operation is allowed
|
|
*pdwEffect &= DROPEFFECT_MOVE;
|
|
|
|
if (*pdwEffect)
|
|
{
|
|
hr = CIDLDropTarget::DragDropMenu(DROPEFFECT_MOVE, pDataObj,
|
|
pt, pdwEffect, NULL, NULL, POPUP_NONDEFAULTDD, grfKeyState);
|
|
|
|
if (hr == S_FALSE)
|
|
{
|
|
// let callers know where this is about to go
|
|
// Defview cares where it went so it can handle non-filesys items
|
|
// SHScrap cares because it needs to close the file so we can delete it
|
|
DataObj_SetDropTarget(pDataObj, &CLSID_RecycleBin);
|
|
|
|
if (DataObj_GetDWORD(pDataObj, g_cfNotRecyclable, 0))
|
|
{
|
|
if (ShellMessageBox(HINST_THISDLL, NULL,
|
|
MAKEINTRESOURCE(IDS_CONFIRMNOTRECYCLABLE),
|
|
MAKEINTRESOURCE(IDS_RECCLEAN_NAMETEXT),
|
|
MB_SETFOREGROUND | MB_ICONQUESTION | MB_YESNO) == IDNO)
|
|
{
|
|
*pdwEffect = DROPEFFECT_NONE;
|
|
goto lCancel;
|
|
}
|
|
}
|
|
|
|
if (m_dwData & DTID_HDROP) // CF_HDROP
|
|
{
|
|
_pbbf->_LaunchThread(_GetWindow(), pDataObj, DFM_CMD_MOVE);
|
|
|
|
// since we will move the file ourself, known as an optimised move,
|
|
// we return zero here. this is per the OLE spec
|
|
|
|
*pdwEffect = DROPEFFECT_NONE;
|
|
}
|
|
else
|
|
{
|
|
// if it was not files, we just say we moved the data, letting the
|
|
// source deleted it. lets hope they support undo...
|
|
|
|
*pdwEffect = DROPEFFECT_MOVE;
|
|
|
|
// HACK: Put up a "you can't undo this" warning for web folders.
|
|
{
|
|
STGMEDIUM stgmed;
|
|
LPIDA pida = DataObj_GetHIDA(pDataObj, &stgmed);
|
|
if (pida)
|
|
{
|
|
LPCITEMIDLIST pidl = IDA_GetIDListPtr(pida, -1);
|
|
if (pidl)
|
|
{
|
|
IPersist *pPers;
|
|
hr = SHBindToIDListParent(pidl, IID_PPV_ARG(IPersist, &pPers), NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
CLSID clsidSource;
|
|
hr = pPers->GetClassID(&clsidSource);
|
|
if (SUCCEEDED(hr) &&
|
|
IsEqualGUID(clsidSource, CLSID_WebFolders))
|
|
{
|
|
if (ShellMessageBox(HINST_THISDLL, NULL,
|
|
MAKEINTRESOURCE(IDS_CONFIRMNOTRECYCLABLE),
|
|
MAKEINTRESOURCE(IDS_RECCLEAN_NAMETEXT),
|
|
MB_SETFOREGROUND | MB_ICONQUESTION | MB_YESNO) == IDNO)
|
|
{
|
|
*pdwEffect = DROPEFFECT_NONE;
|
|
pPers->Release();
|
|
HIDA_ReleaseStgMedium (pida, &stgmed);
|
|
goto lCancel;
|
|
}
|
|
else
|
|
{
|
|
fWebFoldersHack = TRUE;
|
|
}
|
|
}
|
|
pPers->Release();
|
|
}
|
|
}
|
|
HIDA_ReleaseStgMedium(pida, &stgmed);
|
|
}
|
|
}
|
|
}
|
|
lCancel:
|
|
if (!fWebFoldersHack)
|
|
{
|
|
DataObj_SetDWORD(pDataObj, g_cfPerformedDropEffect, *pdwEffect);
|
|
DataObj_SetDWORD(pDataObj, g_cfLogicalPerformedDropEffect, DROPEFFECT_MOVE);
|
|
}
|
|
else
|
|
{
|
|
// Make web folders really delete its source file.
|
|
DataObj_SetDWORD (pDataObj, g_cfPerformedDropEffect, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
CIDLDropTarget::DragLeave();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CALLBACK CBitBucket::_BackgroundMenuCallBack(IShellFolder *psf, HWND hwnd,
|
|
IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CBitBucket *pbbf = _FromFolder(psf);
|
|
HRESULT hr = S_OK;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case DFM_MERGECONTEXTMENU_BOTTOM:
|
|
if (!(wParam & (CMF_VERBSONLY | CMF_DVFILE)))
|
|
{
|
|
QCMINFO *pqcm = (QCMINFO*)lParam;
|
|
UINT idFirst = pqcm->idCmdFirst;
|
|
|
|
CDefFolderMenu_MergeMenu(HINST_THISDLL, POPUP_PROPERTIES_BG, 0, (QCMINFO *)lParam);
|
|
|
|
if (SHRestricted(REST_BITBUCKNOPROP))
|
|
{
|
|
// Disable the Properties menu item
|
|
EnableMenuItem(pqcm->hmenu, idFirst + FSIDM_PROPERTIESBG, MF_GRAYED | MF_BYCOMMAND);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DFM_GETHELPTEXT:
|
|
LoadStringA(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPSTR)lParam, HIWORD(wParam));
|
|
break;
|
|
|
|
case DFM_GETHELPTEXTW:
|
|
LoadStringW(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPWSTR)lParam, HIWORD(wParam));
|
|
break;
|
|
|
|
case DFM_INVOKECOMMAND:
|
|
switch (wParam)
|
|
{
|
|
case FSIDM_PROPERTIESBG:
|
|
hr = pbbf->_LaunchThread(hwnd, NULL, FSIDM_PROPERTIESBG);
|
|
break;
|
|
|
|
case DFM_CMD_PASTE:
|
|
case DFM_CMD_PROPERTIES:
|
|
hr = S_FALSE; // do this for me
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
hr = E_NOTIMPL;
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::CreateViewObject(HWND hwnd, REFIID riid, void **ppv)
|
|
{
|
|
HRESULT hr;
|
|
|
|
*ppv = NULL;
|
|
|
|
if (IsEqualIID(riid, IID_IShellView))
|
|
{
|
|
IShellFolderViewCB* psfvcb;
|
|
hr = Create_CBitBucketViewCB(this, &psfvcb);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
SFV_CREATE sSFV = {0};
|
|
sSFV.cbSize = sizeof(sSFV);
|
|
sSFV.pshf = SAFECAST(this, IShellFolder *);
|
|
sSFV.psfvcb = psfvcb;
|
|
|
|
hr = SHCreateShellFolderView(&sSFV, (IShellView**)ppv);
|
|
|
|
psfvcb->Release();
|
|
}
|
|
}
|
|
else if (IsEqualIID(riid, IID_IDropTarget))
|
|
{
|
|
CBitBucketDropTarget *pbbdt = new CBitBucketDropTarget(hwnd, this);
|
|
if (pbbdt)
|
|
{
|
|
hr = pbbdt->_Init(_pidl);
|
|
if (SUCCEEDED(hr))
|
|
hr = pbbdt->QueryInterface(riid, ppv);
|
|
pbbdt->Release();
|
|
}
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
}
|
|
else if (IsEqualIID(riid, IID_IContextMenu))
|
|
{
|
|
IContextMenu* pcmBase;
|
|
hr = CDefFolderMenu_Create(NULL, hwnd, 0, NULL, SAFECAST(this, IShellFolder *), _BackgroundMenuCallBack,
|
|
NULL, NULL, &pcmBase);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IContextMenu* pcmFolder = SAFECAST(this, IContextMenu*);
|
|
IContextMenu* rgpcm[] = { pcmFolder, pcmBase };
|
|
|
|
hr = Create_ContextMenuOnContextMenuArray(rgpcm, ARRAYSIZE(rgpcm), riid, ppv);
|
|
|
|
pcmBase->Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
hr = E_NOINTERFACE;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// search the database on idDrive for file with index iIndex
|
|
|
|
LPITEMIDLIST CBitBucket::_DriveInfoToIDList(int idDrive, int iIndex)
|
|
{
|
|
LPITEMIDLIST pidl = NULL;
|
|
HANDLE hFile = OpenBBInfoFile(idDrive, OPENBBINFO_WRITE, 0);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
// read records until we find an index match
|
|
BBDATAENTRYW bbdew;
|
|
|
|
while (ReadNextDataEntry(hFile, &bbdew, TRUE, idDrive))
|
|
{
|
|
if (bbdew.iIndex == iIndex)
|
|
{
|
|
ASSERT(idDrive == bbdew.idDrive);
|
|
pidl = DataEntryToIDList(&bbdew);
|
|
break;
|
|
}
|
|
}
|
|
CloseBBInfoFile(hFile, idDrive);
|
|
}
|
|
return pidl;
|
|
}
|
|
|
|
// we implement this supporting D<drive_id><index>.ext
|
|
|
|
STDMETHODIMP CBitBucket::ParseDisplayName(HWND hwnd, IBindCtx *pbc, LPOLESTR pwszDisplayName,
|
|
ULONG *pchEaten, LPITEMIDLIST *ppidl, ULONG *pdwAttributes)
|
|
{
|
|
if (!ppidl)
|
|
return E_INVALIDARG;
|
|
|
|
*ppidl = NULL;
|
|
|
|
if (!pwszDisplayName)
|
|
return E_INVALIDARG;
|
|
|
|
int idDrive, iIndex;
|
|
HRESULT hr = BBFileNameToInfo(pwszDisplayName, &idDrive, &iIndex);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// since anyone can call us with a path that is under the recycled directory,
|
|
// we need to check to make sure that we have inited this drive:
|
|
if (MakeBitBucket(idDrive))
|
|
{
|
|
*ppidl = _DriveInfoToIDList(idDrive, iIndex);
|
|
hr = *ppidl ? S_OK : E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
// takes a full path to a file in a recycle bin storage folder and creates a
|
|
// single level bitbucket pidl
|
|
|
|
LPITEMIDLIST CBitBucket::PathToIDList(LPCTSTR pszPath)
|
|
{
|
|
LPITEMIDLIST pidl = NULL;
|
|
int idDrive = DriveIDFromBBPath(pszPath);
|
|
|
|
ASSERT(idDrive >= 0); // general UNC case will generate -1
|
|
|
|
int iIndex = BBPathToIndex(pszPath);
|
|
if (iIndex != -1)
|
|
{
|
|
pidl = _DriveInfoToIDList(idDrive, iIndex);
|
|
}
|
|
return pidl;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetUIObjectOf(HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl,
|
|
REFIID riid, UINT *prgfInOut, void **ppv)
|
|
{
|
|
HRESULT hr;
|
|
|
|
*ppv = NULL;
|
|
|
|
if (cidl && IsEqualIID(riid, IID_IDataObject))
|
|
{
|
|
CBitBucketData *pbbd = new CBitBucketData(this, cidl, apidl);
|
|
if (pbbd)
|
|
{
|
|
hr = pbbd->QueryInterface(riid, ppv);
|
|
pbbd->Release();
|
|
}
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else if (IsEqualIID(riid, IID_IContextMenu))
|
|
{
|
|
hr = CDefFolderMenu_Create(_pidl, hwnd, cidl, apidl,
|
|
SAFECAST(this, IShellFolder *), _ItemMenuCallBack, NULL, NULL, (IContextMenu**)ppv);
|
|
}
|
|
else if (IsEqualIID(riid, IID_IDropTarget))
|
|
{
|
|
hr = E_FAIL; // You can't drop on internal items of the bitbucket!
|
|
}
|
|
else if (cidl == 1)
|
|
{
|
|
// blindly delegate unknown riid's to folder!
|
|
IShellFolder *psf;
|
|
hr = _FolderFromIDList(apidl[0], IID_PPV_ARG(IShellFolder, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->GetUIObjectOf(hwnd, cidl, apidl, riid, prgfInOut, ppv);
|
|
if (SUCCEEDED(hr) && IsEqualIID(riid, IID_IQueryInfo))
|
|
{
|
|
WrapInfotip(SAFECAST(this, IShellFolder2 *), apidl[0], &SCID_DELETEDFROM, (IUnknown *)*ppv);
|
|
}
|
|
psf->Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_NOTIMPL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CBitBucket::_FolderFromDrive(int idDrive, REFIID riid, void **ppv)
|
|
{
|
|
*ppv = NULL;
|
|
|
|
ASSERT(idDrive < ARRAYSIZE(_rgFolders));
|
|
|
|
if (NULL == _rgFolders[idDrive])
|
|
{
|
|
PERSIST_FOLDER_TARGET_INFO pfti = {0};
|
|
|
|
DriveIDToBBPath(idDrive, pfti.szTargetParsingName);
|
|
pfti.dwAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
pfti.csidl = -1;
|
|
|
|
CFSFolder_CreateFolder(NULL, NULL, _pidl, &pfti, IID_PPV_ARG(IUnknown, &_rgFolders[idDrive]));
|
|
}
|
|
|
|
return _rgFolders[idDrive] ? _rgFolders[idDrive]->QueryInterface(riid, ppv) : E_FAIL;
|
|
}
|
|
|
|
// accepts NULL, or undecorated recycle bin pidl (raw file system pidl).
|
|
// in these cases computes the default recycle bin file system folder
|
|
// index so we will defer to that.
|
|
|
|
int CBitBucket::_DriveIDFromIDList(LPCITEMIDLIST pidl)
|
|
{
|
|
int iDrive = 0;
|
|
|
|
PUBBDATAENTRYA pbbde = _IsValid(pidl);
|
|
if (pbbde)
|
|
{
|
|
iDrive = pbbde->idDrive;
|
|
}
|
|
else
|
|
{
|
|
// unknown, compute the default recycle bin folder index
|
|
TCHAR szPath[MAX_PATH];
|
|
if (GetWindowsDirectory(szPath, ARRAYSIZE(szPath)))
|
|
{
|
|
iDrive = PathGetDriveNumber(szPath);
|
|
if (iDrive < 0)
|
|
iDrive = 0;
|
|
}
|
|
}
|
|
ASSERT(iDrive >= 0 && iDrive < ARRAYSIZE(_rgFolders));
|
|
return iDrive;
|
|
}
|
|
|
|
// in:
|
|
// pidl of item, or NULL for default folder (base recycle bin)
|
|
|
|
HRESULT CBitBucket::_FolderFromIDList(LPCITEMIDLIST pidl, REFIID riid, void **ppv)
|
|
{
|
|
return _FolderFromDrive(_DriveIDFromIDList(pidl), riid, ppv);
|
|
}
|
|
|
|
// create a bitbucket pidl, start with the file system pidl, then add the extra data sections as needed
|
|
|
|
LPITEMIDLIST CBitBucket::DataEntryToIDList(BBDATAENTRYW *pbbde)
|
|
{
|
|
LPITEMIDLIST pidl = NULL;
|
|
|
|
WCHAR szFile[MAX_PATH];
|
|
GetDeletedFileName(szFile, pbbde);
|
|
|
|
IShellFolder *psf;
|
|
if (SUCCEEDED(_FolderFromDrive(pbbde->idDrive, IID_PPV_ARG(IShellFolder, &psf))))
|
|
{
|
|
if (SUCCEEDED(psf->ParseDisplayName(NULL, NULL, szFile, NULL, &pidl, NULL)))
|
|
{
|
|
HIDDENRECYCLEBINDATA hrbd = { {sizeof(hrbd), HRBD_CURRENTVERSION, IDLHID_RECYCLEBINDATA}};
|
|
hrbd.bbde = *((LPBBDATAENTRYA)pbbde);
|
|
|
|
pidl = ILAppendHiddenID(pidl, &hrbd.hid);
|
|
if (pidl)
|
|
{
|
|
if (g_pBitBucket[pbbde->idDrive]->fIsUnicode &&
|
|
!DoesStringRoundTrip(pbbde->szOriginal, NULL, 0))
|
|
{
|
|
pidl = ILAppendHiddenStringW(pidl, IDLHID_RECYCLEBINORIGINAL, pbbde->szOriginal);
|
|
}
|
|
}
|
|
}
|
|
psf->Release();
|
|
}
|
|
return pidl;
|
|
}
|
|
|
|
class CBitBucketEnum : public IEnumIDList
|
|
{
|
|
public:
|
|
// IUnknown
|
|
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
|
|
STDMETHOD_(ULONG,AddRef)();
|
|
STDMETHOD_(ULONG,Release)();
|
|
|
|
// IEnumIDList
|
|
STDMETHOD(Next)(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched);
|
|
STDMETHOD(Skip)(ULONG celt);
|
|
STDMETHOD(Reset)();
|
|
STDMETHOD(Clone)(IEnumIDList **ppenum);
|
|
|
|
CBitBucketEnum(CBitBucket *pbbf, DWORD grfFlags);
|
|
|
|
private:
|
|
HRESULT _BuildEnumDPA();
|
|
~CBitBucketEnum();
|
|
|
|
LONG _cRef;
|
|
CBitBucket *_pbbf;
|
|
HDPA _hdpa;
|
|
int _nItem;
|
|
DWORD _grfFlags;
|
|
};
|
|
|
|
CBitBucketEnum::CBitBucketEnum(CBitBucket *pbbf, DWORD grfFlags) :
|
|
_cRef(1), _pbbf(pbbf), _grfFlags(grfFlags)
|
|
{
|
|
_pbbf->AddRef();
|
|
}
|
|
|
|
CBitBucketEnum::~CBitBucketEnum()
|
|
{
|
|
Reset();
|
|
_pbbf->Release();
|
|
}
|
|
|
|
STDMETHODIMP CBitBucketEnum::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CBitBucketEnum, IEnumIDList),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CBitBucketEnum::AddRef()
|
|
{
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CBitBucketEnum::Release()
|
|
{
|
|
if (InterlockedDecrement(&_cRef))
|
|
return _cRef;
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
#define BB_DELETED_ENTRY_MAX 10 // smaller to force compaction more often
|
|
#else
|
|
#define BB_DELETED_ENTRY_MAX 100
|
|
#endif
|
|
|
|
// on the first ::Next() call snapshot the data needed to do the enum
|
|
|
|
HRESULT CBitBucketEnum::_BuildEnumDPA()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (NULL == _hdpa)
|
|
{
|
|
_hdpa = DPA_CreateEx(0, NULL);
|
|
if (_hdpa)
|
|
{
|
|
if (_grfFlags & SHCONTF_NONFOLDERS) //if they asked for folders we have none so leave DPA empty
|
|
{
|
|
// loop through the bitbucket drives to find an info file
|
|
for (int iBitBucket = 0; iBitBucket < MAX_BITBUCKETS; iBitBucket++)
|
|
{
|
|
if (MakeBitBucket(iBitBucket))
|
|
{
|
|
int cDeleted = 0;
|
|
HANDLE hFile = OpenBBInfoFile(iBitBucket, OPENBBINFO_WRITE, 0);
|
|
if (INVALID_HANDLE_VALUE != hFile)
|
|
{
|
|
BBDATAENTRYW bbdew;
|
|
|
|
while (ReadNextDataEntry(hFile, &bbdew, FALSE, iBitBucket))
|
|
{
|
|
if (IsDeletedEntry(&bbdew))
|
|
cDeleted++;
|
|
else
|
|
{
|
|
ASSERT(iBitBucket == bbdew.idDrive);
|
|
|
|
LPITEMIDLIST pidl = _pbbf->DataEntryToIDList(&bbdew);
|
|
if (pidl)
|
|
{
|
|
if (-1 == DPA_AppendPtr(_hdpa, pidl))
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cDeleted > BB_DELETED_ENTRY_MAX)
|
|
{
|
|
BOOL bTrue = TRUE;
|
|
|
|
// set the registry key so that we will compact the info file after the next delete operation
|
|
RegSetValueEx(g_pBitBucket[iBitBucket]->hkeyPerUser, TEXT("NeedToCompact"), 0, REG_DWORD, (LPBYTE)&bTrue, sizeof(bTrue));
|
|
}
|
|
CloseBBInfoFile(hFile, iBitBucket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucketEnum::Next(ULONG celt, LPITEMIDLIST *ppidl, ULONG *pceltFetched)
|
|
{
|
|
*ppidl = NULL;
|
|
|
|
HRESULT hr = _BuildEnumDPA();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPITEMIDLIST pidl = (LPITEMIDLIST)DPA_GetPtr(_hdpa, _nItem);
|
|
if (pidl)
|
|
{
|
|
hr = SHILClone(pidl, ppidl);
|
|
_nItem++;
|
|
}
|
|
else
|
|
{
|
|
hr = S_FALSE; // no more items
|
|
}
|
|
}
|
|
|
|
if (pceltFetched)
|
|
*pceltFetched = (hr == S_OK) ? 1 : 0;
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucketEnum::Skip(ULONG celt)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
if (_hdpa)
|
|
{
|
|
_nItem += celt;
|
|
if (_nItem >= DPA_GetPtrCount(_hdpa))
|
|
{
|
|
_nItem = DPA_GetPtrCount(_hdpa);
|
|
hr = S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucketEnum::Reset()
|
|
{
|
|
DPA_FreeIDArray(_hdpa);
|
|
_hdpa = NULL;
|
|
_nItem = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucketEnum::Clone(IEnumIDList **ppenum)
|
|
{
|
|
*ppenum = NULL;
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenum)
|
|
{
|
|
*ppenum = NULL;
|
|
|
|
HRESULT hr;
|
|
CBitBucketEnum *penum = new CBitBucketEnum(this, grfFlags);
|
|
if (penum)
|
|
{
|
|
hr = penum->QueryInterface(IID_PPV_ARG(IEnumIDList, ppenum));
|
|
penum->Release();
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::BindToObject(LPCITEMIDLIST pidl, IBindCtx *pbc, REFIID riid, void **ppv)
|
|
{
|
|
*ppv = NULL;
|
|
HRESULT hr = E_NOTIMPL;
|
|
if (riid != IID_IShellFolder && riid != IID_IShellFolder2)
|
|
{
|
|
// let IPropertySetStorage/IStream/etc binds go through
|
|
IShellFolder *psf;
|
|
hr = _FolderFromIDList(pidl, IID_PPV_ARG(IShellFolder, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->BindToObject(pidl, pbc, riid, ppv);
|
|
psf->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::BindToStorage(LPCITEMIDLIST pidl, IBindCtx *pbc, REFIID riid, void **ppv)
|
|
{
|
|
return BindToObject(pidl, pbc, riid, ppv);
|
|
}
|
|
|
|
DWORD CBitBucket::_IsFolder(LPCITEMIDLIST pidl)
|
|
{
|
|
DWORD dwAttributes = SFGAO_FOLDER;
|
|
HRESULT hr = GetAttributesOf(1, &pidl, &dwAttributes);
|
|
return (SUCCEEDED(hr) && (SFGAO_FOLDER & dwAttributes)) ? FILE_ATTRIBUTE_DIRECTORY : 0;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD dwFlags, STRRET *psr)
|
|
{
|
|
HRESULT hr;
|
|
// on change notifications we can get file system pidls that don't have our
|
|
// extra data in them. in this case we need to delegate to the file system
|
|
// folder
|
|
if ((0 == (dwFlags & SHGDN_FORPARSING)) && _IsValid(pidl))
|
|
{
|
|
TCHAR szTemp[MAX_PATH];
|
|
hr = _OriginalPath(pidl, szTemp, ARRAYSIZE(szTemp));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (dwFlags & SHGDN_INFOLDER)
|
|
{
|
|
SHFILEINFO sfi;
|
|
if (SHGetFileInfo(szTemp, _IsFolder(pidl), &sfi, sizeof(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_DISPLAYNAME))
|
|
{
|
|
hr = StringToStrRet(sfi.szDisplayName, psr);
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = StringToStrRet(szTemp, psr);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IShellFolder *psf;
|
|
hr = _FolderFromIDList(pidl, IID_PPV_ARG(IShellFolder, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->GetDisplayNameOf(pidl, dwFlags, psr);
|
|
psf->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName, DWORD dwRes, LPITEMIDLIST *ppidlOut)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetDefaultSearchGUID(GUID *pguid)
|
|
{
|
|
return DefaultSearchGUID(pguid);
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::EnumSearches(IEnumExtraSearch **ppenum)
|
|
{
|
|
*ppenum = NULL;
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetDefaultColumnState(UINT iColumn, DWORD *pdwState)
|
|
{
|
|
HRESULT hr;
|
|
if (_MapColIndex(&iColumn))
|
|
{
|
|
*pdwState = c_bb_cols[iColumn].csFlags | SHCOLSTATE_PREFER_VARCMP;
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
IShellFolder2 *psf;
|
|
hr = _FolderFromIDList(NULL, IID_PPV_ARG(IShellFolder2, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->GetDefaultColumnState(iColumn, pdwState);
|
|
psf->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv)
|
|
{
|
|
HRESULT hr;
|
|
if (IsEqualSCID(*pscid, SCID_DELETEDFROM))
|
|
{
|
|
TCHAR szTemp[MAX_PATH];
|
|
hr = _OriginalDirectory(pidl, szTemp, ARRAYSIZE(szTemp));
|
|
if (SUCCEEDED(hr))
|
|
hr = InitVariantFromStr(pv, szTemp);
|
|
}
|
|
else if (IsEqualSCID(*pscid, SCID_DATEDELETED))
|
|
{
|
|
FILETIME ft;
|
|
_GetDeletedFileTime(pidl, &ft);
|
|
hr = InitVariantFromFileTime(&ft, pv);
|
|
}
|
|
else if (IsEqualSCID(*pscid, SCID_DIRECTORY))
|
|
{
|
|
// don't let this get through to file folder as we want to hide
|
|
// the real file system folder from callers
|
|
VariantInit(pv);
|
|
hr = E_FAIL;
|
|
}
|
|
else if (IsEqualSCID(*pscid, SCID_SIZE))
|
|
{
|
|
pv->ullVal = _GetDeletedSize(pidl);
|
|
pv->vt = VT_UI8;
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
IShellFolder2 *psf;
|
|
hr = _FolderFromIDList(pidl, IID_PPV_ARG(IShellFolder2, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->GetDetailsEx(pidl, pscid, pv);
|
|
psf->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
BOOL CBitBucket::_MapColIndex(UINT *piColumn)
|
|
{
|
|
switch (*piColumn)
|
|
{
|
|
case ICOL_NAME: // 0
|
|
case ICOL_ORIGINAL: // 1
|
|
case ICOL_DATEDELETED: // 2
|
|
return TRUE;
|
|
|
|
default: // >= 3
|
|
*piColumn -= ICOL_DATEDELETED;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *pdi)
|
|
{
|
|
HRESULT hr;
|
|
if (_MapColIndex(&iColumn))
|
|
{
|
|
if (pidl)
|
|
{
|
|
TCHAR szTemp[MAX_PATH];
|
|
szTemp[0] = 0;
|
|
|
|
switch (iColumn)
|
|
{
|
|
case ICOL_NAME:
|
|
DisplayNameOf(this, pidl, SHGDN_INFOLDER, szTemp, ARRAYSIZE(szTemp));
|
|
break;
|
|
|
|
case ICOL_ORIGINAL:
|
|
_OriginalDirectory(pidl, szTemp, ARRAYSIZE(szTemp));
|
|
break;
|
|
|
|
case ICOL_DATEDELETED:
|
|
{
|
|
FILETIME ft;
|
|
_GetDeletedFileTime(pidl, &ft);
|
|
DWORD dwFlags = FDTF_DEFAULT;
|
|
|
|
switch (pdi->fmt)
|
|
{
|
|
case LVCFMT_LEFT_TO_RIGHT:
|
|
dwFlags |= FDTF_LTRDATE;
|
|
break;
|
|
|
|
case LVCFMT_RIGHT_TO_LEFT:
|
|
dwFlags |= FDTF_RTLDATE;
|
|
break;
|
|
}
|
|
|
|
SHFormatDateTime(&ft, &dwFlags, szTemp, ARRAYSIZE(szTemp));
|
|
}
|
|
break;
|
|
}
|
|
hr = StringToStrRet(szTemp, &pdi->str);
|
|
}
|
|
else
|
|
{
|
|
hr = GetDetailsOfInfo(c_bb_cols, ARRAYSIZE(c_bb_cols), iColumn, pdi);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pidl && (_SizeColumn() == iColumn))
|
|
{
|
|
TCHAR szTemp[64];
|
|
StrFormatKBSize(_GetDeletedSize(pidl), szTemp, ARRAYSIZE(szTemp));
|
|
hr = StringToStrRet(szTemp, &pdi->str);
|
|
}
|
|
else
|
|
{
|
|
IShellFolder2 *psf;
|
|
hr = _FolderFromIDList(pidl, IID_PPV_ARG(IShellFolder2, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->GetDetailsOf(pidl, iColumn, pdi);
|
|
psf->Release();
|
|
}
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
UINT CBitBucket::_SizeColumn()
|
|
{
|
|
if (-1 == _uiColumnSize)
|
|
{
|
|
_uiColumnSize = MapSCIDToColumn(SAFECAST(this, IShellFolder2 *), &SCID_SIZE);
|
|
_MapColIndex(&_uiColumnSize); // map to other folder index space
|
|
}
|
|
return _uiColumnSize;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
|
|
{
|
|
HRESULT hr;
|
|
if (_MapColIndex(&iColumn))
|
|
{
|
|
hr = MapColumnToSCIDImpl(c_bb_cols, ARRAYSIZE(c_bb_cols), iColumn, pscid);
|
|
}
|
|
else
|
|
{
|
|
IShellFolder2 *psf;
|
|
hr = _FolderFromIDList(NULL, IID_PPV_ARG(IShellFolder2, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psf->MapColumnToSCID(iColumn, pscid);
|
|
psf->Release();
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
// IPersist
|
|
STDMETHODIMP CBitBucket::GetClassID(CLSID *pclsid)
|
|
{
|
|
*pclsid = CLSID_RecycleBin;
|
|
return S_OK;
|
|
}
|
|
|
|
// IPersistFolder
|
|
STDMETHODIMP CBitBucket::Initialize(LPCITEMIDLIST pidl)
|
|
{
|
|
return Pidl_Set(&_pidl, pidl) ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
// IPersistFolder2
|
|
STDMETHODIMP CBitBucket::GetCurFolder(LPITEMIDLIST *ppidl)
|
|
{
|
|
return GetCurFolderImpl(_pidl, ppidl);
|
|
}
|
|
|
|
// IShellExtInit
|
|
STDMETHODIMP CBitBucket::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
// IContextMenu
|
|
STDMETHODIMP CBitBucket::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
|
|
{
|
|
int idMax = idCmdFirst;
|
|
HMENU hmMerge = SHLoadPopupMenu(HINST_THISDLL, POPUP_BITBUCKET_POPUPMERGE);
|
|
if (hmMerge)
|
|
{
|
|
if (IsRecycleBinEmpty())
|
|
{
|
|
EnableMenuItem(hmMerge, FSIDM_PURGEALL, MF_GRAYED | MF_BYCOMMAND);
|
|
}
|
|
|
|
idMax = Shell_MergeMenus(hmenu, hmMerge, indexMenu, idCmdFirst, idCmdLast, 0);
|
|
|
|
DestroyMenu(hmMerge);
|
|
}
|
|
|
|
return ResultFromShort(idMax - idCmdFirst);
|
|
}
|
|
|
|
const ICIVERBTOIDMAP c_sBBCmdInfo[] = {
|
|
{ L"empty", "empty", FSIDM_PURGEALL, FSIDM_PURGEALL, },
|
|
};
|
|
|
|
STDMETHODIMP CBitBucket::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
|
|
{
|
|
UINT idCmd;
|
|
HRESULT hr = SHMapICIVerbToCmdID(pici, c_sBBCmdInfo, ARRAYSIZE(c_sBBCmdInfo), &idCmd);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
switch (idCmd)
|
|
{
|
|
case FSIDM_PURGEALL:
|
|
hr = BBPurgeAll(pici->hwnd, 0);
|
|
|
|
// command is ours, let caller know we processed it (but we want the right success code)
|
|
if (FAILED(hr))
|
|
hr = S_FALSE;
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::GetCommandString(UINT_PTR idCmd, UINT wFlags, UINT * pwReserved, LPSTR pszName, UINT cchMax)
|
|
{
|
|
switch (wFlags)
|
|
{
|
|
case GCS_VERBA:
|
|
case GCS_VERBW:
|
|
return SHMapCmdIDToVerb(idCmd, c_sBBCmdInfo, ARRAYSIZE(c_sBBCmdInfo), pszName, cchMax, wFlags == GCS_VERBW);
|
|
|
|
case GCS_HELPTEXTA:
|
|
return LoadStringA(HINST_THISDLL,
|
|
(UINT)(idCmd + IDS_MH_FSIDM_FIRST),
|
|
pszName, cchMax) ? S_OK : E_OUTOFMEMORY;
|
|
case GCS_HELPTEXTW:
|
|
return LoadStringW(HINST_THISDLL,
|
|
(UINT)(idCmd + IDS_MH_FSIDM_FIRST),
|
|
(LPWSTR)pszName, cchMax) ? S_OK : E_OUTOFMEMORY;
|
|
default:
|
|
return E_NOTIMPL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Callback function that saves the location of the HPROPSHEETPAGE's
|
|
// LPPROPSHEETPAGE so we can pass it to other propsheet pages.
|
|
//
|
|
UINT CALLBACK CBitBucket::_GlobalSettingsCalback(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case PSPCB_ADDREF:
|
|
{
|
|
// we save off the address of the "real" ppsi in the pGlobal param of the
|
|
// the template, so that the other drives can get to the global page information
|
|
BBPROPSHEETINFO *ppsiGlobal = (BBPROPSHEETINFO *)ppsp;
|
|
BBPROPSHEETINFO *ppsiTemplate = (BBPROPSHEETINFO *)ppsp->lParam;
|
|
ppsiTemplate->pGlobal = ppsiGlobal;
|
|
ppsiGlobal->pGlobal = ppsiGlobal;
|
|
}
|
|
break;
|
|
|
|
case PSPCB_CREATE:
|
|
return TRUE; // Yes, please create me
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
|
|
{
|
|
BBPROPSHEETINFO bbpsp;
|
|
TCHAR szTitle[MAX_PATH];
|
|
|
|
if (SHRestricted(REST_BITBUCKNOPROP))
|
|
{
|
|
return E_ACCESSDENIED;
|
|
}
|
|
|
|
// read in the global settings
|
|
DWORD dwSize1 = sizeof(bbpsp.fUseGlobalSettings);
|
|
DWORD dwSize2 = sizeof(bbpsp.iOriginalPercent);
|
|
DWORD dwSize3 = sizeof(bbpsp.fOriginalNukeOnDelete);
|
|
if (RegQueryValueEx(g_hkBitBucket, TEXT("UseGlobalSettings"), NULL, NULL, (LPBYTE)&bbpsp.fOriginalUseGlobalSettings, &dwSize1) != ERROR_SUCCESS ||
|
|
RegQueryValueEx(g_hkBitBucket, TEXT("Percent"), NULL, NULL, (LPBYTE)&bbpsp.iOriginalPercent, &dwSize2) != ERROR_SUCCESS ||
|
|
RegQueryValueEx(g_hkBitBucket, TEXT("NukeOnDelete"), NULL, NULL, (LPBYTE)&bbpsp.fOriginalNukeOnDelete, &dwSize3) != ERROR_SUCCESS)
|
|
{
|
|
ASSERTMSG(FALSE, "Bitbucket: could not read global settings from the registry, re-regsvr32 shell32.dll!!");
|
|
bbpsp.fUseGlobalSettings = TRUE;
|
|
bbpsp.iOriginalPercent = 10;
|
|
bbpsp.fOriginalNukeOnDelete = FALSE;
|
|
}
|
|
|
|
// Check policies
|
|
|
|
bbpsp.fPolicyNukeOnDelete = SHRestricted(REST_BITBUCKNUKEONDELETE);
|
|
if (bbpsp.fPolicyNukeOnDelete)
|
|
{
|
|
bbpsp.fOriginalNukeOnDelete = TRUE;
|
|
bbpsp.fOriginalUseGlobalSettings = TRUE;
|
|
}
|
|
|
|
bbpsp.fPolicyPercent = (ReadPolicySetting(NULL,
|
|
L"Explorer",
|
|
L"RecycleBinSize",
|
|
(LPBYTE)&bbpsp.iPercent,
|
|
sizeof(bbpsp.iPercent)) == ERROR_SUCCESS);
|
|
if (bbpsp.fPolicyPercent)
|
|
{
|
|
bbpsp.iOriginalPercent = bbpsp.iPercent;
|
|
}
|
|
else
|
|
{
|
|
bbpsp.iPercent = bbpsp.iOriginalPercent;
|
|
}
|
|
|
|
bbpsp.fUseGlobalSettings = bbpsp.fOriginalUseGlobalSettings;
|
|
bbpsp.fNukeOnDelete = bbpsp.fOriginalNukeOnDelete;
|
|
|
|
bbpsp.psp.dwSize = sizeof(bbpsp);
|
|
bbpsp.psp.dwFlags = PSP_DEFAULT | PSP_USECALLBACK;
|
|
bbpsp.psp.hInstance = HINST_THISDLL;
|
|
bbpsp.psp.pszTemplate = MAKEINTRESOURCE(DLG_BITBUCKET_GENCONFIG);
|
|
bbpsp.psp.pfnDlgProc = _GlobalPropDlgProc;
|
|
bbpsp.psp.lParam = (LPARAM)&bbpsp;
|
|
// the callback will fill the bbpsp.pGlobal with the pointer to the "real" psp after it has been copied
|
|
// so that the other drive pages can get to the global information
|
|
bbpsp.psp.pfnCallback = _GlobalSettingsCalback;
|
|
|
|
// add the "Global" settings page
|
|
HPROPSHEETPAGE hpage = CreatePropertySheetPage(&bbpsp.psp);
|
|
|
|
#ifdef UNICODE
|
|
// If this assertion fires, it means that comctl32 lost
|
|
// backwards-compatibility with Win95 shell, WinNT4 shell,
|
|
// and IE4 shell, all of which relied on this undocumented
|
|
// behavior.
|
|
ASSERT(bbpsp.pGlobal == (BBPROPSHEETINFO *)((LPBYTE)hpage + 2 * sizeof(void *)));
|
|
#else
|
|
ASSERT(bbpsp.pGlobal == (BBPROPSHEETINFO *)hpage);
|
|
#endif
|
|
|
|
pfnAddPage(hpage, lParam);
|
|
|
|
// now create the pages for the individual drives
|
|
bbpsp.psp.dwFlags = PSP_USETITLE;
|
|
bbpsp.psp.pszTemplate = MAKEINTRESOURCE(DLG_BITBUCKET_CONFIG);
|
|
bbpsp.psp.pfnDlgProc = _DriveDlgProc;
|
|
bbpsp.psp.pszTitle = szTitle;
|
|
|
|
int idDrive, iPage;
|
|
for (idDrive = 0, iPage = 1; (idDrive < MAX_BITBUCKETS) && (iPage < MAXPROPPAGES); idDrive++)
|
|
{
|
|
if (MakeBitBucket(idDrive))
|
|
{
|
|
dwSize1 = sizeof(bbpsp.iOriginalPercent);
|
|
dwSize2 = sizeof(bbpsp.fOriginalNukeOnDelete);
|
|
if (RegQueryValueEx(g_pBitBucket[idDrive]->hkey, TEXT("Percent"), NULL, NULL, (LPBYTE)&bbpsp.iOriginalPercent, &dwSize1) != ERROR_SUCCESS ||
|
|
RegQueryValueEx(g_pBitBucket[idDrive]->hkey, TEXT("NukeOnDelete"), NULL, NULL, (LPBYTE)&bbpsp.fOriginalNukeOnDelete, &dwSize2) != ERROR_SUCCESS)
|
|
{
|
|
TraceMsg(TF_BITBUCKET, "Bitbucket: could not read settings from the registry for drive %d, using lame defaults", idDrive);
|
|
bbpsp.iOriginalPercent = 10;
|
|
bbpsp.fOriginalNukeOnDelete = FALSE;
|
|
}
|
|
|
|
if (bbpsp.fPolicyNukeOnDelete)
|
|
{
|
|
bbpsp.fOriginalNukeOnDelete = TRUE;
|
|
}
|
|
|
|
if (bbpsp.fPolicyPercent)
|
|
{
|
|
bbpsp.iOriginalPercent = bbpsp.iPercent;
|
|
}
|
|
|
|
bbpsp.iPercent = bbpsp.iOriginalPercent;
|
|
bbpsp.fNukeOnDelete = bbpsp.fOriginalNukeOnDelete;
|
|
|
|
bbpsp.idDrive = idDrive;
|
|
|
|
_GetDriveDisplayName(idDrive, szTitle, ARRAYSIZE(szTitle));
|
|
hpage = CreatePropertySheetPage(&bbpsp.psp);
|
|
pfnAddPage(hpage, lParam);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CBitBucket::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplaceWith, LPARAM lParam)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDAPI CBitBucket_CreateInstance(IUnknown* punkOuter, REFIID riid, void **ppv)
|
|
{
|
|
*ppv = NULL;
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
CBitBucket *pbb = new CBitBucket();
|
|
if (pbb)
|
|
{
|
|
if (InitBBGlobals())
|
|
hr = pbb->QueryInterface(riid, ppv);
|
|
pbb->Release();
|
|
}
|
|
return hr;
|
|
}
|