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 cleanup; 150 151 hr = psfDesktop->BindToObject(pidlCurrent, NULL, IID_PPV_ARG(IShellFolder, &psfCurrent)); 152 if (FAILED_UNEXPECTEDLY(hr)) 153 goto cleanup; 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 /* We couldn't parse a relative path, attempt to parse an absolute path */ 164 hr = psfDesktop->ParseDisplayName(topLevelWindow, NULL, address, &eaten, &pidlLastParsed, &attributes); 165 166 cleanup: 167 if (pidlCurrent) 168 ILFree(pidlCurrent); 169 if (address != input) 170 delete [] address; 171 delete [] input; 172 173 return hr; 174 } 175 176 HRESULT STDMETHODCALLTYPE CAddressEditBox::Execute(long paramC) 177 { 178 HRESULT hr; 179 180 /* 181 * Parse the path is it wasn't parsed 182 */ 183 if (!pidlLastParsed) 184 ParseNow(0); 185 186 if (!pidlLastParsed) 187 return E_FAIL; 188 189 /* 190 * Get the IShellBrowser and IBrowserService interfaces of the shell browser 191 */ 192 CComPtr<IShellBrowser> pisb; 193 hr = IUnknown_QueryService(fSite, SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &pisb)); 194 if (FAILED(hr)) 195 return hr; 196 197 CComPtr<IBrowserService> pbs; 198 pisb->QueryInterface(IID_PPV_ARG(IBrowserService, &pbs)); 199 if (FAILED(hr)) 200 return hr; 201 202 /* 203 * Get the current pidl of the shellbrowser and check if it is the same with the parsed one 204 */ 205 PIDLIST_ABSOLUTE pidl; 206 hr = pbs->GetPidl(&pidl); 207 if (FAILED(hr)) 208 return hr; 209 210 CComPtr<IShellFolder> psf; 211 hr = SHGetDesktopFolder(&psf); 212 if (FAILED(hr)) 213 return hr; 214 215 hr = psf->CompareIDs(0, pidl, pidlLastParsed); 216 217 SHFree(pidl); 218 if (hr == 0) 219 return S_OK; 220 221 /* 222 * Attempt to browse to the parsed pidl 223 */ 224 hr = pisb->BrowseObject(pidlLastParsed, 0); 225 if (SUCCEEDED(hr)) 226 return hr; 227 228 /* 229 * Browsing to the pidl failed so it's not a folder. So invoke its defaule command. 230 */ 231 HWND topLevelWindow; 232 hr = IUnknown_GetWindow(pisb, &topLevelWindow); 233 if (FAILED(hr)) 234 return hr; 235 236 LPCITEMIDLIST pidlChild; 237 CComPtr<IShellFolder> sf; 238 hr = SHBindToParent(pidlLastParsed, IID_PPV_ARG(IShellFolder, &sf), &pidlChild); 239 if (FAILED(hr)) 240 return hr; 241 242 hr = SHInvokeDefaultCommand(topLevelWindow, sf, pidlChild); 243 if (FAILED(hr)) 244 return hr; 245 246 return hr; 247 } 248 249 HRESULT STDMETHODCALLTYPE CAddressEditBox::Save(long paramC) 250 { 251 return E_NOTIMPL; 252 } 253 254 HRESULT STDMETHODCALLTYPE CAddressEditBox::OnWinEvent( 255 HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *theResult) 256 { 257 LPNMHDR hdr; 258 259 *theResult = 0; 260 261 switch (uMsg) 262 { 263 case WM_COMMAND: 264 { 265 if (HIWORD(wParam) == CBN_SELCHANGE) 266 { 267 UINT selectedIndex = SendMessageW((HWND)lParam, CB_GETCURSEL, 0, 0); 268 pidlLastParsed = ILClone((LPITEMIDLIST)SendMessageW((HWND)lParam, CB_GETITEMDATA, selectedIndex, 0)); 269 Execute(0); 270 } 271 break; 272 } 273 case WM_NOTIFY: 274 { 275 hdr = (LPNMHDR) lParam; 276 if (hdr->code == CBEN_ENDEDIT) 277 { 278 NMCBEENDEDITW *endEdit = (NMCBEENDEDITW*) lParam; 279 if (endEdit->iWhy == CBENF_RETURN) 280 { 281 Execute(0); 282 } 283 else if (endEdit->iWhy == CBENF_ESCAPE) 284 { 285 /* Reset the contents of the combo box */ 286 } 287 } 288 else if (hdr->code == CBEN_DELETEITEM) 289 { 290 PNMCOMBOBOXEX pCBEx = (PNMCOMBOBOXEX) lParam; 291 LPITEMIDLIST itemPidl = (LPITEMIDLIST)pCBEx->ceItem.lParam; 292 if (itemPidl) 293 { 294 ILFree(itemPidl); 295 } 296 } 297 break; 298 } 299 } 300 return S_OK; 301 } 302 303 HRESULT STDMETHODCALLTYPE CAddressEditBox::IsWindowOwner(HWND hWnd) 304 { 305 if (fCombobox.m_hWnd == hWnd) 306 return S_OK; 307 if (fEditWindow.m_hWnd == hWnd) 308 return S_OK; 309 return S_FALSE; 310 } 311 312 HRESULT STDMETHODCALLTYPE CAddressEditBox::QueryStatus( 313 const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[ ], OLECMDTEXT *pCmdText) 314 { 315 return E_NOTIMPL; 316 } 317 318 HRESULT STDMETHODCALLTYPE CAddressEditBox::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, 319 DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut) 320 { 321 return E_NOTIMPL; 322 } 323 324 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetTypeInfoCount(UINT *pctinfo) 325 { 326 return E_NOTIMPL; 327 } 328 329 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) 330 { 331 return E_NOTIMPL; 332 } 333 334 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetIDsOfNames( 335 REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) 336 { 337 return E_NOTIMPL; 338 } 339 340 HRESULT STDMETHODCALLTYPE CAddressEditBox::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, 341 WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) 342 { 343 CComPtr<IBrowserService> isb; 344 CComPtr<IShellFolder> sf; 345 HRESULT hr; 346 PIDLIST_ABSOLUTE absolutePIDL; 347 LPCITEMIDLIST pidlChild; 348 STRRET ret; 349 WCHAR buf[4096]; 350 351 if (pDispParams == NULL) 352 return E_INVALIDARG; 353 354 switch (dispIdMember) 355 { 356 case DISPID_NAVIGATECOMPLETE2: 357 case DISPID_DOCUMENTCOMPLETE: 358 359 if (pidlLastParsed) 360 ILFree(pidlLastParsed); 361 pidlLastParsed = NULL; 362 363 /* Get the current pidl of the browser */ 364 hr = IUnknown_QueryService(fSite, SID_STopLevelBrowser, IID_PPV_ARG(IBrowserService, &isb)); 365 if (FAILED_UNEXPECTEDLY(hr)) 366 return hr; 367 368 hr = isb->GetPidl(&absolutePIDL); 369 if (FAILED_UNEXPECTEDLY(hr)) 370 return hr; 371 372 /* Fill the combobox */ 373 PopulateComboBox(absolutePIDL); 374 375 /* Find the current item in the combobox and select it */ 376 CComPtr<IShellFolder> psfDesktop; 377 hr = SHGetDesktopFolder(&psfDesktop); 378 if (FAILED_UNEXPECTEDLY(hr)) 379 return S_OK; 380 381 hr = psfDesktop->GetDisplayNameOf(absolutePIDL, SHGDN_FORADDRESSBAR, &ret); 382 if (FAILED_UNEXPECTEDLY(hr)) 383 return S_OK; 384 385 hr = StrRetToBufW(&ret, absolutePIDL, buf, 4095); 386 if (FAILED_UNEXPECTEDLY(hr)) 387 return S_OK; 388 389 int index = SendMessageW(hComboBoxEx, CB_FINDSTRINGEXACT, 0, (LPARAM)buf); 390 if (index != -1) 391 SendMessageW(hComboBoxEx, CB_SETCURSEL, index, 0); 392 393 /* Add the item that will be visible when the combobox is not expanded */ 394 hr = SHBindToParent(absolutePIDL, IID_PPV_ARG(IShellFolder, &sf), &pidlChild); 395 if (FAILED_UNEXPECTEDLY(hr)) 396 return hr; 397 398 hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, &ret); 399 if (FAILED_UNEXPECTEDLY(hr)) 400 return hr; 401 402 hr = StrRetToBufW(&ret, pidlChild, buf, 4095); 403 if (FAILED_UNEXPECTEDLY(hr)) 404 return hr; 405 406 INT indexClosed, indexOpen; 407 indexClosed = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &indexOpen); 408 409 COMBOBOXEXITEMW item = {0}; 410 item.mask = CBEIF_IMAGE | CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_LPARAM; 411 item.iItem = -1; 412 item.iImage = indexClosed; 413 item.iSelectedImage = indexOpen; 414 item.pszText = buf; 415 item.lParam = reinterpret_cast<LPARAM>(absolutePIDL); 416 fCombobox.SendMessage(CBEM_SETITEM, 0, reinterpret_cast<LPARAM>(&item)); 417 } 418 return S_OK; 419 } 420 421 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetClassID(CLSID *pClassID) 422 { 423 if (pClassID == NULL) 424 return E_POINTER; 425 *pClassID = CLSID_AddressEditBox; 426 return S_OK; 427 } 428 429 HRESULT STDMETHODCALLTYPE CAddressEditBox::IsDirty() 430 { 431 return E_NOTIMPL; 432 } 433 434 HRESULT STDMETHODCALLTYPE CAddressEditBox::Load(IStream *pStm) 435 { 436 return E_NOTIMPL; 437 } 438 439 HRESULT STDMETHODCALLTYPE CAddressEditBox::Save(IStream *pStm, BOOL fClearDirty) 440 { 441 return E_NOTIMPL; 442 } 443 444 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetSizeMax(ULARGE_INTEGER *pcbSize) 445 { 446 return E_NOTIMPL; 447 } 448 449 void CAddressEditBox::PopulateComboBox(LPITEMIDLIST pidlCurrent) 450 { 451 HRESULT hr; 452 LPITEMIDLIST pidl; 453 int indent = 0; 454 int index; 455 456 index = SendMessageW(hComboBoxEx, CB_GETCOUNT, 0, 0); 457 for (int i = 0; i < index; i++) 458 SendMessageW(hComboBoxEx, CBEM_DELETEITEM, i, 0); 459 SendMessageW(hComboBoxEx, CB_RESETCONTENT, 0, 0); 460 461 /* Calculate the indent level. No need to clone the pidl */ 462 pidl = pidlCurrent; 463 do 464 { 465 if(!pidl->mkid.cb) 466 break; 467 pidl = ILGetNext(pidl); 468 indent++; 469 } while (pidl); 470 index = indent; 471 472 /* Add every id from the pidl in the combo box */ 473 pidl = ILClone(pidlCurrent); 474 do 475 { 476 AddComboBoxItem(pidl, 0, index); 477 ILRemoveLastID(pidl); 478 index--; 479 } while (index >= 0); 480 ILFree(pidl); 481 482 /* Add the items of the desktop */ 483 FillOneLevel(0, 1, indent); 484 485 /* Add the items of My Computer */ 486 hr = SHGetSpecialFolderLocation(0, CSIDL_DRIVES, &pidl); 487 if (FAILED_UNEXPECTEDLY(hr)) 488 return; 489 490 for(LPITEMIDLIST i = GetItemData(0); i; i = GetItemData(index)) 491 { 492 if (ILIsEqual(i, pidl)) 493 { 494 FillOneLevel(index, 2, indent); 495 break; 496 } 497 index++; 498 } 499 ILFree(pidl); 500 } 501 502 void CAddressEditBox::AddComboBoxItem(LPITEMIDLIST pidl, int index, int indent) 503 { 504 HRESULT hr; 505 WCHAR buf[4096]; 506 507 LPCITEMIDLIST pidlChild; 508 CComPtr<IShellFolder> sf; 509 hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &sf), &pidlChild); 510 if (FAILED_UNEXPECTEDLY(hr)) 511 return; 512 513 STRRET strret; 514 hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR, &strret); 515 if (FAILED_UNEXPECTEDLY(hr)) 516 return; 517 518 hr = StrRetToBufW(&strret, pidlChild, buf, 4095); 519 if (FAILED_UNEXPECTEDLY(hr)) 520 return; 521 522 COMBOBOXEXITEMW item = {0}; 523 item.mask = CBEIF_LPARAM | CBEIF_INDENT | CBEIF_SELECTEDIMAGE | CBEIF_IMAGE | CBEIF_TEXT; 524 item.iImage = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &item.iSelectedImage); 525 item.pszText = buf; 526 item.lParam = (LPARAM)(ILClone(pidl)); 527 item.iIndent = indent; 528 item.iItem = index; 529 SendMessageW(hComboBoxEx, CBEM_INSERTITEMW, 0, (LPARAM)&item); 530 } 531 532 void CAddressEditBox::FillOneLevel(int index, int levelIndent, int indent) 533 { 534 HRESULT hr; 535 ULONG numObj; 536 int count; 537 LPITEMIDLIST pidl, pidl2, pidl3, pidl4; 538 539 count = index + 1; 540 pidl = GetItemData(index); 541 pidl2 = GetItemData(count); 542 if(pidl) 543 { 544 CComPtr<IShellFolder> psfDesktop; 545 CComPtr<IShellFolder> psfItem; 546 547 hr = SHGetDesktopFolder(&psfDesktop); 548 if (FAILED_UNEXPECTEDLY(hr)) 549 return; 550 551 if (!pidl->mkid.cb) 552 { 553 psfItem = psfDesktop; 554 } 555 else 556 { 557 hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfItem)); 558 if (FAILED_UNEXPECTEDLY(hr)) 559 return; 560 } 561 562 CComPtr<IEnumIDList> pEnumIDList; 563 hr = psfItem->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN, &pEnumIDList); 564 if (FAILED_UNEXPECTEDLY(hr)) 565 return; 566 567 do 568 { 569 hr = pEnumIDList->Next(1, &pidl3, &numObj); 570 if(hr != S_OK || !numObj) 571 break; 572 573 pidl4 = ILCombine(pidl, pidl3); 574 if (pidl2 && ILIsEqual(pidl4, pidl2)) 575 count += (indent - levelIndent); 576 else 577 AddComboBoxItem(pidl4, count, levelIndent); 578 count++; 579 ILFree(pidl3); 580 ILFree(pidl4); 581 } while (true); 582 } 583 } 584 585 LPITEMIDLIST CAddressEditBox::GetItemData(int index) 586 { 587 COMBOBOXEXITEMW item; 588 589 memset(&item, 0, sizeof(COMBOBOXEXITEMW)); 590 item.mask = CBEIF_LPARAM; 591 item.iItem = index; 592 SendMessageW(hComboBoxEx, CBEM_GETITEMW, 0, (LPARAM)&item); 593 return (LPITEMIDLIST)item.lParam; 594 } 595