1 /* 2 * ReactOS Explorer 3 * 4 * Copyright 2009 Andrew Hill <ash77 at domain reactos.org> 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 21 /* 22 This class handles the combo box of the address band. 23 */ 24 25 #include "precomp.h" 26 27 /* 28 TODO: 29 Add drag and drop of icon in edit box 30 Handle change notifies to update appropriately 31 */ 32 33 CAddressEditBox::CAddressEditBox() : 34 fCombobox(NULL, this, 1), 35 fEditWindow(NULL, this, 1), 36 fSite(NULL), 37 pidlLastParsed(NULL) 38 { 39 } 40 41 CAddressEditBox::~CAddressEditBox() 42 { 43 if (pidlLastParsed) 44 ILFree(pidlLastParsed); 45 } 46 47 HRESULT STDMETHODCALLTYPE CAddressEditBox::SetOwner(IUnknown *pOwner) 48 { 49 if (!pOwner) 50 { 51 CComPtr<IBrowserService> browserService; 52 HRESULT hResult = IUnknown_QueryService(fSite, SID_SShellBrowser, IID_PPV_ARG(IBrowserService, &browserService)); 53 if (SUCCEEDED(hResult)) 54 AtlUnadvise(browserService, DIID_DWebBrowserEvents, fAdviseCookie); 55 fSite = NULL; 56 } 57 // connect to browser connection point 58 return 0; 59 } 60 61 HRESULT STDMETHODCALLTYPE CAddressEditBox::FileSysChange(long param8, long paramC) 62 { 63 return E_NOTIMPL; 64 } 65 66 HRESULT STDMETHODCALLTYPE CAddressEditBox::Refresh(long param8) 67 { 68 return E_NOTIMPL; 69 } 70 71 HRESULT STDMETHODCALLTYPE CAddressEditBox::Init(HWND comboboxEx, HWND editControl, long param14, IUnknown *param18) 72 { 73 CComPtr<IBrowserService> browserService; 74 75 fCombobox.SubclassWindow(comboboxEx); 76 fEditWindow.SubclassWindow(editControl); 77 fSite = param18; 78 hComboBoxEx = comboboxEx; 79 80 SHAutoComplete(fEditWindow.m_hWnd, SHACF_FILESYSTEM | SHACF_URLALL | SHACF_USETAB); 81 82 // take advice to watch events 83 HRESULT hResult = IUnknown_QueryService(param18, SID_SShellBrowser, IID_PPV_ARG(IBrowserService, &browserService)); 84 if (SUCCEEDED(hResult)) 85 { 86 hResult = AtlAdvise(browserService, static_cast<IDispatch *>(this), DIID_DWebBrowserEvents, &fAdviseCookie); 87 } 88 89 return hResult; 90 } 91 92 HRESULT STDMETHODCALLTYPE CAddressEditBox::SetCurrentDir(long paramC) 93 { 94 return E_NOTIMPL; 95 } 96 97 HRESULT STDMETHODCALLTYPE CAddressEditBox::ParseNow(long paramC) 98 { 99 ULONG eaten; 100 ULONG attributes; 101 HRESULT hr; 102 HWND topLevelWindow; 103 PIDLIST_ABSOLUTE pidlCurrent= NULL; 104 PIDLIST_RELATIVE pidlRelative = NULL; 105 CComPtr<IShellFolder> psfCurrent; 106 107 CComPtr<IBrowserService> pbs; 108 hr = IUnknown_QueryService(fSite, SID_SShellBrowser, IID_PPV_ARG(IBrowserService, &pbs)); 109 if (FAILED_UNEXPECTEDLY(hr)) 110 return hr; 111 112 hr = IUnknown_GetWindow(pbs, &topLevelWindow); 113 if (FAILED_UNEXPECTEDLY(hr)) 114 return hr; 115 116 /* Get the path to browse and expand it if needed */ 117 LPWSTR input; 118 int inputLength = fCombobox.GetWindowTextLength() + 2; 119 120 input = new WCHAR[inputLength]; 121 fCombobox.GetWindowText(input, inputLength); 122 123 LPWSTR address; 124 int addressLength = ExpandEnvironmentStrings(input, NULL, 0); 125 126 if (addressLength <= 0) 127 { 128 address = input; 129 } 130 else 131 { 132 addressLength += 2; 133 address = new WCHAR[addressLength]; 134 if (!ExpandEnvironmentStrings(input, address, addressLength)) 135 { 136 delete[] address; 137 address = input; 138 } 139 } 140 141 /* Try to parse a relative path and if it fails, try to browse an absolute path */ 142 CComPtr<IShellFolder> psfDesktop; 143 hr = SHGetDesktopFolder(&psfDesktop); 144 if (FAILED_UNEXPECTEDLY(hr)) 145 goto cleanup; 146 147 hr = pbs->GetPidl(&pidlCurrent); 148 if (FAILED_UNEXPECTEDLY(hr)) 149 goto parseabsolute; 150 151 hr = psfDesktop->BindToObject(pidlCurrent, NULL, IID_PPV_ARG(IShellFolder, &psfCurrent)); 152 if (FAILED_UNEXPECTEDLY(hr)) 153 goto parseabsolute; 154 155 hr = psfCurrent->ParseDisplayName(topLevelWindow, NULL, address, &eaten, &pidlRelative, &attributes); 156 if (SUCCEEDED(hr)) 157 { 158 pidlLastParsed = ILCombine(pidlCurrent, pidlRelative); 159 ILFree(pidlRelative); 160 goto cleanup; 161 } 162 163 parseabsolute: 164 /* We couldn't parse a relative path, attempt to parse an absolute path */ 165 hr = psfDesktop->ParseDisplayName(topLevelWindow, NULL, address, &eaten, &pidlLastParsed, &attributes); 166 167 cleanup: 168 if (pidlCurrent) 169 ILFree(pidlCurrent); 170 if (address != input) 171 delete [] address; 172 delete [] input; 173 174 return hr; 175 } 176 177 HRESULT STDMETHODCALLTYPE CAddressEditBox::ShowFileNotFoundError(HRESULT hRet) 178 { 179 CComHeapPtr<WCHAR> input; 180 int inputLength = fCombobox.GetWindowTextLength() + 2; 181 182 input.Allocate(inputLength); 183 fCombobox.GetWindowText(input, inputLength); 184 185 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); 186 187 return hRet; 188 } 189 190 HRESULT STDMETHODCALLTYPE CAddressEditBox::Execute(long paramC) 191 { 192 HRESULT hr; 193 194 /* 195 * Parse the path is it wasn't parsed 196 */ 197 if (!pidlLastParsed) 198 hr = ParseNow(0); 199 200 /* 201 * If the destination path doesn't exist then display an error message 202 */ 203 if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) 204 return ShowFileNotFoundError(hr); 205 206 if (!pidlLastParsed) 207 return E_FAIL; 208 209 /* 210 * Get the IShellBrowser and IBrowserService interfaces of the shell browser 211 */ 212 CComPtr<IShellBrowser> pisb; 213 hr = IUnknown_QueryService(fSite, SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &pisb)); 214 if (FAILED(hr)) 215 return hr; 216 217 CComPtr<IBrowserService> pbs; 218 pisb->QueryInterface(IID_PPV_ARG(IBrowserService, &pbs)); 219 if (FAILED(hr)) 220 return hr; 221 222 /* 223 * Get the current pidl of the shellbrowser and check if it is the same with the parsed one 224 */ 225 PIDLIST_ABSOLUTE pidl; 226 hr = pbs->GetPidl(&pidl); 227 if (FAILED(hr)) 228 return hr; 229 230 CComPtr<IShellFolder> psf; 231 hr = SHGetDesktopFolder(&psf); 232 if (FAILED(hr)) 233 return hr; 234 235 hr = psf->CompareIDs(0, pidl, pidlLastParsed); 236 237 SHFree(pidl); 238 if (hr == 0) 239 return S_OK; 240 241 /* 242 * Attempt to browse to the parsed pidl 243 */ 244 hr = pisb->BrowseObject(pidlLastParsed, 0); 245 if (SUCCEEDED(hr)) 246 return hr; 247 248 /* 249 * Browsing to the pidl failed so it's not a folder. So invoke its defaule command. 250 */ 251 HWND topLevelWindow; 252 hr = IUnknown_GetWindow(pisb, &topLevelWindow); 253 if (FAILED(hr)) 254 return hr; 255 256 LPCITEMIDLIST pidlChild; 257 CComPtr<IShellFolder> sf; 258 hr = SHBindToParent(pidlLastParsed, IID_PPV_ARG(IShellFolder, &sf), &pidlChild); 259 if (FAILED(hr)) 260 return hr; 261 262 hr = SHInvokeDefaultCommand(topLevelWindow, sf, pidlChild); 263 if (FAILED(hr)) 264 return hr; 265 266 return hr; 267 } 268 269 HRESULT STDMETHODCALLTYPE CAddressEditBox::Save(long paramC) 270 { 271 return E_NOTIMPL; 272 } 273 274 HRESULT STDMETHODCALLTYPE CAddressEditBox::OnWinEvent( 275 HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *theResult) 276 { 277 LPNMHDR hdr; 278 279 if (theResult) 280 *theResult = 0; 281 282 switch (uMsg) 283 { 284 case WM_COMMAND: 285 { 286 if (HIWORD(wParam) == CBN_SELCHANGE) 287 { 288 UINT selectedIndex = SendMessageW((HWND)lParam, CB_GETCURSEL, 0, 0); 289 pidlLastParsed = ILClone((LPITEMIDLIST)SendMessageW((HWND)lParam, CB_GETITEMDATA, selectedIndex, 0)); 290 Execute(0); 291 } 292 break; 293 } 294 case WM_NOTIFY: 295 { 296 hdr = (LPNMHDR) lParam; 297 if (hdr->code == CBEN_ENDEDIT) 298 { 299 NMCBEENDEDITW *endEdit = (NMCBEENDEDITW*) lParam; 300 if (endEdit->iWhy == CBENF_RETURN) 301 { 302 Execute(0); 303 } 304 else if (endEdit->iWhy == CBENF_ESCAPE) 305 { 306 /* Reset the contents of the combo box */ 307 } 308 } 309 else if (hdr->code == CBEN_DELETEITEM) 310 { 311 PNMCOMBOBOXEX pCBEx = (PNMCOMBOBOXEX) lParam; 312 LPITEMIDLIST itemPidl = (LPITEMIDLIST)pCBEx->ceItem.lParam; 313 if (itemPidl) 314 { 315 ILFree(itemPidl); 316 } 317 } 318 break; 319 } 320 } 321 return S_OK; 322 } 323 324 HRESULT STDMETHODCALLTYPE CAddressEditBox::IsWindowOwner(HWND hWnd) 325 { 326 if (fCombobox.m_hWnd == hWnd) 327 return S_OK; 328 if (fEditWindow.m_hWnd == hWnd) 329 return S_OK; 330 return S_FALSE; 331 } 332 333 HRESULT STDMETHODCALLTYPE CAddressEditBox::QueryStatus( 334 const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[ ], OLECMDTEXT *pCmdText) 335 { 336 return E_NOTIMPL; 337 } 338 339 HRESULT STDMETHODCALLTYPE CAddressEditBox::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, 340 DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut) 341 { 342 return E_NOTIMPL; 343 } 344 345 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetTypeInfoCount(UINT *pctinfo) 346 { 347 return E_NOTIMPL; 348 } 349 350 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) 351 { 352 return E_NOTIMPL; 353 } 354 355 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetIDsOfNames( 356 REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) 357 { 358 return E_NOTIMPL; 359 } 360 361 HRESULT STDMETHODCALLTYPE CAddressEditBox::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, 362 WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) 363 { 364 CComPtr<IBrowserService> isb; 365 CComPtr<IShellFolder> sf; 366 HRESULT hr; 367 PIDLIST_ABSOLUTE absolutePIDL; 368 LPCITEMIDLIST pidlChild; 369 STRRET ret; 370 WCHAR buf[4096]; 371 372 if (pDispParams == NULL) 373 return E_INVALIDARG; 374 375 switch (dispIdMember) 376 { 377 case DISPID_NAVIGATECOMPLETE2: 378 case DISPID_DOCUMENTCOMPLETE: 379 380 if (pidlLastParsed) 381 ILFree(pidlLastParsed); 382 pidlLastParsed = NULL; 383 384 /* Get the current pidl of the browser */ 385 hr = IUnknown_QueryService(fSite, SID_STopLevelBrowser, IID_PPV_ARG(IBrowserService, &isb)); 386 if (FAILED_UNEXPECTEDLY(hr)) 387 return hr; 388 389 hr = isb->GetPidl(&absolutePIDL); 390 if (FAILED_UNEXPECTEDLY(hr)) 391 return hr; 392 393 /* Fill the combobox */ 394 PopulateComboBox(absolutePIDL); 395 396 /* Find the current item in the combobox and select it */ 397 CComPtr<IShellFolder> psfDesktop; 398 hr = SHGetDesktopFolder(&psfDesktop); 399 if (FAILED_UNEXPECTEDLY(hr)) 400 return S_OK; 401 402 hr = psfDesktop->GetDisplayNameOf(absolutePIDL, SHGDN_FORADDRESSBAR, &ret); 403 if (FAILED_UNEXPECTEDLY(hr)) 404 return S_OK; 405 406 hr = StrRetToBufW(&ret, absolutePIDL, buf, 4095); 407 if (FAILED_UNEXPECTEDLY(hr)) 408 return S_OK; 409 410 int index = SendMessageW(hComboBoxEx, CB_FINDSTRINGEXACT, 0, (LPARAM)buf); 411 if (index != -1) 412 SendMessageW(hComboBoxEx, CB_SETCURSEL, index, 0); 413 414 /* Add the item that will be visible when the combobox is not expanded */ 415 hr = SHBindToParent(absolutePIDL, IID_PPV_ARG(IShellFolder, &sf), &pidlChild); 416 if (FAILED_UNEXPECTEDLY(hr)) 417 return hr; 418 419 hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, &ret); 420 if (FAILED_UNEXPECTEDLY(hr)) 421 return hr; 422 423 hr = StrRetToBufW(&ret, pidlChild, buf, 4095); 424 if (FAILED_UNEXPECTEDLY(hr)) 425 return hr; 426 427 INT indexClosed, indexOpen; 428 indexClosed = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &indexOpen); 429 430 COMBOBOXEXITEMW item = {0}; 431 item.mask = CBEIF_IMAGE | CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_LPARAM; 432 item.iItem = -1; 433 item.iImage = indexClosed; 434 item.iSelectedImage = indexOpen; 435 item.pszText = buf; 436 item.lParam = reinterpret_cast<LPARAM>(absolutePIDL); 437 fCombobox.SendMessage(CBEM_SETITEM, 0, reinterpret_cast<LPARAM>(&item)); 438 } 439 return S_OK; 440 } 441 442 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetClassID(CLSID *pClassID) 443 { 444 if (pClassID == NULL) 445 return E_POINTER; 446 *pClassID = CLSID_AddressEditBox; 447 return S_OK; 448 } 449 450 HRESULT STDMETHODCALLTYPE CAddressEditBox::IsDirty() 451 { 452 return E_NOTIMPL; 453 } 454 455 HRESULT STDMETHODCALLTYPE CAddressEditBox::Load(IStream *pStm) 456 { 457 return E_NOTIMPL; 458 } 459 460 HRESULT STDMETHODCALLTYPE CAddressEditBox::Save(IStream *pStm, BOOL fClearDirty) 461 { 462 return E_NOTIMPL; 463 } 464 465 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetSizeMax(ULARGE_INTEGER *pcbSize) 466 { 467 return E_NOTIMPL; 468 } 469 470 void CAddressEditBox::PopulateComboBox(LPITEMIDLIST pidlCurrent) 471 { 472 HRESULT hr; 473 LPITEMIDLIST pidl; 474 int indent = 0; 475 int index; 476 477 index = SendMessageW(hComboBoxEx, CB_GETCOUNT, 0, 0); 478 for (int i = 0; i < index; i++) 479 SendMessageW(hComboBoxEx, CBEM_DELETEITEM, i, 0); 480 SendMessageW(hComboBoxEx, CB_RESETCONTENT, 0, 0); 481 482 /* Calculate the indent level. No need to clone the pidl */ 483 pidl = pidlCurrent; 484 do 485 { 486 if(!pidl->mkid.cb) 487 break; 488 pidl = ILGetNext(pidl); 489 indent++; 490 } while (pidl); 491 index = indent; 492 493 /* Add every id from the pidl in the combo box */ 494 pidl = ILClone(pidlCurrent); 495 do 496 { 497 AddComboBoxItem(pidl, 0, index); 498 ILRemoveLastID(pidl); 499 index--; 500 } while (index >= 0); 501 ILFree(pidl); 502 503 /* Add the items of the desktop */ 504 FillOneLevel(0, 1, indent); 505 506 /* Add the items of My Computer */ 507 hr = SHGetSpecialFolderLocation(0, CSIDL_DRIVES, &pidl); 508 if (FAILED_UNEXPECTEDLY(hr)) 509 return; 510 511 for(LPITEMIDLIST i = GetItemData(0); i; i = GetItemData(index)) 512 { 513 if (ILIsEqual(i, pidl)) 514 { 515 FillOneLevel(index, 2, indent); 516 break; 517 } 518 index++; 519 } 520 ILFree(pidl); 521 } 522 523 void CAddressEditBox::AddComboBoxItem(LPITEMIDLIST pidl, int index, int indent) 524 { 525 HRESULT hr; 526 WCHAR buf[4096]; 527 528 LPCITEMIDLIST pidlChild; 529 CComPtr<IShellFolder> sf; 530 hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &sf), &pidlChild); 531 if (FAILED_UNEXPECTEDLY(hr)) 532 return; 533 534 STRRET strret; 535 hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR, &strret); 536 if (FAILED_UNEXPECTEDLY(hr)) 537 return; 538 539 hr = StrRetToBufW(&strret, pidlChild, buf, 4095); 540 if (FAILED_UNEXPECTEDLY(hr)) 541 return; 542 543 COMBOBOXEXITEMW item = {0}; 544 item.mask = CBEIF_LPARAM | CBEIF_INDENT | CBEIF_SELECTEDIMAGE | CBEIF_IMAGE | CBEIF_TEXT; 545 item.iImage = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &item.iSelectedImage); 546 item.pszText = buf; 547 item.lParam = (LPARAM)(ILClone(pidl)); 548 item.iIndent = indent; 549 item.iItem = index; 550 SendMessageW(hComboBoxEx, CBEM_INSERTITEMW, 0, (LPARAM)&item); 551 } 552 553 void CAddressEditBox::FillOneLevel(int index, int levelIndent, int indent) 554 { 555 HRESULT hr; 556 ULONG numObj; 557 int count; 558 LPITEMIDLIST pidl, pidl2, pidl3, pidl4; 559 560 count = index + 1; 561 pidl = GetItemData(index); 562 pidl2 = GetItemData(count); 563 if(pidl) 564 { 565 CComPtr<IShellFolder> psfDesktop; 566 CComPtr<IShellFolder> psfItem; 567 568 hr = SHGetDesktopFolder(&psfDesktop); 569 if (FAILED_UNEXPECTEDLY(hr)) 570 return; 571 572 if (!pidl->mkid.cb) 573 { 574 psfItem = psfDesktop; 575 } 576 else 577 { 578 hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfItem)); 579 if (FAILED_UNEXPECTEDLY(hr)) 580 return; 581 } 582 583 CComPtr<IEnumIDList> pEnumIDList; 584 hr = psfItem->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN, &pEnumIDList); 585 if (FAILED_UNEXPECTEDLY(hr)) 586 return; 587 588 do 589 { 590 hr = pEnumIDList->Next(1, &pidl3, &numObj); 591 if(hr != S_OK || !numObj) 592 break; 593 594 pidl4 = ILCombine(pidl, pidl3); 595 if (pidl2 && ILIsEqual(pidl4, pidl2)) 596 count += (indent - levelIndent); 597 else 598 AddComboBoxItem(pidl4, count, levelIndent); 599 count++; 600 ILFree(pidl3); 601 ILFree(pidl4); 602 } while (true); 603 } 604 } 605 606 LPITEMIDLIST CAddressEditBox::GetItemData(int index) 607 { 608 COMBOBOXEXITEMW item; 609 610 memset(&item, 0, sizeof(COMBOBOXEXITEMW)); 611 item.mask = CBEIF_LPARAM; 612 item.iItem = index; 613 SendMessageW(hComboBoxEx, CBEM_GETITEMW, 0, (LPARAM)&item); 614 return (LPITEMIDLIST)item.lParam; 615 } 616