WindowsXP-SP1/shell/shell32/bbckfldr.cpp

3143 lines
101 KiB
C++

#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;
}