Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

470 lines
16 KiB

  1. #include "shellprv.h"
  2. #include "ids.h"
  3. class CFolderInfoTip : public IQueryInfo, public ICustomizeInfoTip, public IParentAndItem, public IShellTreeWalkerCallBack
  4. {
  5. public:
  6. CFolderInfoTip(IUnknown *punk, LPCTSTR pszFolder);
  7. // IUnknown
  8. STDMETHODIMP QueryInterface(REFIID, void **);
  9. STDMETHODIMP_(ULONG) AddRef(void);
  10. STDMETHODIMP_(ULONG) Release(void);
  11. // IQueryInfo methods.
  12. STDMETHODIMP GetInfoTip(DWORD dwFlags, WCHAR** ppwszTip);
  13. STDMETHODIMP GetInfoFlags(DWORD *pdwFlags);
  14. // ICustomizeInfoTip
  15. STDMETHODIMP SetPrefixText(LPCWSTR pszPrefix);
  16. STDMETHODIMP SetExtraProperties(const SHCOLUMNID *pscid, UINT cscid);
  17. // IParentAndItem
  18. STDMETHODIMP SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psf, LPCITEMIDLIST pidlChild);
  19. STDMETHODIMP GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidlChild);
  20. // IShellTreeWalkerCallBack methods
  21. STDMETHODIMP FoundFile(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd);
  22. STDMETHODIMP EnterFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd);
  23. STDMETHODIMP LeaveFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws);
  24. STDMETHODIMP HandleError(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, HRESULT hrError);
  25. private:
  26. ~CFolderInfoTip();
  27. HRESULT _GetTreeWalkerData(TREEWALKERSTATS *ptws);
  28. HRESULT _BufferInsert(LPWSTR pszBuffer, int *puBufferUsed, int uBufferMaxSize, LPCWSTR pwszPath, int cBufferItems);
  29. HRESULT _WalkTree(LPWSTR pszTip, DWORD cchSize);
  30. HRESULT _BuildSizeBlurb(HRESULT hr, LPWSTR pszSizeBlurb, DWORD cchSize);
  31. HRESULT _BuildFolderBlurb(HRESULT hr, LPWSTR pszFolderBlurb, DWORD cchSize);
  32. HRESULT _BuildFileBlurb(HRESULT hr, LPWSTR pszSizeBlurb, DWORD cchSize);
  33. LONG _cRef; // Reference Counter
  34. LPWSTR _pszFolderName; // File name of the target folder
  35. IQueryInfo *_pqiOuter; // Outer info tip for folders (say, for comments)
  36. ULONGLONG _ulTotalSize; // Total size of encountered files
  37. UINT _nSubFolders; // Total number of subfolders of target
  38. UINT _nFiles; // Total number of subfiles of target folder
  39. DWORD _dwSearchStartTime; // Time when search started
  40. WCHAR _szFileList[60]; // List of files in target folder
  41. int _nFileListCharsUsed; // Number of characters used in buffer
  42. WCHAR _szFolderList[60]; // List of subfolders of target
  43. int _nFolderListCharsUsed; // Number of chars used in folder buffer
  44. };
  45. // Constructor and Destructor do nothing more than set everything to
  46. // 0 and ping the dll
  47. CFolderInfoTip::CFolderInfoTip(IUnknown *punkOutter, LPCTSTR pszFolder) : _cRef(1)
  48. {
  49. // Init everything to 0
  50. _pszFolderName = StrDup(pszFolder);
  51. _szFileList[0] = 0;
  52. _nFileListCharsUsed = 0;
  53. _szFolderList[0] = 0;
  54. _nFolderListCharsUsed = 0;
  55. _ulTotalSize = 0;
  56. _nSubFolders = 0;
  57. _nFiles = 0;
  58. punkOutter->QueryInterface(IID_PPV_ARG(IQueryInfo, &_pqiOuter));
  59. DllAddRef();
  60. }
  61. CFolderInfoTip::~CFolderInfoTip()
  62. {
  63. LocalFree(_pszFolderName);
  64. if (_pqiOuter)
  65. _pqiOuter->Release();
  66. DllRelease();
  67. }
  68. HRESULT CFolderInfoTip::QueryInterface(REFIID riid, void **ppv)
  69. {
  70. static const QITAB qit[] = {
  71. QITABENT(CFolderInfoTip, IQueryInfo),
  72. QITABENT(CFolderInfoTip, ICustomizeInfoTip),
  73. QITABENT(CFolderInfoTip, IParentAndItem),
  74. QITABENT(CFolderInfoTip, IShellTreeWalkerCallBack),
  75. { 0 },
  76. };
  77. return QISearch(this, qit, riid, ppv);
  78. }
  79. ULONG CFolderInfoTip::AddRef()
  80. {
  81. return InterlockedIncrement(&_cRef);
  82. }
  83. ULONG CFolderInfoTip::Release()
  84. {
  85. if (InterlockedDecrement(&_cRef))
  86. return _cRef;
  87. delete this;
  88. return 0;
  89. }
  90. // IQueryInfo functions
  91. STDMETHODIMP CFolderInfoTip::GetInfoFlags(DWORD *pdwFlags)
  92. {
  93. *pdwFlags = 0;
  94. return S_OK;
  95. }
  96. //
  97. // Wrapper for FormatMessage. Is this duplicated somewhere else?
  98. DWORD _FormatMessageArg(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageID, DWORD dwLangID, LPWSTR pszBuffer, DWORD cchSize, ...)
  99. {
  100. va_list vaParamList;
  101. va_start(vaParamList, cchSize);
  102. DWORD dwResult = FormatMessage(dwFlags, lpSource, dwMessageID, dwLangID, pszBuffer, cchSize, &vaParamList);
  103. va_end(vaParamList);
  104. return dwResult;
  105. }
  106. // This runs a TreeWalker that gets the info about files and file
  107. // sizes, etc. and then takes those date and stuffs them into a infotip
  108. STDMETHODIMP CFolderInfoTip::GetInfoTip(DWORD dwFlags, LPWSTR *ppwszTip)
  109. {
  110. HRESULT hr = S_OK;
  111. *ppwszTip = NULL;
  112. if (_pszFolderName)
  113. {
  114. TCHAR szTip[INFOTIPSIZE]; // The info tip I build w/ folder contents
  115. szTip[0] = 0;
  116. // If we are to search, then search!
  117. if ((dwFlags & QITIPF_USESLOWTIP) &&
  118. SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("FolderContentsInfoTip"), 0, TRUE))
  119. {
  120. _WalkTree(szTip, ARRAYSIZE(szTip));
  121. }
  122. // Now that we've built or skipped our tip, get the outer tip's info.
  123. if (_pqiOuter)
  124. {
  125. if (szTip[0])
  126. {
  127. LPWSTR pszOuterTip = NULL;
  128. _pqiOuter->GetInfoTip(dwFlags, &pszOuterTip);
  129. // Allocate and build the return tip, ommitting the outer tip if
  130. // it's null, and putting a \n between them
  131. // if they both aren't.
  132. int cch = lstrlen(szTip) + (pszOuterTip ? lstrlen(pszOuterTip) + 1 : 0) + 1;
  133. *ppwszTip = (LPWSTR)CoTaskMemAlloc(cch * sizeof(szTip[0]));
  134. if (*ppwszTip)
  135. {
  136. **ppwszTip = 0; // zero init string
  137. if (pszOuterTip)
  138. {
  139. if (*pszOuterTip)
  140. {
  141. // outer tip first
  142. StrCpyN(*ppwszTip, pszOuterTip, cch);
  143. StrCatBuff(*ppwszTip, L"\n", cch);
  144. }
  145. SHFree(pszOuterTip);
  146. }
  147. StrCatBuff(*ppwszTip, szTip, cch);
  148. }
  149. }
  150. else
  151. {
  152. hr = _pqiOuter->GetInfoTip(dwFlags, ppwszTip);
  153. }
  154. }
  155. }
  156. return hr;
  157. }
  158. STDMETHODIMP CFolderInfoTip::SetPrefixText(LPCWSTR pszPrefix)
  159. {
  160. ICustomizeInfoTip *pcit;
  161. if (_pqiOuter && SUCCEEDED(_pqiOuter->QueryInterface(IID_PPV_ARG(ICustomizeInfoTip, &pcit))))
  162. {
  163. pcit->SetPrefixText(pszPrefix);
  164. pcit->Release();
  165. }
  166. return S_OK;
  167. }
  168. STDMETHODIMP CFolderInfoTip::SetExtraProperties(const SHCOLUMNID *pscid, UINT cscid)
  169. {
  170. ICustomizeInfoTip *pcit;
  171. if (_pqiOuter && SUCCEEDED(_pqiOuter->QueryInterface(IID_PPV_ARG(ICustomizeInfoTip, &pcit))))
  172. {
  173. pcit->SetExtraProperties(pscid, cscid);
  174. pcit->Release();
  175. }
  176. return S_OK;
  177. }
  178. // IParentAndItem
  179. STDMETHODIMP CFolderInfoTip::SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psf, LPCITEMIDLIST pidl)
  180. {
  181. IParentAndItem *ppai;
  182. if (_pqiOuter && SUCCEEDED(_pqiOuter->QueryInterface(IID_PPV_ARG(IParentAndItem, &ppai))))
  183. {
  184. ppai->SetParentAndItem(pidlParent, psf, pidl);
  185. ppai->Release();
  186. }
  187. return S_OK;
  188. }
  189. STDMETHODIMP CFolderInfoTip::GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidl)
  190. {
  191. IParentAndItem *ppai;
  192. if (_pqiOuter && SUCCEEDED(_pqiOuter->QueryInterface(IID_PPV_ARG(IParentAndItem, &ppai))))
  193. {
  194. ppai->GetParentAndItem(ppidlParent, ppsf, ppidl);
  195. ppai->Release();
  196. }
  197. return S_OK;
  198. }
  199. // Helper functions for GetInfoTip
  200. HRESULT CFolderInfoTip::_WalkTree(LPWSTR pszTip, DWORD cchSize)
  201. {
  202. // Get a CShellTreeWalker object to run the search for us.
  203. IShellTreeWalker *pstw;
  204. HRESULT hr = ::CoCreateInstance(CLSID_CShellTreeWalker, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellTreeWalker, &pstw));
  205. if (SUCCEEDED(hr))
  206. {
  207. TCHAR szFolderBlurb[128], szFileBlurb[128], szSizeBlurb[128];
  208. // Remember when we started so we know when to stop
  209. _dwSearchStartTime = GetTickCount();
  210. // Now, if hrTreeWalk is an error, it's not really an error; it just means
  211. // that the search was cut off early, so we don't bother to check
  212. // it. hrTreeWalk is passed to _BuildSizeBlurb so that it know whether or not
  213. // to add "greater than" to the string.
  214. HRESULT hrTreeWalk = pstw->WalkTree(WT_EXCLUDEWALKROOT | WT_NOTIFYFOLDERENTER,
  215. _pszFolderName, L"*.*", 32, SAFECAST(this, IShellTreeWalkerCallBack *));
  216. // Create substrings for size, files, folders (may be empty if there's
  217. // nothing to show)
  218. _BuildSizeBlurb(hrTreeWalk, szSizeBlurb, ARRAYSIZE(szSizeBlurb));
  219. _BuildFileBlurb(hrTreeWalk, szFileBlurb, ARRAYSIZE(szFileBlurb));
  220. _BuildFolderBlurb(hrTreeWalk, szFolderBlurb, ARRAYSIZE(szFolderBlurb));
  221. // Build our local tip
  222. TCHAR szFormatStr[64];
  223. LoadString(HINST_THISDLL, IDS_FIT_TipFormat, szFormatStr, ARRAYSIZE(szFormatStr));
  224. _FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, szFormatStr, 0, 0, pszTip,
  225. cchSize, szSizeBlurb, szFolderBlurb, szFileBlurb);
  226. pstw->Release();
  227. }
  228. return hr;
  229. }
  230. HRESULT CFolderInfoTip::_BuildSizeBlurb(HRESULT hr, LPWSTR pszBlurb, DWORD cchSize)
  231. {
  232. if (_ulTotalSize || (_nFiles || _nSubFolders))
  233. {
  234. WCHAR szSizeString[20];
  235. WCHAR szFormatStr[64];
  236. StrFormatByteSize(_ulTotalSize, szSizeString, ARRAYSIZE(szSizeString));
  237. if (SUCCEEDED(hr))
  238. {
  239. LoadString(HINST_THISDLL, IDS_FIT_Size, szFormatStr, ARRAYSIZE(szFormatStr));
  240. }
  241. else
  242. {
  243. LoadString(HINST_THISDLL, IDS_FIT_Size_LT, szFormatStr, ARRAYSIZE(szFormatStr));
  244. }
  245. _FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, szFormatStr, 0, 0, pszBlurb, cchSize, szSizeString);
  246. }
  247. else
  248. {
  249. LoadString(HINST_THISDLL, IDS_FIT_Size_Empty, pszBlurb, cchSize);
  250. }
  251. return S_OK;
  252. }
  253. HRESULT CFolderInfoTip::_BuildFileBlurb(HRESULT hr, LPWSTR pszBlurb, DWORD cchSize)
  254. {
  255. if (_nFiles && _nFileListCharsUsed)
  256. {
  257. WCHAR szFormatStr[64];
  258. LoadString(HINST_THISDLL, IDS_FIT_Files, szFormatStr, ARRAYSIZE(szFormatStr));
  259. _FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, szFormatStr, 0, 0, pszBlurb, cchSize, _szFileList);
  260. }
  261. else
  262. {
  263. _FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, L"", 0, 0, pszBlurb, cchSize);
  264. }
  265. return S_OK;
  266. }
  267. HRESULT CFolderInfoTip::_BuildFolderBlurb(HRESULT hr, LPWSTR pszBlurb, DWORD cchSize)
  268. {
  269. if (_nSubFolders)
  270. {
  271. WCHAR szFormatStr[64];
  272. LoadString(HINST_THISDLL, IDS_FIT_Folders, szFormatStr, ARRAYSIZE(szFormatStr));
  273. _FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, szFormatStr, 0, 0, pszBlurb, cchSize, _szFolderList);
  274. }
  275. else
  276. {
  277. _FormatMessageArg(FORMAT_MESSAGE_FROM_STRING, L"", 0, 0, pszBlurb, cchSize);
  278. }
  279. return S_OK;
  280. }
  281. //
  282. // A helper func that copies strings into a fixed size buffer,
  283. // taking care of delimeters and everything. Used by EnterFolder
  284. // and FoundFile to build the file and folder lists.
  285. HRESULT CFolderInfoTip::_BufferInsert(LPWSTR pszBuffer, int *pnBufferUsed,
  286. int nBufferMaxSize, LPCWSTR pszPath, int nBufferItems)
  287. {
  288. TCHAR szDelimeter[100], szExtraItems[100];
  289. LoadString(HINST_THISDLL, IDS_FIT_Delimeter, szDelimeter, ARRAYSIZE(szDelimeter));
  290. LoadString(HINST_THISDLL, IDS_FIT_ExtraItems, szExtraItems, ARRAYSIZE(szExtraItems));
  291. // Check to see if the buffer is full, if not, proceed.
  292. if (*pnBufferUsed != nBufferMaxSize)
  293. {
  294. // Holds the file name form the abs. path
  295. // Grab the file name
  296. LPWSTR pszFile = PathFindFileName(pszPath);
  297. if (pszFile)
  298. {
  299. // Calculates if the item will fit, remembering to leave room
  300. // not noly for the delimeter, but for for the extra item marker
  301. // that might be added in the future.
  302. if (*pnBufferUsed + lstrlen(pszFile) + lstrlen(szDelimeter) * 2 + lstrlen(szExtraItems) + 1 <
  303. nBufferMaxSize)
  304. {
  305. // Add the delimeter if this is not the 1st item
  306. if (nBufferItems > 1)
  307. {
  308. StrCpyN(&(pszBuffer[*pnBufferUsed]),
  309. szDelimeter, (nBufferMaxSize - *pnBufferUsed));
  310. *pnBufferUsed += lstrlen(szDelimeter);
  311. }
  312. // Add the item to the buffer
  313. StrCpyN(&(pszBuffer[*pnBufferUsed]), pszFile, (nBufferMaxSize - *pnBufferUsed));
  314. *pnBufferUsed += lstrlen(pszFile);
  315. }
  316. else
  317. {
  318. // In this case, the item won't fit, so just add the extra
  319. // items marker and set the buffer to be full
  320. if (nBufferItems > 1)
  321. {
  322. StrCpyN(&(pszBuffer[*pnBufferUsed]), szDelimeter, (nBufferMaxSize - *pnBufferUsed));
  323. *pnBufferUsed += lstrlen(szDelimeter);
  324. }
  325. StrCpyN(&(pszBuffer[*pnBufferUsed]), szExtraItems, (nBufferMaxSize - *pnBufferUsed));
  326. *pnBufferUsed = nBufferMaxSize;
  327. }
  328. }
  329. }
  330. return S_OK;
  331. }
  332. // IShellTreeWalkerCallBack functions
  333. //
  334. // The TreeWalker calls these whenever it finds a file, etc. We grab
  335. // the data out of the passed TREEWALKERSTATS *and use it to build the
  336. // tip. We also take the filenames that are passed to FoundFile and to
  337. // to EnterFolder to build the file and folder listings
  338. STDMETHODIMP CFolderInfoTip::FoundFile(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd)
  339. {
  340. if (ptws->nDepth == 0)
  341. {
  342. if (!(pwfd->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
  343. _BufferInsert(_szFileList, &_nFileListCharsUsed, ARRAYSIZE(_szFileList), pwszPath, ptws->nFiles);
  344. }
  345. return _GetTreeWalkerData(ptws);
  346. }
  347. STDMETHODIMP CFolderInfoTip::EnterFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd)
  348. {
  349. if (ptws->nDepth == 0)
  350. {
  351. if (!(pwfd->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
  352. _BufferInsert(_szFolderList, &_nFolderListCharsUsed, ARRAYSIZE(_szFolderList), pwszPath, ptws->nFolders);
  353. }
  354. return _GetTreeWalkerData(ptws);
  355. }
  356. STDMETHODIMP CFolderInfoTip::LeaveFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws)
  357. {
  358. return _GetTreeWalkerData(ptws);
  359. }
  360. STDMETHODIMP CFolderInfoTip::HandleError(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, HRESULT hrError)
  361. {
  362. // TODO: look for HRESULT_FROM_WIN32(ACCESS_DENIED) for folders we can't look into.
  363. return _GetTreeWalkerData(ptws);
  364. }
  365. // copies data from the treewalker callback into
  366. // class vars so that they can be used to build the InfoTip. This also cuts
  367. // off the search if too much time has elapsed.
  368. HRESULT CFolderInfoTip::_GetTreeWalkerData(TREEWALKERSTATS *ptws)
  369. {
  370. HRESULT hr = S_OK;
  371. _ulTotalSize = ptws->ulTotalSize;
  372. _nSubFolders = ptws->nFolders;
  373. _nFiles = ptws->nFiles;
  374. if ((GetTickCount() - _dwSearchStartTime) > 3000) // 3 seconds
  375. {
  376. hr = E_UNEXPECTED;
  377. }
  378. return hr;
  379. }
  380. STDAPI CFolderInfoTip_CreateInstance(IUnknown *punkOuter, LPCTSTR pszFolder, REFIID riid, void **ppv)
  381. {
  382. HRESULT hr;
  383. CFolderInfoTip *pdocp = new CFolderInfoTip(punkOuter, pszFolder);
  384. if (pdocp)
  385. {
  386. hr = pdocp->QueryInterface(riid, ppv);
  387. pdocp->Release();
  388. }
  389. else
  390. {
  391. *ppv = NULL;
  392. hr = E_OUTOFMEMORY;
  393. }
  394. return hr;
  395. }