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

471 lines
16 KiB
C++

#include "shellprv.h"
#include "ids.h"
class CFolderInfoTip : public IQueryInfo, public ICustomizeInfoTip, public IParentAndItem, public IShellTreeWalkerCallBack
{
public:
CFolderInfoTip(IUnknown *punk, LPCTSTR pszFolder);
// IUnknown
STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IQueryInfo methods.
STDMETHODIMP GetInfoTip(DWORD dwFlags, WCHAR** ppwszTip);
STDMETHODIMP GetInfoFlags(DWORD *pdwFlags);
// ICustomizeInfoTip
STDMETHODIMP SetPrefixText(LPCWSTR pszPrefix);
STDMETHODIMP SetExtraProperties(const SHCOLUMNID *pscid, UINT cscid);
// IParentAndItem
STDMETHODIMP SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psf, LPCITEMIDLIST pidlChild);
STDMETHODIMP GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidlChild);
// IShellTreeWalkerCallBack methods
STDMETHODIMP FoundFile(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd);
STDMETHODIMP EnterFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd);
STDMETHODIMP LeaveFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws);
STDMETHODIMP HandleError(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, HRESULT hrError);
private:
~CFolderInfoTip();
HRESULT _GetTreeWalkerData(TREEWALKERSTATS *ptws);
HRESULT _BufferInsert(LPWSTR pszBuffer, int *puBufferUsed, int uBufferMaxSize, LPCWSTR pwszPath, int cBufferItems);
HRESULT _WalkTree(LPWSTR pszTip, DWORD cchSize);
HRESULT _BuildSizeBlurb(HRESULT hr, LPWSTR pszSizeBlurb, DWORD cchSize);
HRESULT _BuildFolderBlurb(HRESULT hr, LPWSTR pszFolderBlurb, DWORD cchSize);
HRESULT _BuildFileBlurb(HRESULT hr, LPWSTR pszSizeBlurb, DWORD cchSize);
LONG _cRef; // Reference Counter
LPWSTR _pszFolderName; // File name of the target folder
IQueryInfo *_pqiOuter; // Outer info tip for folders (say, for comments)
ULONGLONG _ulTotalSize; // Total size of encountered files
UINT _nSubFolders; // Total number of subfolders of target
UINT _nFiles; // Total number of subfiles of target folder
DWORD _dwSearchStartTime; // Time when search started
WCHAR _szFileList[60]; // List of files in target folder
int _nFileListCharsUsed; // Number of characters used in buffer
WCHAR _szFolderList[60]; // List of subfolders of target
int _nFolderListCharsUsed; // Number of chars used in folder buffer
};
// Constructor and Destructor do nothing more than set everything to
// 0 and ping the dll
CFolderInfoTip::CFolderInfoTip(IUnknown *punkOutter, LPCTSTR pszFolder) : _cRef(1)
{
// Init everything to 0
_pszFolderName = StrDup(pszFolder);
_szFileList[0] = 0;
_nFileListCharsUsed = 0;
_szFolderList[0] = 0;
_nFolderListCharsUsed = 0;
_ulTotalSize = 0;
_nSubFolders = 0;
_nFiles = 0;
punkOutter->QueryInterface(IID_PPV_ARG(IQueryInfo, &_pqiOuter));
DllAddRef();
}
CFolderInfoTip::~CFolderInfoTip()
{
LocalFree(_pszFolderName);
if (_pqiOuter)
_pqiOuter->Release();
DllRelease();
}
HRESULT CFolderInfoTip::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENT(CFolderInfoTip, IQueryInfo),
QITABENT(CFolderInfoTip, ICustomizeInfoTip),
QITABENT(CFolderInfoTip, IParentAndItem),
QITABENT(CFolderInfoTip, IShellTreeWalkerCallBack),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CFolderInfoTip::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CFolderInfoTip::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
// IQueryInfo functions
STDMETHODIMP CFolderInfoTip::GetInfoFlags(DWORD *pdwFlags)
{
*pdwFlags = 0;
return S_OK;
}
//
// Wrapper for FormatMessage. Is this duplicated somewhere else?
DWORD _FormatMessageArg(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageID, DWORD dwLangID, LPWSTR pszBuffer, DWORD cchSize, ...)
{
va_list vaParamList;
va_start(vaParamList, cchSize);
DWORD dwResult = FormatMessage(dwFlags, lpSource, dwMessageID, dwLangID, pszBuffer, cchSize, &vaParamList);
va_end(vaParamList);
return dwResult;
}
// This runs a TreeWalker that gets the info about files and file
// sizes, etc. and then takes those date and stuffs them into a infotip
STDMETHODIMP CFolderInfoTip::GetInfoTip(DWORD dwFlags, LPWSTR *ppwszTip)
{
HRESULT hr = S_OK;
*ppwszTip = NULL;
if (_pszFolderName)
{
TCHAR szTip[INFOTIPSIZE]; // The info tip I build w/ folder contents
szTip[0] = 0;
// If we are to search, then search!
if ((dwFlags & QITIPF_USESLOWTIP) &&
SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("FolderContentsInfoTip"), 0, TRUE))
{
_WalkTree(szTip, ARRAYSIZE(szTip));
}
// Now that we've built or skipped our tip, get the outer tip's info.
if (_pqiOuter)
{
if (szTip[0])
{
LPWSTR pszOuterTip = NULL;
_pqiOuter->GetInfoTip(dwFlags, &pszOuterTip);
// Allocate and build the return tip, ommitting the outer tip if
// it's null, and putting a \n between them
// if they both aren't.
int cch = lstrlen(szTip) + (pszOuterTip ? lstrlen(pszOuterTip) + 1 : 0) + 1;
*ppwszTip = (LPWSTR)CoTaskMemAlloc(cch * sizeof(szTip[0]));
if (*ppwszTip)
{
**ppwszTip = 0; // zero init string
if (pszOuterTip)
{
if (*pszOuterTip)
{
// outer tip first
StrCpyN(*ppwszTip, pszOuterTip, cch);
StrCatBuff(*ppwszTip, L"\n", cch);
}
SHFree(pszOuterTip);
}
StrCatBuff(*ppwszTip, szTip, cch);
}
}
else
{
hr = _pqiOuter->GetInfoTip(dwFlags, ppwszTip);
}
}
}
return hr;
}
STDMETHODIMP CFolderInfoTip::SetPrefixText(LPCWSTR pszPrefix)
{
ICustomizeInfoTip *pcit;
if (_pqiOuter && SUCCEEDED(_pqiOuter->QueryInterface(IID_PPV_ARG(ICustomizeInfoTip, &pcit))))
{
pcit->SetPrefixText(pszPrefix);
pcit->Release();
}
return S_OK;
}
STDMETHODIMP CFolderInfoTip::SetExtraProperties(const SHCOLUMNID *pscid, UINT cscid)
{
ICustomizeInfoTip *pcit;
if (_pqiOuter && SUCCEEDED(_pqiOuter->QueryInterface(IID_PPV_ARG(ICustomizeInfoTip, &pcit))))
{
pcit->SetExtraProperties(pscid, cscid);
pcit->Release();
}
return S_OK;
}
// IParentAndItem
STDMETHODIMP CFolderInfoTip::SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psf, LPCITEMIDLIST pidl)
{
IParentAndItem *ppai;
if (_pqiOuter && SUCCEEDED(_pqiOuter->QueryInterface(IID_PPV_ARG(IParentAndItem, &ppai))))
{
ppai->SetParentAndItem(pidlParent, psf, pidl);
ppai->Release();
}
return S_OK;
}
STDMETHODIMP CFolderInfoTip::GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidl)
{
IParentAndItem *ppai;
if (_pqiOuter && SUCCEEDED(_pqiOuter->QueryInterface(IID_PPV_ARG(IParentAndItem, &ppai))))
{
ppai->GetParentAndItem(ppidlParent, ppsf, ppidl);
ppai->Release();
}
return S_OK;
}
// Helper functions for GetInfoTip
HRESULT CFolderInfoTip::_WalkTree(LPWSTR pszTip, DWORD cchSize)
{
// Get a CShellTreeWalker object to run the search for us.
IShellTreeWalker *pstw;
HRESULT hr = ::CoCreateInstance(CLSID_CShellTreeWalker, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellTreeWalker, &pstw));
if (SUCCEEDED(hr))
{
TCHAR szFolderBlurb[128], szFileBlurb[128], szSizeBlurb[128];
// Remember when we started so we know when to stop
_dwSearchStartTime = GetTickCount();
// Now, if hrTreeWalk is an error, it's not really an error; it just means
// that the search was cut off early, so we don't bother to check
// it. hrTreeWalk is passed to _BuildSizeBlurb so that it know whether or not
// to add "greater than" to the string.
HRESULT hrTreeWalk = pstw->WalkTree(WT_EXCLUDEWALKROOT | WT_NOTIFYFOLDERENTER,
_pszFolderName, L"*.*", 32, SAFECAST(this, IShellTreeWalkerCallBack *));
// Create substrings for size, files, folders (may be empty if there's
// nothing to show)
_BuildSizeBlurb(hrTreeWalk, szSizeBlurb, ARRAYSIZE(szSizeBlurb));
_BuildFileBlurb(hrTreeWalk, szFileBlurb, ARRAYSIZE(szFileBlurb));
_BuildFolderBlurb(hrTreeWalk, szFolderBlurb, ARRAYSIZE(szFolderBlurb));
// Build our local tip
TCHAR szFormatStr[64];
LoadString(HINST_THISDLL, IDS_FIT_TipFormat, szFormatStr, ARRAYSIZE(szFormatStr));
_FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, szFormatStr, 0, 0, pszTip,
cchSize, szSizeBlurb, szFolderBlurb, szFileBlurb);
pstw->Release();
}
return hr;
}
HRESULT CFolderInfoTip::_BuildSizeBlurb(HRESULT hr, LPWSTR pszBlurb, DWORD cchSize)
{
if (_ulTotalSize || (_nFiles || _nSubFolders))
{
WCHAR szSizeString[20];
WCHAR szFormatStr[64];
StrFormatByteSize(_ulTotalSize, szSizeString, ARRAYSIZE(szSizeString));
if (SUCCEEDED(hr))
{
LoadString(HINST_THISDLL, IDS_FIT_Size, szFormatStr, ARRAYSIZE(szFormatStr));
}
else
{
LoadString(HINST_THISDLL, IDS_FIT_Size_LT, szFormatStr, ARRAYSIZE(szFormatStr));
}
_FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, szFormatStr, 0, 0, pszBlurb, cchSize, szSizeString);
}
else
{
LoadString(HINST_THISDLL, IDS_FIT_Size_Empty, pszBlurb, cchSize);
}
return S_OK;
}
HRESULT CFolderInfoTip::_BuildFileBlurb(HRESULT hr, LPWSTR pszBlurb, DWORD cchSize)
{
if (_nFiles && _nFileListCharsUsed)
{
WCHAR szFormatStr[64];
LoadString(HINST_THISDLL, IDS_FIT_Files, szFormatStr, ARRAYSIZE(szFormatStr));
_FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, szFormatStr, 0, 0, pszBlurb, cchSize, _szFileList);
}
else
{
_FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, L"", 0, 0, pszBlurb, cchSize);
}
return S_OK;
}
HRESULT CFolderInfoTip::_BuildFolderBlurb(HRESULT hr, LPWSTR pszBlurb, DWORD cchSize)
{
if (_nSubFolders)
{
WCHAR szFormatStr[64];
LoadString(HINST_THISDLL, IDS_FIT_Folders, szFormatStr, ARRAYSIZE(szFormatStr));
_FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, szFormatStr, 0, 0, pszBlurb, cchSize, _szFolderList);
}
else
{
_FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, L"", 0, 0, pszBlurb, cchSize);
}
return S_OK;
}
//
// A helper func that copies strings into a fixed size buffer,
// taking care of delimeters and everything. Used by EnterFolder
// and FoundFile to build the file and folder lists.
HRESULT CFolderInfoTip::_BufferInsert(LPWSTR pszBuffer, int *pnBufferUsed,
int nBufferMaxSize, LPCWSTR pszPath, int nBufferItems)
{
TCHAR szDelimeter[100], szExtraItems[100];
LoadString(HINST_THISDLL, IDS_FIT_Delimeter, szDelimeter, ARRAYSIZE(szDelimeter));
LoadString(HINST_THISDLL, IDS_FIT_ExtraItems, szExtraItems, ARRAYSIZE(szExtraItems));
// Check to see if the buffer is full, if not, proceed.
if (*pnBufferUsed != nBufferMaxSize)
{
// Holds the file name form the abs. path
// Grab the file name
LPWSTR pszFile = PathFindFileName(pszPath);
if (pszFile)
{
// Calculates if the item will fit, remembering to leave room
// not noly for the delimeter, but for for the extra item marker
// that might be added in the future.
if (*pnBufferUsed + lstrlen(pszFile) + lstrlen(szDelimeter) * 2 + lstrlen(szExtraItems) + 1 <
nBufferMaxSize)
{
// Add the delimeter if this is not the 1st item
if (nBufferItems > 1)
{
StrCpyN(&(pszBuffer[*pnBufferUsed]),
szDelimeter, (nBufferMaxSize - *pnBufferUsed));
*pnBufferUsed += lstrlen(szDelimeter);
}
// Add the item to the buffer
StrCpyN(&(pszBuffer[*pnBufferUsed]), pszFile, (nBufferMaxSize - *pnBufferUsed));
*pnBufferUsed += lstrlen(pszFile);
}
else
{
// In this case, the item won't fit, so just add the extra
// items marker and set the buffer to be full
if (nBufferItems > 1)
{
StrCpyN(&(pszBuffer[*pnBufferUsed]), szDelimeter, (nBufferMaxSize - *pnBufferUsed));
*pnBufferUsed += lstrlen(szDelimeter);
}
StrCpyN(&(pszBuffer[*pnBufferUsed]), szExtraItems, (nBufferMaxSize - *pnBufferUsed));
*pnBufferUsed = nBufferMaxSize;
}
}
}
return S_OK;
}
// IShellTreeWalkerCallBack functions
//
// The TreeWalker calls these whenever it finds a file, etc. We grab
// the data out of the passed TREEWALKERSTATS *and use it to build the
// tip. We also take the filenames that are passed to FoundFile and to
// to EnterFolder to build the file and folder listings
STDMETHODIMP CFolderInfoTip::FoundFile(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd)
{
if (ptws->nDepth == 0)
{
if (!(pwfd->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
_BufferInsert(_szFileList, &_nFileListCharsUsed, ARRAYSIZE(_szFileList), pwszPath, ptws->nFiles);
}
return _GetTreeWalkerData(ptws);
}
STDMETHODIMP CFolderInfoTip::EnterFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd)
{
if (ptws->nDepth == 0)
{
if (!(pwfd->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
_BufferInsert(_szFolderList, &_nFolderListCharsUsed, ARRAYSIZE(_szFolderList), pwszPath, ptws->nFolders);
}
return _GetTreeWalkerData(ptws);
}
STDMETHODIMP CFolderInfoTip::LeaveFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws)
{
return _GetTreeWalkerData(ptws);
}
STDMETHODIMP CFolderInfoTip::HandleError(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, HRESULT hrError)
{
// TODO: look for HRESULT_FROM_WIN32(ACCESS_DENIED) for folders we can't look into.
return _GetTreeWalkerData(ptws);
}
// copies data from the treewalker callback into
// class vars so that they can be used to build the InfoTip. This also cuts
// off the search if too much time has elapsed.
HRESULT CFolderInfoTip::_GetTreeWalkerData(TREEWALKERSTATS *ptws)
{
HRESULT hr = S_OK;
_ulTotalSize = ptws->ulTotalSize;
_nSubFolders = ptws->nFolders;
_nFiles = ptws->nFiles;
if ((GetTickCount() - _dwSearchStartTime) > 3000) // 3 seconds
{
hr = E_UNEXPECTED;
}
return hr;
}
STDAPI CFolderInfoTip_CreateInstance(IUnknown *punkOuter, LPCTSTR pszFolder, REFIID riid, void **ppv)
{
HRESULT hr;
CFolderInfoTip *pdocp = new CFolderInfoTip(punkOuter, pszFolder);
if (pdocp)
{
hr = pdocp->QueryInterface(riid, ppv);
pdocp->Release();
}
else
{
*ppv = NULL;
hr = E_OUTOFMEMORY;
}
return hr;
}