/* * PROJECT: ReactOS Explorer * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) * PURPOSE: The combo box of the address band * COPYRIGHT: Copyright 2009 Andrew Hill * Copyright 2023-2026 Katayama Hirofumi MZ */ #include "precomp.h" /* TODO: Add drag and drop of icon in edit box Handle change notifies to update appropriately */ CAddressEditBox::CAddressEditBox() : fCombobox(WC_COMBOBOXEXW, this), fEditWindow(WC_EDITW, this), fSite(NULL) { } CAddressEditBox::~CAddressEditBox() { } HRESULT STDMETHODCALLTYPE CAddressEditBox::SetOwner(IUnknown *pOwner) { if (!pOwner) { CComPtr browserService; HRESULT hResult = IUnknown_QueryService(fSite, SID_SShellBrowser, IID_PPV_ARG(IBrowserService, &browserService)); if (SUCCEEDED(hResult)) AtlUnadvise(browserService, DIID_DWebBrowserEvents, fAdviseCookie); fSite = NULL; } // connect to browser connection point return 0; } HRESULT STDMETHODCALLTYPE CAddressEditBox::FileSysChange(long param8, long paramC) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::Refresh(long param8) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::Init(HWND comboboxEx, HWND editControl, long param14, IUnknown *param18) { CComPtr browserService; fCombobox.SubclassWindow(comboboxEx); fEditWindow.SubclassWindow(editControl); fSite = param18; hComboBoxEx = comboboxEx; SHAutoComplete(fEditWindow.m_hWnd, SHACF_FILESYSTEM | SHACF_URLALL | SHACF_USETAB); // take advice to watch events HRESULT hResult = IUnknown_QueryService(param18, SID_SShellBrowser, IID_PPV_ARG(IBrowserService, &browserService)); if (SUCCEEDED(hResult)) { hResult = AtlAdvise(browserService, static_cast(this), DIID_DWebBrowserEvents, &fAdviseCookie); } return hResult; } HRESULT STDMETHODCALLTYPE CAddressEditBox::SetCurrentDir(PCWSTR pszPath) { m_pidlLastParsed.Free(); m_pidlLastParsed.Attach(ILCreateFromPathW(pszPath)); return m_pidlLastParsed ? S_OK : E_OUTOFMEMORY; } BOOL CAddressEditBox::GetComboBoxText(CComHeapPtr& pszText) { pszText.Free(); INT cchMax = fCombobox.GetWindowTextLength() + sizeof(UNICODE_NULL); if (!pszText.Allocate(cchMax)) return FALSE; return fCombobox.GetWindowText(pszText, cchMax); } HRESULT CAddressEditBox::RefreshAddress() { /* Get the current pidl of the browser */ CComHeapPtr absolutePIDL; HRESULT hr = GetAbsolutePidl(&absolutePIDL); if (FAILED_UNEXPECTEDLY(hr)) return hr; /* Fill the combobox */ ATLASSERT(absolutePIDL != NULL); PopulateComboBox(absolutePIDL); /* Get pShellFolder and pidlChild */ CComPtr pShellFolder; PCITEMID_CHILD pidlChild; hr = SHBindToParent(absolutePIDL, IID_PPV_ARG(IShellFolder, &pShellFolder), &pidlChild); if (FAILED_UNEXPECTEDLY(hr)) return hr; /* Get ready to set the displayed item */ COMBOBOXEXITEMW item = { CBEIF_IMAGE | CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_LPARAM }; item.iItem = -1; /* -1 to specify the displayed item */ item.iImage = SHMapPIDLToSystemImageListIndex(pShellFolder, pidlChild, &item.iSelectedImage); /* Set the path if filesystem; otherwise use the name */ WCHAR szPathOrName[MAX_PATH]; SHGDNF flags = SHGDN_FORADDRESSBAR; if (gCabinetState.fFullPathAddress) flags |= SHGDN_FORPARSING; if (SUCCEEDED(IEGetNameAndFlags(absolutePIDL, flags, szPathOrName, _countof(szPathOrName), NULL))) item.pszText = szPathOrName; /* Ownership of absolutePIDL will be moved to fCombobox. See CBEN_DELETEITEM */ item.lParam = reinterpret_cast(absolutePIDL.Detach()); fCombobox.SendMessage(CBEM_SETITEM, 0, reinterpret_cast(&item)); /* Set it! */ return S_OK; } HRESULT CAddressEditBox::GetAbsolutePidl(PIDLIST_ABSOLUTE *pAbsolutePIDL) { CComPtr isb; HRESULT hr = IUnknown_QueryService(fSite, SID_STopLevelBrowser, IID_PPV_ARG(IBrowserService, &isb)); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = isb->GetPidl(pAbsolutePIDL); if (FAILED_UNEXPECTEDLY(hr)) return hr; return S_OK; } /* Execute command line from address bar */ BOOL CAddressEditBox::ExecuteCommandLine() { /* Get command line */ CComHeapPtr pszCmdLine; if (!GetComboBoxText(pszCmdLine)) return FALSE; /* Split 1st parameter from trailing arguments */ PWCHAR args = PathGetArgsW(pszCmdLine); PathRemoveArgsW(pszCmdLine); PathUnquoteSpacesW(pszCmdLine); /* Unquote the 1st parameter */ /* Get ready for execution */ SHELLEXECUTEINFOW info = { sizeof(info), SEE_MASK_FLAG_NO_UI, m_hWnd }; info.lpFile = pszCmdLine; info.lpParameters = args; info.nShow = SW_SHOWNORMAL; /* Set current directory */ WCHAR dir[MAX_PATH] = L""; CComHeapPtr pidl; if (SUCCEEDED(GetAbsolutePidl(&pidl))) { if (SHGetPathFromIDListW(pidl, dir) && PathIsDirectoryW(dir)) info.lpDirectory = dir; } if (!::ShellExecuteExW(&info)) /* Execute! */ return FALSE; RefreshAddress(); return TRUE; } HRESULT STDMETHODCALLTYPE CAddressEditBox::ParseNow(long paramC) { ULONG eaten, attributes; CComHeapPtr pidlCurrent; CComHeapPtr pidlRelative; CComPtr psfCurrent; HRESULT hr; m_pidlLastParsed.Free(); CComPtr pbs; hr = IUnknown_QueryService(fSite, SID_SShellBrowser, IID_PPV_ARG(IBrowserService, &pbs)); if (FAILED_UNEXPECTEDLY(hr)) return hr; HWND topLevelWindow; hr = IUnknown_GetWindow(pbs, &topLevelWindow); if (FAILED_UNEXPECTEDLY(hr)) return hr; /* Get the path to browse and expand it if needed */ CComHeapPtr input, address; if (!GetComboBoxText(input)) return E_FAIL; INT addressLength = (wcschr(input, L'%') ? ::SHExpandEnvironmentStringsW(input, NULL, 0) : 0); if (addressLength <= 0 || !address.Allocate(addressLength + 1) || !::SHExpandEnvironmentStringsW(input, address, addressLength)) { address.Free(); address.Attach(input.Detach()); } /* Try to parse a relative path and if it fails, try to browse an absolute path */ CComPtr psfDesktop; hr = SHGetDesktopFolder(&psfDesktop); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pbs->GetPidl(&pidlCurrent); if (FAILED_UNEXPECTEDLY(hr)) goto parseabsolute; if (!pidlCurrent->mkid.cb) { psfCurrent = psfDesktop; } else { hr = psfDesktop->BindToObject(pidlCurrent, NULL, IID_PPV_ARG(IShellFolder, &psfCurrent)); if (FAILED_UNEXPECTEDLY(hr)) goto parseabsolute; ATLASSERT(address && address[0]); WCHAR szPath[MAX_PATH], szFullPath[MAX_PATH]; if (SHGetPathFromIDListW(pidlCurrent, szPath)) // File-system? { if (PathIsRelativeW(address)) // Relative path? { PathAppendW(szPath, address); if (GetFullPathNameW(szPath, _countof(szFullPath), szFullPath, NULL)) { address.Free(); SHStrDupW(szFullPath, &address); } } else if (address[0] == L'\\' && address[1] != L'\\') // Drive letter omitted? { // GetFullPathNameW won't resolve drive letter INT iDrive = PathGetDriveNumberW(szPath); if (iDrive >= 0) { PathBuildRootW(szPath, iDrive); PathAppendW(szPath, &address[1]); if (GetFullPathNameW(szPath, _countof(szFullPath), szFullPath, NULL)) { address.Free(); SHStrDupW(szFullPath, &address); } } } else if (StrChrW(address, L'.') && PathFileExistsW(address)) // Trailing relative path? { if (GetFullPathNameW(address, _countof(szFullPath), szFullPath, NULL)) { address.Free(); SHStrDupW(szFullPath, &address); } } } } hr = psfCurrent->ParseDisplayName(topLevelWindow, NULL, address, &eaten, &pidlRelative, &attributes); if (SUCCEEDED(hr)) { m_pidlLastParsed.Attach(ILCombine(pidlCurrent, pidlRelative)); return S_OK; } parseabsolute: /* We couldn't parse a relative path, attempt to parse an absolute path */ hr = psfDesktop->ParseDisplayName(topLevelWindow, NULL, address, &eaten, &m_pidlLastParsed, &attributes); return hr; } HRESULT STDMETHODCALLTYPE CAddressEditBox::ShowFileNotFoundError(HRESULT hRet) { CComHeapPtr input; if (!GetComboBoxText(input)) return E_FAIL; ShellMessageBoxW(_AtlBaseModule.GetResourceInstance(), fCombobox.m_hWnd, MAKEINTRESOURCEW(IDS_PARSE_ADDR_ERR_TEXT), MAKEINTRESOURCEW(IDS_PARSE_ADDR_ERR_TITLE), MB_OK | MB_ICONERROR, input.m_pData); return hRet; } HRESULT STDMETHODCALLTYPE CAddressEditBox::Execute(long paramC) { BOOL bParsedForExec = FALSE; HRESULT hr; /* * Parse the path if it wasn't parsed */ if (!m_pidlLastParsed) { hr = ParseNow(0); /* If the destination path doesn't exist then display an error message */ if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { if (ExecuteCommandLine()) return S_OK; return ShowFileNotFoundError(hr); } if (!m_pidlLastParsed) return E_FAIL; bParsedForExec = TRUE; } /* * Get the IShellBrowser and IBrowserService interfaces of the shell browser */ CComPtr pisb; hr = IUnknown_QueryService(fSite, SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &pisb)); if (FAILED_UNEXPECTEDLY(hr)) return hr; /* * Get the current pidl of the shellbrowser and check if it is the same with the parsed one */ PIDLIST_ABSOLUTE pidl; hr = GetAbsolutePidl(&pidl); if (FAILED_UNEXPECTEDLY(hr)) { m_pidlLastParsed.Free(); return hr; } BOOL bEqual = ILIsEqual(pidl, m_pidlLastParsed); ILFree(pidl); if (bEqual) { m_pidlLastParsed.Free(); return S_OK; } /* * Attempt to browse to the parsed pidl */ hr = pisb->BrowseObject(m_pidlLastParsed, SBSP_SAMEBROWSER | SBSP_ABSOLUTE); if (SUCCEEDED(hr)) return hr; /* * Browsing to the pidl failed so it's not a folder. So invoke its default command. */ HWND hWnd; if (FAILED(IUnknown_GetWindow(pisb, &hWnd))) hWnd = NULL; LPCITEMIDLIST pidlChild; CComPtr sf; hr = SHBindToParent(m_pidlLastParsed, IID_PPV_ARG(IShellFolder, &sf), &pidlChild); if (SUCCEEDED(hr)) hr = SHInvokeDefaultCommand(hWnd, sf, pidlChild); if (bParsedForExec) m_pidlLastParsed.Free(); // Throw away the non-folder item we just parsed RefreshAddress(); // Set the address back to a valid folder return hr; } HRESULT STDMETHODCALLTYPE CAddressEditBox::Save(long paramC) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::OnWinEvent( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *theResult) { LPNMHDR hdr; if (theResult) *theResult = 0; switch (uMsg) { case WM_COMMAND: { if (HIWORD(wParam) == CBN_SELCHANGE && fCombobox == (HWND)lParam) { INT iItem = (INT)fCombobox.SendMessage(CB_GETCURSEL); PIDLIST_ABSOLUTE pidl = (PIDLIST_ABSOLUTE)fCombobox.SendMessage(CB_GETITEMDATA, iItem); m_pidlLastParsed.Free(); if (pidl) m_pidlLastParsed.Attach(ILClone(pidl)); Execute(0); } break; } case WM_NOTIFY: { hdr = (LPNMHDR) lParam; if (hdr->code == CBEN_ENDEDIT) { NMCBEENDEDITW *endEdit = (NMCBEENDEDITW*) lParam; if (endEdit->iWhy == CBENF_RETURN) { Execute(0); } else if (endEdit->iWhy == CBENF_ESCAPE) { /* Reset the contents of the combo box */ RefreshAddress(); } } else if (hdr->code == CBEN_DELETEITEM) { PNMCOMBOBOXEX pCBEx = (PNMCOMBOBOXEX) lParam; LPITEMIDLIST itemPidl = (LPITEMIDLIST)pCBEx->ceItem.lParam; if (itemPidl) { ILFree(itemPidl); } } break; } } return S_OK; } HRESULT STDMETHODCALLTYPE CAddressEditBox::IsWindowOwner(HWND hWnd) { if (fCombobox.m_hWnd == hWnd) return S_OK; if (fEditWindow.m_hWnd == hWnd) return S_OK; return S_FALSE; } HRESULT STDMETHODCALLTYPE CAddressEditBox::QueryStatus( const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[ ], OLECMDTEXT *pCmdText) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::GetTypeInfoCount(UINT *pctinfo) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::GetIDsOfNames( REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { if (pDispParams == NULL) return E_INVALIDARG; switch (dispIdMember) { case DISPID_NAVIGATECOMPLETE2: case DISPID_DOCUMENTCOMPLETE: m_pidlLastParsed.Free(); RefreshAddress(); break; } return S_OK; } HRESULT STDMETHODCALLTYPE CAddressEditBox::GetClassID(CLSID *pClassID) { if (pClassID == NULL) return E_POINTER; *pClassID = CLSID_AddressEditBox; return S_OK; } HRESULT STDMETHODCALLTYPE CAddressEditBox::IsDirty() { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::Load(IStream *pStm) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::Save(IStream *pStm, BOOL fClearDirty) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE CAddressEditBox::GetSizeMax(ULARGE_INTEGER *pcbSize) { return E_NOTIMPL; } void CAddressEditBox::PopulateComboBox(LPITEMIDLIST pidlCurrent) { HRESULT hr; LPITEMIDLIST pidl; int indent = 0; int index; index = SendMessageW(hComboBoxEx, CB_GETCOUNT, 0, 0); for (int i = 0; i < index; i++) SendMessageW(hComboBoxEx, CBEM_DELETEITEM, i, 0); SendMessageW(hComboBoxEx, CB_RESETCONTENT, 0, 0); /* Calculate the indent level. No need to clone the pidl */ pidl = pidlCurrent; do { if(!pidl->mkid.cb) break; pidl = ILGetNext(pidl); indent++; } while (pidl); index = indent; /* Add every id from the pidl in the combo box */ pidl = ILClone(pidlCurrent); do { AddComboBoxItem(pidl, 0, index); ILRemoveLastID(pidl); index--; } while (index >= 0); ILFree(pidl); /* Add the items of the desktop */ FillOneLevel(0, 1, indent); /* Add the items of My Computer */ hr = SHGetSpecialFolderLocation(0, CSIDL_DRIVES, &pidl); if (FAILED_UNEXPECTEDLY(hr)) return; for(LPITEMIDLIST i = GetItemData(0); i; i = GetItemData(index)) { if (ILIsEqual(i, pidl)) { FillOneLevel(index, 2, indent); break; } index++; } ILFree(pidl); } void CAddressEditBox::AddComboBoxItem(LPITEMIDLIST pidl, int index, int indent) { HRESULT hr; WCHAR buf[4096]; LPCITEMIDLIST pidlChild; CComPtr sf; hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &sf), &pidlChild); if (FAILED_UNEXPECTEDLY(hr)) return; STRRET strret; hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR, &strret); if (FAILED_UNEXPECTEDLY(hr)) return; hr = StrRetToBufW(&strret, pidlChild, buf, 4095); if (FAILED_UNEXPECTEDLY(hr)) return; COMBOBOXEXITEMW item = {0}; item.mask = CBEIF_LPARAM | CBEIF_INDENT | CBEIF_SELECTEDIMAGE | CBEIF_IMAGE | CBEIF_TEXT; item.iImage = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &item.iSelectedImage); item.pszText = buf; item.lParam = (LPARAM)(ILClone(pidl)); item.iIndent = indent; item.iItem = index; SendMessageW(hComboBoxEx, CBEM_INSERTITEMW, 0, (LPARAM)&item); } void CAddressEditBox::FillOneLevel(int index, int levelIndent, int indent) { HRESULT hr; ULONG numObj; int count; LPITEMIDLIST pidl, pidl2, pidl3, pidl4; count = index + 1; pidl = GetItemData(index); pidl2 = GetItemData(count); if(pidl) { CComPtr psfDesktop; CComPtr psfItem; hr = SHGetDesktopFolder(&psfDesktop); if (FAILED_UNEXPECTEDLY(hr)) return; if (!pidl->mkid.cb) { psfItem = psfDesktop; } else { hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfItem)); if (FAILED_UNEXPECTEDLY(hr)) return; } CComPtr pEnumIDList; hr = psfItem->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN, &pEnumIDList); if (FAILED_UNEXPECTEDLY(hr)) return; do { hr = pEnumIDList->Next(1, &pidl3, &numObj); if(hr != S_OK || !numObj) break; pidl4 = ILCombine(pidl, pidl3); if (pidl2 && ILIsEqual(pidl4, pidl2)) count += (indent - levelIndent); else AddComboBoxItem(pidl4, count, levelIndent); count++; ILFree(pidl3); ILFree(pidl4); } while (true); } } LPITEMIDLIST CAddressEditBox::GetItemData(int index) { COMBOBOXEXITEMW item; memset(&item, 0, sizeof(COMBOBOXEXITEMW)); item.mask = CBEIF_LPARAM; item.iItem = index; SendMessageW(hComboBoxEx, CBEM_GETITEMW, 0, (LPARAM)&item); return (LPITEMIDLIST)item.lParam; } LRESULT CAddressEditBox::OnSettingChange(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { RefreshAddress(); return NO_ERROR; }