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.

676 lines
17 KiB

  1. /* Copyright 1996 Microsoft */
  2. #include <priv.h>
  3. #include "sccls.h"
  4. #include "aclisf.h"
  5. #include "shellurl.h"
  6. #define AC_GENERAL TF_GENERAL + TF_AUTOCOMPLETE
  7. //
  8. // CACLIShellFolder -- An AutoComplete List COM object that
  9. // opens an IShellFolder for enumeration.
  10. //
  11. /* IUnknown methods */
  12. HRESULT CACLIShellFolder::QueryInterface(REFIID riid, void **ppvObj)
  13. {
  14. static const QITAB qit[] = {
  15. QITABENT(CACLIShellFolder, IEnumString),
  16. QITABENT(CACLIShellFolder, IACList),
  17. QITABENT(CACLIShellFolder, IACList2),
  18. QITABENT(CACLIShellFolder, IShellService),
  19. QITABENT(CACLIShellFolder, ICurrentWorkingDirectory),
  20. QITABENT(CACLIShellFolder, IPersistFolder),
  21. { 0 },
  22. };
  23. return QISearch(this, qit, riid, ppvObj);
  24. }
  25. ULONG CACLIShellFolder::AddRef(void)
  26. {
  27. _cRef++;
  28. return _cRef;
  29. }
  30. ULONG CACLIShellFolder::Release(void)
  31. {
  32. ASSERT(_cRef > 0);
  33. _cRef--;
  34. if (_cRef > 0)
  35. {
  36. return _cRef;
  37. }
  38. delete this;
  39. return 0;
  40. }
  41. /* ICurrentWorkingDirectory methods */
  42. HRESULT CACLIShellFolder::SetDirectory(LPCWSTR pwzPath)
  43. {
  44. HRESULT hr;
  45. LPITEMIDLIST pidl = NULL;
  46. hr = IECreateFromPathW(pwzPath, &pidl);
  47. if (SUCCEEDED(hr))
  48. {
  49. hr = Initialize(pidl);
  50. ILFree(pidl);
  51. }
  52. return hr;
  53. }
  54. /* IPersistFolder methods */
  55. HRESULT CACLIShellFolder::Initialize(LPCITEMIDLIST pidl)
  56. {
  57. HRESULT hr = S_OK;
  58. hr = _Init();
  59. if (FAILED(hr))
  60. return hr;
  61. if (pidl)
  62. {
  63. #ifdef DEBUG
  64. TCHAR szPath[MAX_URL_STRING];
  65. hr = IEGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, SIZECHARS(szPath), NULL);
  66. TraceMsg(AC_GENERAL, "ACListISF::Initialize(%s), SetCurrentWorking Directory happening", szPath);
  67. #endif // DEBUG
  68. hr = _pshuLocation->SetCurrentWorkingDir(pidl);
  69. SetDefaultShellPath(_pshuLocation);
  70. }
  71. Pidl_Set(&_pidlCWD, pidl);
  72. return hr;
  73. }
  74. HRESULT CACLIShellFolder::GetClassID(CLSID *pclsid)
  75. {
  76. *pclsid = CLSID_ACListISF;
  77. return S_OK;
  78. }
  79. /* IEnumString methods */
  80. HRESULT CACLIShellFolder::Reset(void)
  81. {
  82. HRESULT hr;
  83. LPITEMIDLIST pidl = NULL;
  84. TraceMsg(AC_GENERAL, "ACListISF::Reset()");
  85. _fExpand = FALSE;
  86. _nPathIndex = 0;
  87. hr = _Init();
  88. // See if we should show hidden files
  89. SHELLSTATE ss;
  90. ss.fShowAllObjects = FALSE;
  91. SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS /*| SSF_SHOWSYSFILES*/, FALSE);
  92. _fShowHidden = BOOLIFY(ss.fShowAllObjects);
  93. // _fShowSysFiles = BOOLIFY(ss.fShowSysFiles);
  94. if (SUCCEEDED(hr) && IsFlagSet(_dwOptions, ACLO_CURRENTDIR))
  95. {
  96. // Set the Browser's Current Directory.
  97. if (_pbs)
  98. {
  99. _pbs->GetPidl(&pidl);
  100. if (pidl)
  101. Initialize(pidl);
  102. }
  103. hr = _SetLocation(pidl);
  104. if (FAILED(hr))
  105. hr = S_FALSE; // If we failed, keep going, we will just end up now doing anything.
  106. ILFree(pidl);
  107. }
  108. return hr;
  109. }
  110. // If this is an FTP URL, skip it if:
  111. // 1) It's absolute (has a FTP scheme), and
  112. // 2) it contains a '/' after the server name.
  113. BOOL CACLIShellFolder::_SkipForPerf(LPCWSTR pwzExpand)
  114. {
  115. BOOL fSkip = FALSE;
  116. if ((URL_SCHEME_FTP == GetUrlScheme(pwzExpand)))
  117. {
  118. // If it's FTP, we want to prevent from hitting the server until
  119. // after the user has finished AutoCompleting the Server name.
  120. // Since we can't enum server names, the server names will need
  121. // to come from the MRU.
  122. if ((7 >= lstrlen(pwzExpand)) || // There's more than 7 chars "ftp://"
  123. (NULL == StrChr(&(pwzExpand[7]), TEXT('/')))) // There is a '/' after the server, "ftp://serv/"
  124. {
  125. fSkip = TRUE;
  126. }
  127. }
  128. return fSkip;
  129. }
  130. /* IACList methods */
  131. /****************************************************\
  132. FUNCTION: Expand
  133. DESCRIPTION:
  134. This function will attempt to use the pszExpand
  135. parameter to bind to a location in the Shell Name Space.
  136. If that succeeds, this AutoComplete List will then
  137. contain entries which are the display names in that ISF.
  138. \****************************************************/
  139. HRESULT CACLIShellFolder::Expand(LPCOLESTR pszExpand)
  140. {
  141. HRESULT hr = S_OK;
  142. SHSTR strExpand;
  143. LPITEMIDLIST pidl = NULL;
  144. DWORD dwParseFlags = SHURL_FLAGS_NOUI;
  145. TraceMsg(AC_GENERAL, "ACListISF::Expand(%ls)", pszExpand);
  146. _fExpand = FALSE;
  147. _nPathIndex = 0;
  148. strExpand.SetStr(pszExpand);
  149. if (Str_SetPtr(&_szExpandStr, strExpand.GetStr()) && _szExpandStr)
  150. {
  151. if (_SkipForPerf(pszExpand)) // Do we want to skip this item for perf reasons?
  152. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  153. else
  154. hr = _Init();
  155. }
  156. else
  157. {
  158. hr = E_OUTOFMEMORY;
  159. }
  160. if (FAILED(hr))
  161. return hr;
  162. // See if the string points to a location in the Shell Name Space
  163. hr = _pshuLocation->ParseFromOutsideSource(_szExpandStr, dwParseFlags);
  164. if (SUCCEEDED(hr))
  165. {
  166. // Yes it did, so now AutoComplete from that ISF
  167. hr = _pshuLocation->GetPidl(&pidl);
  168. // This may fail if it's something like "ftp:/" and not yet valid".
  169. DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
  170. TraceMsg(AC_GENERAL, "ACListISF::Expand() Pidl=>%s<", Dbg_PidlStr(pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
  171. }
  172. // Set the ISF that we need to enumerate for AutoComplete.
  173. hr = _SetLocation(pidl);
  174. if (pidl)
  175. {
  176. ILFree(pidl);
  177. if (SUCCEEDED(hr))
  178. {
  179. _fExpand = TRUE;
  180. }
  181. }
  182. return hr;
  183. }
  184. /* IACList2 methods */
  185. //+-------------------------------------------------------------------------
  186. // Enables/disables various autocomplete features (see ACLO_* flags)
  187. //--------------------------------------------------------------------------
  188. HRESULT CACLIShellFolder::SetOptions(DWORD dwOptions)
  189. {
  190. _dwOptions = dwOptions;
  191. return S_OK;
  192. }
  193. //+-------------------------------------------------------------------------
  194. // Returns the current option settings
  195. //--------------------------------------------------------------------------
  196. HRESULT CACLIShellFolder::GetOptions(DWORD* pdwOptions)
  197. {
  198. HRESULT hr = E_INVALIDARG;
  199. if (pdwOptions)
  200. {
  201. *pdwOptions = _dwOptions;
  202. hr = S_OK;
  203. }
  204. return hr;
  205. }
  206. HRESULT CACLIShellFolder::_GetNextWrapper(LPWSTR pszName, DWORD cchSize)
  207. {
  208. HRESULT hr = S_OK;
  209. LPITEMIDLIST pidl = NULL;
  210. ULONG celtFetched = 0;
  211. // If this directory (ISF) doesn't contain any more items to enum,
  212. // then go on to the next directory (ISF) to enum.
  213. do
  214. {
  215. BOOL fFilter;
  216. do
  217. {
  218. fFilter = FALSE;
  219. hr = _peidl->Next(1, &pidl, &celtFetched);
  220. if (S_OK == hr)
  221. {
  222. hr = _PassesFilter(pidl, pszName, cchSize);
  223. if (FAILED(hr))
  224. {
  225. fFilter = TRUE;
  226. }
  227. ILFree(pidl);
  228. }
  229. }
  230. while (fFilter);
  231. }
  232. while ((S_OK != hr) && (S_OK == _TryNextPath()));
  233. return hr;
  234. }
  235. HRESULT CACLIShellFolder::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
  236. {
  237. HRESULT hr = S_OK;
  238. LPITEMIDLIST pidl;
  239. ULONG celtFetched;
  240. BOOL fUsingCachePidl = FALSE;
  241. WCHAR szDisplayName[MAX_PATH];
  242. *pceltFetched = 0;
  243. if (!celt)
  244. return S_OK;
  245. // If there isn't a Current Working Directory, skip to another
  246. // Path to enum.
  247. if (!_peidl)
  248. hr = _TryNextPath();
  249. if ((!_peidl) || (!rgelt))
  250. return S_FALSE;
  251. // Get the next PIDL.
  252. if (_pidlInFolder)
  253. {
  254. // We have a cached, SHGDN_INFOLDER, so lets try that.
  255. pidl = _pidlInFolder;
  256. celtFetched = 1;
  257. _pidlInFolder = NULL;
  258. fUsingCachePidl = TRUE;
  259. hr = _GetPidlName(pidl, fUsingCachePidl, szDisplayName, ARRAYSIZE(szDisplayName));
  260. ILFree(pidl);
  261. AssertMsg((S_OK == hr), TEXT("CACLIShellFolder::Next() hr doesn't equal S_OK, so we need to call _GetNextWrapper() but we aren't. Please call BryanSt."));
  262. }
  263. else
  264. {
  265. hr = _GetNextWrapper(szDisplayName, ARRAYSIZE(szDisplayName));
  266. }
  267. // This is giving us entries (favorites without .url extension) that cannot be navigated to.
  268. // So I'm disabling this for IE5B2. (stevepro)
  269. //
  270. // else
  271. // Pidl_Set(&_pidlInFolder, pidl); // We will try (SHGDN_INFOLDER) next time.
  272. if (SUCCEEDED(hr))
  273. {
  274. LPOLESTR pwszPath;
  275. // Allocate a return buffer (caller will free it).
  276. pwszPath = (LPOLESTR)CoTaskMemAlloc((lstrlenW(szDisplayName) + 1) * SIZEOF(WCHAR));
  277. if (pwszPath)
  278. {
  279. StrCpyW(pwszPath, szDisplayName);
  280. TraceMsg(AC_GENERAL, "ACListISF::Next() Str=%s, _nPathIndex=%d", pwszPath, _nPathIndex);
  281. if (SUCCEEDED(hr))
  282. {
  283. rgelt[0] = pwszPath;
  284. *pceltFetched = 1;
  285. }
  286. }
  287. else
  288. {
  289. hr = E_OUTOFMEMORY;
  290. }
  291. }
  292. return hr;
  293. }
  294. HRESULT CACLIShellFolder::_GetPidlName(LPCITEMIDLIST pidl, BOOL fUsingCachePidl, LPWSTR pszName, DWORD cchSize)
  295. {
  296. HRESULT hr = S_OK;
  297. WCHAR szName[MAX_PATH];
  298. // Get the display name of the PIDL.
  299. if (!fUsingCachePidl)
  300. {
  301. hr = DisplayNameOf(_psf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING | SHGDN_FORADDRESSBAR, szName, ARRAYSIZE(szName));
  302. // some namespaces don't understand _FORADDRESSBAR -- default to IE4 behavior
  303. if (FAILED(hr))
  304. hr = DisplayNameOf(_psf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING, szName, ARRAYSIZE(szName));
  305. }
  306. if (fUsingCachePidl || FAILED(hr))
  307. {
  308. hr = DisplayNameOf(_psf, pidl, SHGDN_INFOLDER | SHGDN_FORADDRESSBAR, szName, ARRAYSIZE(szName));
  309. // some namespaces don't understand _FORADDRESSBAR -- default to IE4 behavior
  310. if (FAILED(hr))
  311. hr = DisplayNameOf(_psf, pidl, SHGDN_INFOLDER, szName, ARRAYSIZE(szName));
  312. }
  313. if (SUCCEEDED(hr))
  314. {
  315. DWORD cchRemain = cchSize;
  316. LPTSTR pszInsert = pszName;
  317. pszName[0] = 0; // Init the out buffer.
  318. // First, prepend the _szExpandStr if necessary.
  319. // This is needed for sections that don't give
  320. // the entire path, like "My Computer" items
  321. // which is (3 == _nPathIndex)
  322. if (_fExpand && ((_nPathIndex == 0) /*|| (_nPathIndex == 3)*/))
  323. {
  324. DWORD cchExpand = lstrlen(_szExpandStr);
  325. // Make sure that for UNC paths the "\\share" is not already
  326. // prepended. NT5 returns the name in this final form.
  327. if ((StrCmpNI(szName, _szExpandStr, cchExpand) != 0) ||
  328. (szName[0] != L'\\') || (szName[1] != L'\\'))
  329. {
  330. StrCpyN(pszInsert, _szExpandStr, cchSize);
  331. pszInsert += cchExpand;
  332. cchRemain -= cchExpand;
  333. }
  334. }
  335. // Next, append the display name.
  336. StrCpyN(pszInsert, szName, cchRemain);
  337. TraceMsg(AC_GENERAL, "ACListISF::_GetPidlName() Str=%s, _nPathIndex=%d", pszInsert, _nPathIndex);
  338. }
  339. return hr;
  340. }
  341. HRESULT CACLIShellFolder::_PassesFilter(LPCITEMIDLIST pidl, LPWSTR pszName, DWORD cchSize)
  342. {
  343. HRESULT hr = S_OK;
  344. DWORD dwAttributes = (SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM);
  345. hr = _GetPidlName(pidl, FALSE, pszName, cchSize);
  346. if (SUCCEEDED(hr))
  347. {
  348. if (((ACLO_FILESYSONLY & _dwOptions) || (ACLO_FILESYSDIRS & _dwOptions)) &&
  349. SUCCEEDED(_psf->GetAttributesOf(1, (LPCITEMIDLIST *) &pidl, &dwAttributes)))
  350. {
  351. if (!(dwAttributes & (SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM)))
  352. {
  353. // We reject it because it's not in the file system.
  354. hr = E_FAIL; // Skip this item.
  355. TraceMsg(AC_GENERAL, "ACListISF::_PassesFilter() We are skipping\"%s\" because it doesn't match the filter", pszName);
  356. }
  357. else
  358. {
  359. if ((ACLO_FILESYSDIRS & _dwOptions) && !PathIsDirectory(pszName))
  360. {
  361. hr = E_FAIL; // Skip this item since it's not a directory
  362. }
  363. }
  364. }
  365. }
  366. return hr;
  367. }
  368. HRESULT CACLIShellFolder::_Init(void)
  369. {
  370. HRESULT hr = S_OK;
  371. if (!_pshuLocation)
  372. {
  373. _pshuLocation = new CShellUrl();
  374. if (!_pshuLocation)
  375. return E_OUTOFMEMORY;
  376. }
  377. return hr;
  378. }
  379. HRESULT CACLIShellFolder::_SetLocation(LPCITEMIDLIST pidl)
  380. {
  381. HRESULT hr;
  382. // Free old location
  383. ATOMICRELEASE(_peidl);
  384. ATOMICRELEASE(_psf);
  385. // Set to new location (Valid if NULL)
  386. Pidl_Set(&_pidl, pidl);
  387. if (_pidl)
  388. {
  389. hr = IEBindToObject(_pidl, &_psf);
  390. if (SUCCEEDED(hr))
  391. {
  392. DWORD grfFlags = (_fShowHidden ? SHCONTF_INCLUDEHIDDEN : 0) |
  393. SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
  394. hr = IShellFolder_EnumObjects(_psf, NULL, grfFlags, &_peidl);
  395. if (hr != S_OK)
  396. {
  397. hr = E_FAIL; // S_FALSE -> empty enumerator
  398. }
  399. }
  400. }
  401. else
  402. hr = E_OUTOFMEMORY;
  403. if (FAILED(hr))
  404. {
  405. // Clear if we could not get all the info
  406. ATOMICRELEASE(_peidl);
  407. ATOMICRELEASE(_psf);
  408. Pidl_Set(&_pidl, NULL);
  409. }
  410. //
  411. // NOTE: This is necessary because this memory is alloced in a ACBG thread, but not
  412. // freed until the next call to Reset() or the destructor, which will
  413. // happen in the main thread or another ACBG thread.
  414. //
  415. return hr;
  416. }
  417. HRESULT CACLIShellFolder::_TryNextPath(void)
  418. {
  419. HRESULT hr = S_FALSE;
  420. if (0 == _nPathIndex)
  421. {
  422. _nPathIndex = 1;
  423. if (_pidlCWD && IsFlagSet(_dwOptions, ACLO_CURRENTDIR))
  424. {
  425. hr = _SetLocation(_pidlCWD);
  426. if (SUCCEEDED(hr))
  427. {
  428. goto done;
  429. }
  430. }
  431. }
  432. if (1 == _nPathIndex)
  433. {
  434. _nPathIndex = 2;
  435. if(IsFlagSet(_dwOptions, ACLO_DESKTOP))
  436. {
  437. // we used to autocomplete g_pidlRoot in the rooted explorer
  438. // case, but this was a little weird. if we want to add this,
  439. // we should add ACLO_ROOTED or something.
  440. // use the desktop...
  441. hr = _SetLocation(&s_idlNULL);
  442. if (SUCCEEDED(hr))
  443. {
  444. goto done;
  445. }
  446. }
  447. }
  448. if (2 == _nPathIndex)
  449. {
  450. _nPathIndex = 3;
  451. if (IsFlagSet(_dwOptions, ACLO_MYCOMPUTER))
  452. {
  453. LPITEMIDLIST pidlMyComputer;
  454. if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &pidlMyComputer)))
  455. {
  456. hr = _SetLocation(pidlMyComputer);
  457. ILFree(pidlMyComputer);
  458. if (SUCCEEDED(hr))
  459. {
  460. goto done;
  461. }
  462. }
  463. }
  464. }
  465. // Also search favorites
  466. if (3 == _nPathIndex)
  467. {
  468. _nPathIndex = 4;
  469. if (IsFlagSet(_dwOptions, ACLO_FAVORITES))
  470. {
  471. LPITEMIDLIST pidlFavorites;
  472. if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidlFavorites)))
  473. {
  474. hr = _SetLocation(pidlFavorites);
  475. ILFree(pidlFavorites);
  476. if (SUCCEEDED(hr))
  477. {
  478. goto done;
  479. }
  480. }
  481. }
  482. }
  483. if (FAILED(hr))
  484. hr = S_FALSE; // This is how we want our errors returned.
  485. done:
  486. return hr;
  487. }
  488. //================================
  489. // *** IShellService Interface ***
  490. /****************************************************\
  491. FUNCTION: SetOwner
  492. DESCRIPTION:
  493. Update the connection to the Browser window so
  494. we can always get the PIDL of the current location.
  495. \****************************************************/
  496. HRESULT CACLIShellFolder::SetOwner(IUnknown* punkOwner)
  497. {
  498. HRESULT hr = S_OK;
  499. IBrowserService * pbs = NULL;
  500. ATOMICRELEASE(_pbs);
  501. if (punkOwner)
  502. hr = punkOwner->QueryInterface(IID_IBrowserService, (LPVOID *) &pbs);
  503. if (EVAL(SUCCEEDED(hr)))
  504. _pbs = pbs;
  505. return S_OK;
  506. }
  507. /* Constructor / Destructor / CreateInstance */
  508. CACLIShellFolder::CACLIShellFolder()
  509. {
  510. DllAddRef();
  511. ASSERT(!_peidl);
  512. ASSERT(!_psf);
  513. ASSERT(!_pbs);
  514. ASSERT(!_pidl);
  515. ASSERT(!_pidlCWD);
  516. ASSERT(!_fExpand);
  517. ASSERT(!_pshuLocation);
  518. ASSERT(!_szExpandStr);
  519. _cRef = 1;
  520. // Default search paths
  521. _dwOptions = ACLO_CURRENTDIR | ACLO_MYCOMPUTER;
  522. }
  523. CACLIShellFolder::~CACLIShellFolder()
  524. {
  525. ATOMICRELEASE(_peidl);
  526. ATOMICRELEASE(_psf);
  527. ATOMICRELEASE(_pbs);
  528. Pidl_Set(&_pidl, NULL);
  529. Pidl_Set(&_pidlCWD, NULL);
  530. Pidl_Set(&_pidlInFolder, NULL);
  531. Str_SetPtr(&_szExpandStr, NULL);
  532. if (_pshuLocation)
  533. delete _pshuLocation;
  534. DllRelease();
  535. }
  536. HRESULT CACLIShellFolder_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
  537. {
  538. *ppunk = NULL;
  539. CACLIShellFolder *paclSF = new CACLIShellFolder();
  540. if (paclSF)
  541. {
  542. *ppunk = SAFECAST(paclSF, IEnumString *);
  543. return S_OK;
  544. }
  545. return E_OUTOFMEMORY;
  546. }