/* Copyright 1996 Microsoft */ #include #include "sccls.h" #include "aclisf.h" #include "shellurl.h" #define AC_GENERAL TF_GENERAL + TF_AUTOCOMPLETE // // CACLIShellFolder -- An AutoComplete List COM object that // opens an IShellFolder for enumeration. // /* IUnknown methods */ HRESULT CACLIShellFolder::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CACLIShellFolder, IEnumString), QITABENT(CACLIShellFolder, IACList), QITABENT(CACLIShellFolder, IACList2), QITABENT(CACLIShellFolder, IShellService), QITABENT(CACLIShellFolder, ICurrentWorkingDirectory), QITABENT(CACLIShellFolder, IPersistFolder), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } ULONG CACLIShellFolder::AddRef(void) { _cRef++; return _cRef; } ULONG CACLIShellFolder::Release(void) { ASSERT(_cRef > 0); _cRef--; if (_cRef > 0) { return _cRef; } delete this; return 0; } /* ICurrentWorkingDirectory methods */ HRESULT CACLIShellFolder::SetDirectory(LPCWSTR pwzPath) { HRESULT hr; LPITEMIDLIST pidl = NULL; hr = IECreateFromPathW(pwzPath, &pidl); if (SUCCEEDED(hr)) { hr = Initialize(pidl); ILFree(pidl); } return hr; } /* IPersistFolder methods */ HRESULT CACLIShellFolder::Initialize(LPCITEMIDLIST pidl) { HRESULT hr = S_OK; hr = _Init(); if (FAILED(hr)) return hr; if (pidl) { #ifdef DEBUG TCHAR szPath[MAX_URL_STRING]; hr = IEGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, SIZECHARS(szPath), NULL); TraceMsg(AC_GENERAL, "ACListISF::Initialize(%s), SetCurrentWorking Directory happening", szPath); #endif // DEBUG hr = _pshuLocation->SetCurrentWorkingDir(pidl); SetDefaultShellPath(_pshuLocation); } Pidl_Set(&_pidlCWD, pidl); return hr; } HRESULT CACLIShellFolder::GetClassID(CLSID *pclsid) { *pclsid = CLSID_ACListISF; return S_OK; } /* IEnumString methods */ HRESULT CACLIShellFolder::Reset(void) { HRESULT hr; LPITEMIDLIST pidl = NULL; TraceMsg(AC_GENERAL, "ACListISF::Reset()"); _fExpand = FALSE; _nPathIndex = 0; hr = _Init(); // See if we should show hidden files SHELLSTATE ss; ss.fShowAllObjects = FALSE; SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS /*| SSF_SHOWSYSFILES*/, FALSE); _fShowHidden = BOOLIFY(ss.fShowAllObjects); // _fShowSysFiles = BOOLIFY(ss.fShowSysFiles); if (SUCCEEDED(hr) && IsFlagSet(_dwOptions, ACLO_CURRENTDIR)) { // Set the Browser's Current Directory. if (_pbs) { _pbs->GetPidl(&pidl); if (pidl) Initialize(pidl); } hr = _SetLocation(pidl); if (FAILED(hr)) hr = S_FALSE; // If we failed, keep going, we will just end up now doing anything. ILFree(pidl); } return hr; } // If this is an FTP URL, skip it if: // 1) It's absolute (has a FTP scheme), and // 2) it contains a '/' after the server name. BOOL CACLIShellFolder::_SkipForPerf(LPCWSTR pwzExpand) { BOOL fSkip = FALSE; if ((URL_SCHEME_FTP == GetUrlScheme(pwzExpand))) { // If it's FTP, we want to prevent from hitting the server until // after the user has finished AutoCompleting the Server name. // Since we can't enum server names, the server names will need // to come from the MRU. if ((7 >= lstrlen(pwzExpand)) || // There's more than 7 chars "ftp://" (NULL == StrChr(&(pwzExpand[7]), TEXT('/')))) // There is a '/' after the server, "ftp://serv/" { fSkip = TRUE; } } return fSkip; } /* IACList methods */ /****************************************************\ FUNCTION: Expand DESCRIPTION: This function will attempt to use the pszExpand parameter to bind to a location in the Shell Name Space. If that succeeds, this AutoComplete List will then contain entries which are the display names in that ISF. \****************************************************/ HRESULT CACLIShellFolder::Expand(LPCOLESTR pszExpand) { HRESULT hr = S_OK; LPITEMIDLIST pidl = NULL; DWORD dwParseFlags = SHURL_FLAGS_NOUI; TraceMsg(AC_GENERAL, "ACListISF::Expand(%ls)", pszExpand); _fExpand = FALSE; _nPathIndex = 0; hr = StringCchCopy( _szExpandStr, ARRAYSIZE(_szExpandStr), pszExpand); if (SUCCEEDED(hr)) { if (_SkipForPerf(pszExpand)) // Do we want to skip this item for perf reasons? { hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); } else { hr = _Init(); } } if (FAILED(hr)) return hr; // See if the string points to a location in the Shell Name Space hr = _pshuLocation->ParseFromOutsideSource(_szExpandStr, dwParseFlags); if (SUCCEEDED(hr)) { // Yes it did, so now AutoComplete from that ISF hr = _pshuLocation->GetPidl(&pidl); // This may fail if it's something like "ftp:/" and not yet valid". DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];) TraceMsg(AC_GENERAL, "ACListISF::Expand() Pidl=>%s<", Dbg_PidlStr(pidl, szDbgBuffer, SIZECHARS(szDbgBuffer))); } // Set the ISF that we need to enumerate for AutoComplete. hr = _SetLocation(pidl); if (pidl) { ILFree(pidl); if (SUCCEEDED(hr)) { _fExpand = TRUE; } } return hr; } /* IACList2 methods */ //+------------------------------------------------------------------------- // Enables/disables various autocomplete features (see ACLO_* flags) //-------------------------------------------------------------------------- HRESULT CACLIShellFolder::SetOptions(DWORD dwOptions) { _dwOptions = dwOptions; return S_OK; } //+------------------------------------------------------------------------- // Returns the current option settings //-------------------------------------------------------------------------- HRESULT CACLIShellFolder::GetOptions(DWORD* pdwOptions) { HRESULT hr = E_INVALIDARG; if (pdwOptions) { *pdwOptions = _dwOptions; hr = S_OK; } return hr; } HRESULT CACLIShellFolder::_GetNextWrapper(LPWSTR pszName, DWORD cchSize) { HRESULT hr = S_OK; LPITEMIDLIST pidl = NULL; ULONG celtFetched = 0; // If this directory (ISF) doesn't contain any more items to enum, // then go on to the next directory (ISF) to enum. do { BOOL fFilter; do { fFilter = FALSE; hr = _peidl->Next(1, &pidl, &celtFetched); if (S_OK == hr) { hr = _PassesFilter(pidl, pszName, cchSize); if (FAILED(hr)) { fFilter = TRUE; } ILFree(pidl); } } while (fFilter); } while ((S_OK != hr) && (S_OK == _TryNextPath())); return hr; } HRESULT CACLIShellFolder::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) { HRESULT hr = S_OK; LPITEMIDLIST pidl; ULONG celtFetched; BOOL fUsingCachePidl = FALSE; WCHAR szDisplayName[MAX_PATH]; *pceltFetched = 0; if (!celt) return S_OK; // If there isn't a Current Working Directory, skip to another // Path to enum. if (!_peidl) hr = _TryNextPath(); if ((!_peidl) || (!rgelt)) return S_FALSE; // Get the next PIDL. if (_pidlInFolder) { // We have a cached, SHGDN_INFOLDER, so lets try that. pidl = _pidlInFolder; celtFetched = 1; _pidlInFolder = NULL; fUsingCachePidl = TRUE; hr = _GetPidlName(pidl, fUsingCachePidl, szDisplayName, ARRAYSIZE(szDisplayName)); ILFree(pidl); 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.")); } else { hr = _GetNextWrapper(szDisplayName, ARRAYSIZE(szDisplayName)); } // This is giving us entries (favorites without .url extension) that cannot be navigated to. // So I'm disabling this for IE5B2. (stevepro) // // else // Pidl_Set(&_pidlInFolder, pidl); // We will try (SHGDN_INFOLDER) next time. if (SUCCEEDED(hr)) { LPOLESTR pwszPath; // Allocate a return buffer (caller will free it). DWORD cch = lstrlenW(szDisplayName) + 1; pwszPath = (LPOLESTR)CoTaskMemAlloc(cch * SIZEOF(WCHAR)); if (pwszPath) { StringCchCopy(pwszPath, cch, szDisplayName); TraceMsg(AC_GENERAL, "ACListISF::Next() Str=%s, _nPathIndex=%d", pwszPath, _nPathIndex); if (SUCCEEDED(hr)) { rgelt[0] = pwszPath; *pceltFetched = 1; } } else { hr = E_OUTOFMEMORY; } } return hr; } HRESULT CACLIShellFolder::_GetPidlName(LPCITEMIDLIST pidl, BOOL fUsingCachePidl, LPWSTR pszName, DWORD cchSize) { HRESULT hr = S_OK; WCHAR szName[MAX_PATH]; // Get the display name of the PIDL. if (!fUsingCachePidl) { hr = DisplayNameOf(_psf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING | SHGDN_FORADDRESSBAR, szName, ARRAYSIZE(szName)); // some namespaces don't understand _FORADDRESSBAR -- default to IE4 behavior if (FAILED(hr)) hr = DisplayNameOf(_psf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING, szName, ARRAYSIZE(szName)); } if (fUsingCachePidl || FAILED(hr)) { hr = DisplayNameOf(_psf, pidl, SHGDN_INFOLDER | SHGDN_FORADDRESSBAR, szName, ARRAYSIZE(szName)); // some namespaces don't understand _FORADDRESSBAR -- default to IE4 behavior if (FAILED(hr)) hr = DisplayNameOf(_psf, pidl, SHGDN_INFOLDER, szName, ARRAYSIZE(szName)); } if (SUCCEEDED(hr)) { pszName[0] = 0; // Init the out buffer. // First, prepend the _szExpandStr if necessary. // This is needed for sections that don't give // the entire path, like "My Computer" items // which is (3 == _nPathIndex) if (_fExpand && ((_nPathIndex == 0) /*|| (_nPathIndex == 3)*/)) { DWORD cchExpand = lstrlen(_szExpandStr); // Make sure that for UNC paths the "\\share" is not already // prepended. NT5 returns the name in this final form. if ((StrCmpNI(szName, _szExpandStr, cchExpand) != 0) || (szName[0] != L'\\') || (szName[1] != L'\\')) { hr = StringCchCopy(pszName, cchSize, _szExpandStr); } } if(SUCCEEDED(hr)) { // Next, append the display name. hr = StringCchCat(pszName, cchSize, szName); TraceMsg(AC_GENERAL, "ACListISF::_GetPidlName() Str=%s, _nPathIndex=%d", szName, _nPathIndex); } } return hr; } HRESULT CACLIShellFolder::_PassesFilter(LPCITEMIDLIST pidl, LPWSTR pszName, DWORD cchSize) { HRESULT hr = S_OK; DWORD dwAttributes = (SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM); hr = _GetPidlName(pidl, FALSE, pszName, cchSize); if (SUCCEEDED(hr)) { if (((ACLO_FILESYSONLY & _dwOptions) || (ACLO_FILESYSDIRS & _dwOptions)) && SUCCEEDED(_psf->GetAttributesOf(1, (LPCITEMIDLIST *) &pidl, &dwAttributes))) { if (!(dwAttributes & (SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM))) { // We reject it because it's not in the file system. hr = E_FAIL; // Skip this item. TraceMsg(AC_GENERAL, "ACListISF::_PassesFilter() We are skipping\"%s\" because it doesn't match the filter", pszName); } else { if ((ACLO_FILESYSDIRS & _dwOptions) && !PathIsDirectory(pszName)) { hr = E_FAIL; // Skip this item since it's not a directory } } } } return hr; } HRESULT CACLIShellFolder::_Init(void) { HRESULT hr = S_OK; if (!_pshuLocation) { _pshuLocation = new CShellUrl(); if (!_pshuLocation) return E_OUTOFMEMORY; } return hr; } HRESULT CACLIShellFolder::_SetLocation(LPCITEMIDLIST pidl) { HRESULT hr; // Free old location ATOMICRELEASE(_peidl); ATOMICRELEASE(_psf); // Set to new location (Valid if NULL) Pidl_Set(&_pidl, pidl); if (_pidl) { hr = IEBindToObject(_pidl, &_psf); if (SUCCEEDED(hr)) { DWORD grfFlags = (_fShowHidden ? SHCONTF_INCLUDEHIDDEN : 0) | SHCONTF_FOLDERS | SHCONTF_NONFOLDERS; hr = IShellFolder_EnumObjects(_psf, NULL, grfFlags, &_peidl); if (hr != S_OK) { hr = E_FAIL; // S_FALSE -> empty enumerator } } } else hr = E_OUTOFMEMORY; if (FAILED(hr)) { // Clear if we could not get all the info ATOMICRELEASE(_peidl); ATOMICRELEASE(_psf); Pidl_Set(&_pidl, NULL); } // // NOTE: This is necessary because this memory is alloced in a ACBG thread, but not // freed until the next call to Reset() or the destructor, which will // happen in the main thread or another ACBG thread. // return hr; } HRESULT CACLIShellFolder::_TryNextPath(void) { HRESULT hr = S_FALSE; if (0 == _nPathIndex) { _nPathIndex = 1; if (_pidlCWD && IsFlagSet(_dwOptions, ACLO_CURRENTDIR)) { hr = _SetLocation(_pidlCWD); if (SUCCEEDED(hr)) { goto done; } } } if (1 == _nPathIndex) { _nPathIndex = 2; if(IsFlagSet(_dwOptions, ACLO_DESKTOP)) { // we used to autocomplete g_pidlRoot in the rooted explorer // case, but this was a little weird. if we want to add this, // we should add ACLO_ROOTED or something. // use the desktop... hr = _SetLocation(&s_idlNULL); if (SUCCEEDED(hr)) { goto done; } } } if (2 == _nPathIndex) { _nPathIndex = 3; if (IsFlagSet(_dwOptions, ACLO_MYCOMPUTER)) { LPITEMIDLIST pidlMyComputer; if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &pidlMyComputer))) { hr = _SetLocation(pidlMyComputer); ILFree(pidlMyComputer); if (SUCCEEDED(hr)) { goto done; } } } } // Also search favorites if (3 == _nPathIndex) { _nPathIndex = 4; if (IsFlagSet(_dwOptions, ACLO_FAVORITES)) { LPITEMIDLIST pidlFavorites; if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidlFavorites))) { hr = _SetLocation(pidlFavorites); ILFree(pidlFavorites); if (SUCCEEDED(hr)) { goto done; } } } } if (FAILED(hr)) hr = S_FALSE; // This is how we want our errors returned. done: return hr; } //================================ // *** IShellService Interface *** /****************************************************\ FUNCTION: SetOwner DESCRIPTION: Update the connection to the Browser window so we can always get the PIDL of the current location. \****************************************************/ HRESULT CACLIShellFolder::SetOwner(IUnknown* punkOwner) { HRESULT hr = S_OK; IBrowserService * pbs = NULL; ATOMICRELEASE(_pbs); if (punkOwner) hr = punkOwner->QueryInterface(IID_IBrowserService, (LPVOID *) &pbs); if (EVAL(SUCCEEDED(hr))) _pbs = pbs; return S_OK; } /* Constructor / Destructor / CreateInstance */ CACLIShellFolder::CACLIShellFolder() { DllAddRef(); ASSERT(!_peidl); ASSERT(!_psf); ASSERT(!_pbs); ASSERT(!_pidl); ASSERT(!_pidlCWD); ASSERT(!_fExpand); ASSERT(!_pshuLocation); ASSERT(0==_szExpandStr[0]); _cRef = 1; // Default search paths _dwOptions = ACLO_CURRENTDIR | ACLO_MYCOMPUTER; } CACLIShellFolder::~CACLIShellFolder() { ATOMICRELEASE(_peidl); ATOMICRELEASE(_psf); ATOMICRELEASE(_pbs); Pidl_Set(&_pidl, NULL); Pidl_Set(&_pidlCWD, NULL); Pidl_Set(&_pidlInFolder, NULL); if (_pshuLocation) delete _pshuLocation; DllRelease(); } HRESULT CACLIShellFolder_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi) { *ppunk = NULL; CACLIShellFolder *paclSF = new CACLIShellFolder(); if (paclSF) { *ppunk = SAFECAST(paclSF, IEnumString *); return S_OK; } return E_OUTOFMEMORY; }