#include "priv.h" #include "sccls.h" #include "nscband.h" #include "resource.h" #include "uemapp.h" // KMTF: Included for instrumentation #include "shlguid.h" #include #include #include "varutil.h" #include "apithk.h" #define TF_EXPLORERBAND 0 typedef struct { LPITEMIDLIST pidl; IShellFolder *psf; } SFCITEM; class CExplorerBand : public CNSCBand, public IDispatch { public: // *** IUnknown *** STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj); STDMETHODIMP_(ULONG) AddRef(void) { return CNSCBand::AddRef(); }; STDMETHODIMP_(ULONG) Release(void) { return CNSCBand::Release(); }; // *** IOleCommandTarget methods *** STDMETHODIMP QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pcmdtext); STDMETHODIMP Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut); // *** IDockingWindow methods *** STDMETHODIMP CloseDW(DWORD dw); STDMETHODIMP ShowDW(BOOL fShow); // *** IObjectWithSite methods *** STDMETHODIMP SetSite(IUnknown* punkSite); // *** INamespaceProxy methods *** STDMETHODIMP Invoke(LPCITEMIDLIST pidl); STDMETHODIMP OnSelectionChanged(LPCITEMIDLIST pidl); STDMETHODIMP CacheItem(LPCITEMIDLIST pidl) {_MaybeAddToLegacySFC(pidl); return S_OK;} // *** IDispatch methods *** STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) {return E_NOTIMPL;} STDMETHODIMP GetTypeInfo(UINT itinfo,LCID lcid,ITypeInfo **pptinfo) {return E_NOTIMPL;} STDMETHODIMP GetIDsOfNames(REFIID riid,OLECHAR **rgszNames,UINT cNames, LCID lcid, DISPID * rgdispid) {return E_NOTIMPL;} STDMETHODIMP Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, EXCEPINFO *pexcepinfo, UINT *puArgErr); protected: CExplorerBand() : _fCanSelect(TRUE), _fIgnoreSelection(TRUE) {} virtual ~CExplorerBand(); virtual HRESULT _TranslatePidl(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlTarget, ULONG *pulAttrib); virtual BOOL _ShouldNavigateToPidl(LPCITEMIDLIST pidl, ULONG ulAttrib); virtual HRESULT _InitializeNsc(); virtual DWORD _GetTVStyle(); virtual DWORD _GetTVExStyle(); virtual DWORD _GetEnumFlags(); void _MaybeAddToLegacySFC(LPCITEMIDLIST pidl); void _AddToLegacySFC(LPCITEMIDLIST pidl, IShellFolder *psf); BOOL _IsInSFC(LPCITEMIDLIST pidl); BOOL _IsFloppy(LPCITEMIDLIST pidl); void _OnNavigate(); HRESULT _ConnectToBrowser(BOOL fConnect); HRESULT _BrowserExec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut); friend HRESULT CExplorerBand_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi); static void s_DVEnumReadyCallback(void *pvData); CDSA *_pdsaLegacySFC; DWORD _dwcpCookie; LPITEMIDLIST _pidlView; //pidl view is navigated to BOOL _fCanSelect; BOOL _fIgnoreSelection; //so we don't navigate away from the web page when user opens explorer pane BOOL _fFloppyRefresh; }; HRESULT CExplorerBand::_BrowserExec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut) { return IUnknown_QueryServiceExec(_punkSite, SID_STopLevelBrowser, pguidCmdGroup, nCmdID, nCmdexecopt, pvarargIn, pvarargOut); } HRESULT _UnwrapRootedPidl(LPCITEMIDLIST pidlRooted, BOOL bOnlyIfRooted, LPITEMIDLIST *ppidl) { HRESULT hr = E_FAIL; if (ILIsRooted(pidlRooted)) { hr = SHILCombine(ILRootedFindIDList(pidlRooted), _ILNext(pidlRooted), ppidl); } else if (!bOnlyIfRooted) { hr = SHILClone(pidlRooted, ppidl); } return hr; } BOOL IsFTPPidl(LPCITEMIDLIST pidl) { BOOL fIsFTP = FALSE; IShellFolder * psf; if (pidl && SUCCEEDED(IEBindToObject(pidl, &psf))) { fIsFTP = IsFTPFolder(psf); psf->Release(); } return fIsFTP; } void CExplorerBand::_OnNavigate() { IBrowserService* pbs; HRESULT hr = IUnknown_QueryService(_punkSite, SID_STopLevelBrowser, IID_PPV_ARG(IBrowserService, &pbs)); if (SUCCEEDED(hr)) { LPITEMIDLIST pidl; hr = pbs->GetPidl(&pidl); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlNew; hr = _UnwrapRootedPidl(pidl, FALSE, &pidlNew); if (SUCCEEDED(hr)) { // We must go in this code path if the pidl is an FTP pidl. FTP pidls can contain // passwords so it needs to replace any existing pidl. Whistler #252206. if (!_pidlView || !ILIsEqual(pidlNew, _pidlView) || IsFTPPidl(pidlNew)) { DWORD dwAttributes = SFGAO_FOLDER; // only let folders go through (to filter out Web pages) hr = IEGetAttributesOf(pidlNew, &dwAttributes); if (SUCCEEDED(hr) && (dwAttributes & SFGAO_FOLDER)) { BOOL fExpand = (_pidlView == NULL); //the very first time we expand the folder the view is navigated to Pidl_Set(&_pidlView, pidlNew); _fIgnoreSelection = FALSE; //in the web page case we don't come here because the page does not have folder attribute if (_fCanSelect) { if (fExpand) { VARIANT var; hr = InitVariantFromIDList(&var, _pidlView); if (SUCCEEDED(hr)) { IShellNameSpace *psns; hr = _pns->QueryInterface(IID_PPV_ARG(IShellNameSpace, &psns)); if (SUCCEEDED(hr)) { psns->Expand(var, 1); psns->Release(); } VariantClear(&var); } } else { _pns->SetSelectedItem(_pidlView, TRUE, FALSE, 0); } } } } // view navigation is asynchronous so we don't know if it failed in OnSelectionChanged // but the view is getting navigated to the old pidl and _fCanSelect is false (which happens after we try // to navigate the view) so it is safe to assume that navigation failed. // we need to update the selection to match the view else if (ILIsEqual(pidlNew, _pidlView) && !_fCanSelect) { _pns->SetSelectedItem(_pidlView, TRUE, FALSE, 0); } _fCanSelect = TRUE; ILFree(pidlNew); } ILFree(pidl); } pbs->Release(); } if (FAILED(hr)) { Pidl_Set(&_pidlView, NULL); } } HRESULT CExplorerBand::Invoke(DISPID dispidMember, REFIID riid,LCID lcid, WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, EXCEPINFO *pexcepinfo, UINT *puArgErr) { HRESULT hr = S_OK; if (!pdispparams) return E_INVALIDARG; switch(dispidMember) { case DISPID_NAVIGATECOMPLETE2: case DISPID_DOCUMENTCOMPLETE: { BOOL fCallNavigateFinished = TRUE; IDVGetEnum *pdvge; if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IDVGetEnum, &pdvge)))) { // callback will call it fCallNavigateFinished = FALSE; if (dispidMember == DISPID_NAVIGATECOMPLETE2) pdvge->SetEnumReadyCallback(s_DVEnumReadyCallback, this); pdvge->Release(); } _OnNavigate(); if (fCallNavigateFinished && DISPID_DOCUMENTCOMPLETE == dispidMember) { // need to let nsc know the navigation finished in case we navigated to a 3rd party namespace extension (w/ its own view impl) // because it does not implement IDVGetEnum, hence s_DVEnumReadyCallback will not get called LPITEMIDLIST pidlClone = ILClone(_pidlView); // should we unwrap this pidl if rooted? if (pidlClone) _pns->RightPaneNavigationFinished(pidlClone); // takes ownership } } break; default: hr = E_INVALIDARG; break; } return hr; } void CExplorerBand::s_DVEnumReadyCallback(void *pvData) { CExplorerBand *peb = (CExplorerBand *) pvData; IBrowserService* pbs; if (SUCCEEDED(IUnknown_QueryService(peb->_punkSite, SID_STopLevelBrowser, IID_PPV_ARG(IBrowserService, &pbs)))) { LPITEMIDLIST pidlTemp; if (SUCCEEDED(pbs->GetPidl(&pidlTemp))) { LPITEMIDLIST pidl; if (SUCCEEDED(_UnwrapRootedPidl(pidlTemp, FALSE, &pidl))) { peb->_pns->RightPaneNavigationFinished(pidl); // takes ownership } ILFree(pidlTemp); } pbs->Release(); } } const TCHAR c_szLink[] = TEXT("link"); const TCHAR c_szRename[] = TEXT("rename"); const TCHAR c_szMove[] = TEXT("cut"); const TCHAR c_szPaste[] = TEXT("paste"); const TCHAR c_szCopy[] = TEXT("copy"); const TCHAR c_szDelete[] = TEXT("delete"); const TCHAR c_szProperties[] = TEXT("properties"); // IOleCommandTarget HRESULT CExplorerBand::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pcmdtext) { if (pguidCmdGroup == NULL) { IContextMenu *pcm = NULL; HRESULT hr = _QueryContextMenuSelection(&pcm); if (SUCCEEDED(hr)) { HMENU hmenu = CreatePopupMenu(); if (hmenu) { hr = pcm->QueryContextMenu(hmenu, 0, 0, 255, 0); if (SUCCEEDED(hr)) { UINT ilast = GetMenuItemCount(hmenu); for (UINT ipos=0; ipos < ilast; ipos++) { MENUITEMINFO mii = {0}; TCHAR szVerb[40]; UINT idCmd; mii.cbSize = SIZEOF(MENUITEMINFO); mii.fMask = MIIM_ID|MIIM_STATE; if (!GetMenuItemInfoWrap(hmenu, ipos, TRUE, &mii)) continue; if (0 != (mii.fState & (MF_GRAYED|MF_DISABLED))) continue; idCmd = mii.wID; hr = ContextMenu_GetCommandStringVerb(pcm, idCmd, szVerb, ARRAYSIZE(szVerb)); if (SUCCEEDED(hr)) { LPCTSTR szCmd = NULL; for (ULONG cItem = 0; cItem < cCmds; cItem++) { switch (rgCmds[cItem].cmdID) { case OLECMDID_CUT: szCmd = c_szMove; break; case OLECMDID_COPY: szCmd = c_szCopy; break; case OLECMDID_PASTE: szCmd = c_szPaste; break; case OLECMDID_DELETE: szCmd = c_szDelete; break; case OLECMDID_PROPERTIES: szCmd = c_szProperties; break; } if (StrCmpI(szVerb, szCmd)==0) { rgCmds[cItem].cmdf = OLECMDF_ENABLED; } } } } } DestroyMenu(hmenu); } else { hr = E_FAIL; } pcm->Release(); } if (SUCCEEDED(hr)) return hr; } return CNSCBand::QueryStatus(pguidCmdGroup, cCmds, rgCmds, pcmdtext); } HRESULT CExplorerBand::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut) { if (pguidCmdGroup == NULL) { HRESULT hr; switch(nCmdID) { case OLECMDID_CUT: hr = _InvokeCommandOnItem(c_szMove); break; case OLECMDID_COPY: hr = _InvokeCommandOnItem(c_szCopy); break; case OLECMDID_PASTE: hr = _InvokeCommandOnItem(c_szPaste); break; case OLECMDID_DELETE: hr = _InvokeCommandOnItem(c_szDelete); break; case OLECMDID_PROPERTIES: hr = _InvokeCommandOnItem(c_szProperties); break; default: hr = E_FAIL; break; } if (SUCCEEDED(hr)) return hr; } return CNSCBand::Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvarargIn, pvarargOut); } // IDockingWindow HRESULT CExplorerBand::CloseDW(DWORD dw) { _ConnectToBrowser(FALSE); return CNSCBand::CloseDW(dw); } HRESULT CExplorerBand::ShowDW(BOOL fShow) { return CNSCBand::ShowDW(fShow); } // IObjectWithSite HRESULT CExplorerBand::SetSite(IUnknown* punkSite) { HRESULT hr = CNSCBand::SetSite(punkSite); if (punkSite) _ConnectToBrowser(TRUE); return hr; } int _SFCDestroyCB(SFCITEM *psfcItem, void *pv) { psfcItem->psf->Release(); ILFree(psfcItem->pidl); return 1; } CExplorerBand::~CExplorerBand() { ILFree(_pidlView); if (_pdsaLegacySFC) { _pdsaLegacySFC->DestroyCallback(_SFCDestroyCB, NULL); delete _pdsaLegacySFC; } } HRESULT CExplorerBand::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CExplorerBand, IDispatch), { 0 }, }; HRESULT hr = QISearch(this, qit, riid, ppvObj); if (FAILED(hr)) hr = CNSCBand::QueryInterface(riid, ppvObj); return hr; } DWORD CExplorerBand::_GetEnumFlags() { DWORD dwFlags = SHCONTF_FOLDERS; SHELLSTATE ss = {0}; SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS, FALSE); if (ss.fShowAllObjects) dwFlags |= SHCONTF_INCLUDEHIDDEN; return dwFlags; } DWORD CExplorerBand::_GetTVExStyle() { DWORD dwExStyle = 0; if (IsOS(OS_WHISTLERORGREATER) && SHRegGetBoolUSValue(TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"), TEXT("FriendlyTree"), FALSE, TRUE)) { dwExStyle |= TVS_EX_NOSINGLECOLLAPSE; } return dwExStyle; } DWORD CExplorerBand::_GetTVStyle() { DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS | WS_TABSTOP | WS_HSCROLL | TVS_EDITLABELS | TVS_SHOWSELALWAYS; if (IsOS(OS_WHISTLERORGREATER) && SHRegGetBoolUSValue(TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"), TEXT("FriendlyTree"), FALSE, TRUE)) { dwStyle |= TVS_HASBUTTONS | TVS_SINGLEEXPAND | TVS_TRACKSELECT; } else { dwStyle |= TVS_HASBUTTONS | TVS_HASLINES; } // If the parent window is mirrored then the treeview window will inheret the mirroring flag // And we need the reading order to be Left to right, which is the right to left in the mirrored mode. if (_hwndParent && IS_WINDOW_RTL_MIRRORED(_hwndParent)) { // This means left to right reading order because this window will be mirrored. _dwStyle |= TVS_RTLREADING; } return dwStyle; } HRESULT CExplorerBand_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi) { // aggregation checking is handled in class factory CExplorerBand * peb = new CExplorerBand(); if (!peb) return E_OUTOFMEMORY; if (SUCCEEDED(peb->_Init((LPCITEMIDLIST)CSIDL_DESKTOP))) { peb->_pns = CNscTree_CreateInstance(); if (peb->_pns) { ASSERT(poi); peb->_poi = poi; // if you change this cast, fix up CFavBand_CreateInstance *ppunk = SAFECAST(peb, IDeskBand *); IUnknown_SetSite(peb->_pns, *ppunk); peb->_SetNscMode(MODE_NORMAL); return S_OK; } } peb->Release(); return E_FAIL; } HRESULT CExplorerBand::_ConnectToBrowser(BOOL fConnect) { IBrowserService* pbs; HRESULT hr = IUnknown_QueryService(_punkSite, SID_STopLevelBrowser, IID_PPV_ARG(IBrowserService, &pbs)); if (SUCCEEDED(hr)) { if (fConnect) { LPITEMIDLIST pidlTemp = NULL; // try to get the pidl the browser is navigated to // this usually fails if user just opened Explorer window because navigation is asynchronous // so we're not initialized yet if (FAILED(pbs->GetPidl(&pidlTemp))) { IBrowserService2 *pbs2; if (SUCCEEDED(pbs->QueryInterface(IID_PPV_ARG(IBrowserService2, &pbs2)))) { LPCBASEBROWSERDATA pbbd; // our last hope is the pidl browser is navigating to... if (SUCCEEDED(pbs2->GetBaseBrowserData(&pbbd)) && pbbd->_pidlPending) { pidlTemp = ILClone(pbbd->_pidlPending); } pbs2->Release(); } } if (pidlTemp) { LPITEMIDLIST pidl; // see if we're dealing with a rooted namespace if (SUCCEEDED(_UnwrapRootedPidl(pidlTemp, TRUE, &pidl))) { _Init(pidl); //if so, reinitialize ourself with the rooted pidl ILFree(pidl); } ILFree(pidlTemp); } } IConnectionPointContainer* pcpc; hr = IUnknown_QueryService(pbs, SID_SWebBrowserApp, IID_PPV_ARG(IConnectionPointContainer, &pcpc)); // Let's now have the Browser Window give us notification when something happens. if (SUCCEEDED(hr)) { hr = ConnectToConnectionPoint(SAFECAST(this, IDispatch*), DIID_DWebBrowserEvents2, fConnect, pcpc, &_dwcpCookie, NULL); pcpc->Release(); } pbs->Release(); } ASSERT(SUCCEEDED(hr)); return hr; } HRESULT CExplorerBand::_InitializeNsc() { HRESULT hr = _pns->Initialize(_pidl, _GetEnumFlags(), NSS_DROPTARGET | NSS_BROWSERSELECT); if (SUCCEEDED(hr)) _OnNavigate(); return hr; } HRESULT CExplorerBand::_TranslatePidl(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlTarget, ULONG *pulAttrib) { HRESULT hr = E_INVALIDARG; if (pidl && ppidlTarget && pulAttrib) { hr = IEGetAttributesOf(pidl, pulAttrib); if (SUCCEEDED(hr)) { hr = SHILClone(pidl, ppidlTarget); } } return hr; } BOOL CExplorerBand::_ShouldNavigateToPidl(LPCITEMIDLIST pidl, ULONG ulAttrib) { return ulAttrib & SFGAO_FOLDER; } BOOL CExplorerBand::_IsFloppy(LPCITEMIDLIST pidl) { BOOL fRet = FALSE; WCHAR szPath[MAX_PATH]; if (SUCCEEDED(SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL))) { if (DRIVE_REMOVABLE == GetDriveType(szPath)) { fRet = (L'A' == szPath[0] || L'B' == szPath[0] || L'a' == szPath[0] || L'b' == szPath[0]); } } return fRet; } HRESULT CExplorerBand::Invoke(LPCITEMIDLIST pidl) { HRESULT hr; // allow user to navigate to an already selected item if they opened Explorer band in Web browser // (because we put selection on the root node but don't navigate away from the web page, if they click // on the root we don't navigate there, because selection never changed) if (!_pidlView) { _fIgnoreSelection = FALSE; hr = OnSelectionChanged(pidl); } else if (ILIsEqual(pidl, _pidlView) && _IsFloppy(pidl)) { // If the drive is a floppy and the user reselects the drive refresh the contents. This enables // a user to refresh when a floppy is replaced. _fFloppyRefresh = TRUE; hr = OnSelectionChanged(pidl); _fFloppyRefresh = FALSE; } else { hr = S_OK; } return hr; } HRESULT CExplorerBand::OnSelectionChanged(LPCITEMIDLIST pidl) { HRESULT hr = E_INVALIDARG; if (!_fIgnoreSelection) { if (pidl) { ULONG ulAttrib = SFGAO_FOLDER; LPITEMIDLIST pidlTarget; hr = GetNavigateTarget(pidl, &pidlTarget, &ulAttrib); if (hr == S_OK) { if (!_pidlView || _fFloppyRefresh || !ILIsEqual(pidlTarget, _pidlView)) { hr = CNSCBand::Invoke(pidlTarget); if (SUCCEEDED(hr)) _fCanSelect = FALSE; _pns->RightPaneNavigationStarted(pidlTarget); pidlTarget = NULL; // ownership passed } ILFree(pidlTarget); } #ifdef DEBUG else if (hr == S_FALSE) { ASSERT(pidlTarget == NULL); } #endif } } else { _fIgnoreSelection = FALSE; //we ignore only first selection } return hr; } void CExplorerBand::_MaybeAddToLegacySFC(LPCITEMIDLIST pidl) { IShellFolder *psf = NULL; if (pidl && SUCCEEDED(SHBindToObjectEx(NULL, pidl, NULL, IID_PPV_ARG(IShellFolder, &psf)))) { // // APPCOMPAT LEGACY - Compatibility. needs the Shell folder cache, - ZekeL - 4-MAY-99 // some apps, specifically WS_FTP and AECO Zip Pro, // rely on having a shellfolder existing in order for them to work. // we pulled the SFC because it wasnt any perf win. // if (OBJCOMPATF_OTNEEDSSFCACHE & SHGetObjectCompatFlags(psf, NULL)) _AddToLegacySFC(pidl, psf); psf->Release(); } } BOOL CExplorerBand::_IsInSFC(LPCITEMIDLIST pidl) { BOOL bReturn = FALSE; ASSERT(_pdsaLegacySFC); for (int i=0; i<_pdsaLegacySFC->GetItemCount(); i++) { SFCITEM *psfcItem = _pdsaLegacySFC->GetItemPtr(i); if (ILIsEqual(psfcItem->pidl, pidl)) { bReturn = TRUE; break; } } return bReturn; } void CExplorerBand::_AddToLegacySFC(LPCITEMIDLIST pidl, IShellFolder *psf) { if (!_pdsaLegacySFC) { _pdsaLegacySFC = new CDSA; if (_pdsaLegacySFC && !_pdsaLegacySFC->Create(4)) { delete _pdsaLegacySFC; _pdsaLegacySFC = NULL; } } if (_pdsaLegacySFC) { LPITEMIDLIST pidlCache; if (!_IsInSFC(pidl) && SUCCEEDED(SHILClone(pidl, &pidlCache))) { SFCITEM sfc = {pidlCache, psf}; if (-1 != _pdsaLegacySFC->InsertItem(0, &sfc)) psf->AddRef(); else ILFree(pidlCache); } } }