#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() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } // 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) { WCHAR 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(WCHAR)); if (*ppwszTip) { **ppwszTip = 0; // zero init string if (pszOuterTip && *pszOuterTip) { // outer tip first StrCpyN(*ppwszTip, pszOuterTip, cch); StrCatBuff(*ppwszTip, L"\n", cch); } StrCatBuff(*ppwszTip, szTip, cch); } if (pszOuterTip) { SHFree(pszOuterTip); } } 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 szDelimiter[100], szExtraItems[100]; LoadString(HINST_THISDLL, IDS_FIT_Delimeter, szDelimiter, ARRAYSIZE(szDelimiter)); 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 only for the delimeter, but for for the extra item marker // that might be added in the future. if (*pnBufferUsed + lstrlen(pszFile) + lstrlen(szDelimiter) * 2 + lstrlen(szExtraItems) + 1 < nBufferMaxSize) { // Add the delimeter if this is not the 1st item if (nBufferItems > 1) { StrCpyN(&(pszBuffer[*pnBufferUsed]), szDelimiter, (nBufferMaxSize - *pnBufferUsed)); *pnBufferUsed += lstrlen(szDelimiter); } // 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]), szDelimiter, (nBufferMaxSize - *pnBufferUsed)); *pnBufferUsed += lstrlen(szDelimiter); } 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; }