[SHELL] IPersistFolder::Initialize takes a PCIDLIST_ABSOLUTE. CORE-16385
[reactos.git] / dll / win32 / browseui / addresseditbox.cpp
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 if (theResult)
260 *theResult = 0;
261
262 switch (uMsg)
263 {
264 case WM_COMMAND:
265 {
266 if (HIWORD(wParam) == CBN_SELCHANGE)
267 {
268 UINT selectedIndex = SendMessageW((HWND)lParam, CB_GETCURSEL, 0, 0);
269 pidlLastParsed = ILClone((LPITEMIDLIST)SendMessageW((HWND)lParam, CB_GETITEMDATA, selectedIndex, 0));
270 Execute(0);
271 }
272 break;
273 }
274 case WM_NOTIFY:
275 {
276 hdr = (LPNMHDR) lParam;
277 if (hdr->code == CBEN_ENDEDIT)
278 {
279 NMCBEENDEDITW *endEdit = (NMCBEENDEDITW*) lParam;
280 if (endEdit->iWhy == CBENF_RETURN)
281 {
282 Execute(0);
283 }
284 else if (endEdit->iWhy == CBENF_ESCAPE)
285 {
286 /* Reset the contents of the combo box */
287 }
288 }
289 else if (hdr->code == CBEN_DELETEITEM)
290 {
291 PNMCOMBOBOXEX pCBEx = (PNMCOMBOBOXEX) lParam;
292 LPITEMIDLIST itemPidl = (LPITEMIDLIST)pCBEx->ceItem.lParam;
293 if (itemPidl)
294 {
295 ILFree(itemPidl);
296 }
297 }
298 break;
299 }
300 }
301 return S_OK;
302 }
303
304 HRESULT STDMETHODCALLTYPE CAddressEditBox::IsWindowOwner(HWND hWnd)
305 {
306 if (fCombobox.m_hWnd == hWnd)
307 return S_OK;
308 if (fEditWindow.m_hWnd == hWnd)
309 return S_OK;
310 return S_FALSE;
311 }
312
313 HRESULT STDMETHODCALLTYPE CAddressEditBox::QueryStatus(
314 const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[ ], OLECMDTEXT *pCmdText)
315 {
316 return E_NOTIMPL;
317 }
318
319 HRESULT STDMETHODCALLTYPE CAddressEditBox::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
320 DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
321 {
322 return E_NOTIMPL;
323 }
324
325 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetTypeInfoCount(UINT *pctinfo)
326 {
327 return E_NOTIMPL;
328 }
329
330 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
331 {
332 return E_NOTIMPL;
333 }
334
335 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetIDsOfNames(
336 REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
337 {
338 return E_NOTIMPL;
339 }
340
341 HRESULT STDMETHODCALLTYPE CAddressEditBox::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
342 WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
343 {
344 CComPtr<IBrowserService> isb;
345 CComPtr<IShellFolder> sf;
346 HRESULT hr;
347 PIDLIST_ABSOLUTE absolutePIDL;
348 LPCITEMIDLIST pidlChild;
349 STRRET ret;
350 WCHAR buf[4096];
351
352 if (pDispParams == NULL)
353 return E_INVALIDARG;
354
355 switch (dispIdMember)
356 {
357 case DISPID_NAVIGATECOMPLETE2:
358 case DISPID_DOCUMENTCOMPLETE:
359
360 if (pidlLastParsed)
361 ILFree(pidlLastParsed);
362 pidlLastParsed = NULL;
363
364 /* Get the current pidl of the browser */
365 hr = IUnknown_QueryService(fSite, SID_STopLevelBrowser, IID_PPV_ARG(IBrowserService, &isb));
366 if (FAILED_UNEXPECTEDLY(hr))
367 return hr;
368
369 hr = isb->GetPidl(&absolutePIDL);
370 if (FAILED_UNEXPECTEDLY(hr))
371 return hr;
372
373 /* Fill the combobox */
374 PopulateComboBox(absolutePIDL);
375
376 /* Find the current item in the combobox and select it */
377 CComPtr<IShellFolder> psfDesktop;
378 hr = SHGetDesktopFolder(&psfDesktop);
379 if (FAILED_UNEXPECTEDLY(hr))
380 return S_OK;
381
382 hr = psfDesktop->GetDisplayNameOf(absolutePIDL, SHGDN_FORADDRESSBAR, &ret);
383 if (FAILED_UNEXPECTEDLY(hr))
384 return S_OK;
385
386 hr = StrRetToBufW(&ret, absolutePIDL, buf, 4095);
387 if (FAILED_UNEXPECTEDLY(hr))
388 return S_OK;
389
390 int index = SendMessageW(hComboBoxEx, CB_FINDSTRINGEXACT, 0, (LPARAM)buf);
391 if (index != -1)
392 SendMessageW(hComboBoxEx, CB_SETCURSEL, index, 0);
393
394 /* Add the item that will be visible when the combobox is not expanded */
395 hr = SHBindToParent(absolutePIDL, IID_PPV_ARG(IShellFolder, &sf), &pidlChild);
396 if (FAILED_UNEXPECTEDLY(hr))
397 return hr;
398
399 hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, &ret);
400 if (FAILED_UNEXPECTEDLY(hr))
401 return hr;
402
403 hr = StrRetToBufW(&ret, pidlChild, buf, 4095);
404 if (FAILED_UNEXPECTEDLY(hr))
405 return hr;
406
407 INT indexClosed, indexOpen;
408 indexClosed = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &indexOpen);
409
410 COMBOBOXEXITEMW item = {0};
411 item.mask = CBEIF_IMAGE | CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_LPARAM;
412 item.iItem = -1;
413 item.iImage = indexClosed;
414 item.iSelectedImage = indexOpen;
415 item.pszText = buf;
416 item.lParam = reinterpret_cast<LPARAM>(absolutePIDL);
417 fCombobox.SendMessage(CBEM_SETITEM, 0, reinterpret_cast<LPARAM>(&item));
418 }
419 return S_OK;
420 }
421
422 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetClassID(CLSID *pClassID)
423 {
424 if (pClassID == NULL)
425 return E_POINTER;
426 *pClassID = CLSID_AddressEditBox;
427 return S_OK;
428 }
429
430 HRESULT STDMETHODCALLTYPE CAddressEditBox::IsDirty()
431 {
432 return E_NOTIMPL;
433 }
434
435 HRESULT STDMETHODCALLTYPE CAddressEditBox::Load(IStream *pStm)
436 {
437 return E_NOTIMPL;
438 }
439
440 HRESULT STDMETHODCALLTYPE CAddressEditBox::Save(IStream *pStm, BOOL fClearDirty)
441 {
442 return E_NOTIMPL;
443 }
444
445 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetSizeMax(ULARGE_INTEGER *pcbSize)
446 {
447 return E_NOTIMPL;
448 }
449
450 void CAddressEditBox::PopulateComboBox(LPITEMIDLIST pidlCurrent)
451 {
452 HRESULT hr;
453 LPITEMIDLIST pidl;
454 int indent = 0;
455 int index;
456
457 index = SendMessageW(hComboBoxEx, CB_GETCOUNT, 0, 0);
458 for (int i = 0; i < index; i++)
459 SendMessageW(hComboBoxEx, CBEM_DELETEITEM, i, 0);
460 SendMessageW(hComboBoxEx, CB_RESETCONTENT, 0, 0);
461
462 /* Calculate the indent level. No need to clone the pidl */
463 pidl = pidlCurrent;
464 do
465 {
466 if(!pidl->mkid.cb)
467 break;
468 pidl = ILGetNext(pidl);
469 indent++;
470 } while (pidl);
471 index = indent;
472
473 /* Add every id from the pidl in the combo box */
474 pidl = ILClone(pidlCurrent);
475 do
476 {
477 AddComboBoxItem(pidl, 0, index);
478 ILRemoveLastID(pidl);
479 index--;
480 } while (index >= 0);
481 ILFree(pidl);
482
483 /* Add the items of the desktop */
484 FillOneLevel(0, 1, indent);
485
486 /* Add the items of My Computer */
487 hr = SHGetSpecialFolderLocation(0, CSIDL_DRIVES, &pidl);
488 if (FAILED_UNEXPECTEDLY(hr))
489 return;
490
491 for(LPITEMIDLIST i = GetItemData(0); i; i = GetItemData(index))
492 {
493 if (ILIsEqual(i, pidl))
494 {
495 FillOneLevel(index, 2, indent);
496 break;
497 }
498 index++;
499 }
500 ILFree(pidl);
501 }
502
503 void CAddressEditBox::AddComboBoxItem(LPITEMIDLIST pidl, int index, int indent)
504 {
505 HRESULT hr;
506 WCHAR buf[4096];
507
508 LPCITEMIDLIST pidlChild;
509 CComPtr<IShellFolder> sf;
510 hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &sf), &pidlChild);
511 if (FAILED_UNEXPECTEDLY(hr))
512 return;
513
514 STRRET strret;
515 hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR, &strret);
516 if (FAILED_UNEXPECTEDLY(hr))
517 return;
518
519 hr = StrRetToBufW(&strret, pidlChild, buf, 4095);
520 if (FAILED_UNEXPECTEDLY(hr))
521 return;
522
523 COMBOBOXEXITEMW item = {0};
524 item.mask = CBEIF_LPARAM | CBEIF_INDENT | CBEIF_SELECTEDIMAGE | CBEIF_IMAGE | CBEIF_TEXT;
525 item.iImage = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &item.iSelectedImage);
526 item.pszText = buf;
527 item.lParam = (LPARAM)(ILClone(pidl));
528 item.iIndent = indent;
529 item.iItem = index;
530 SendMessageW(hComboBoxEx, CBEM_INSERTITEMW, 0, (LPARAM)&item);
531 }
532
533 void CAddressEditBox::FillOneLevel(int index, int levelIndent, int indent)
534 {
535 HRESULT hr;
536 ULONG numObj;
537 int count;
538 LPITEMIDLIST pidl, pidl2, pidl3, pidl4;
539
540 count = index + 1;
541 pidl = GetItemData(index);
542 pidl2 = GetItemData(count);
543 if(pidl)
544 {
545 CComPtr<IShellFolder> psfDesktop;
546 CComPtr<IShellFolder> psfItem;
547
548 hr = SHGetDesktopFolder(&psfDesktop);
549 if (FAILED_UNEXPECTEDLY(hr))
550 return;
551
552 if (!pidl->mkid.cb)
553 {
554 psfItem = psfDesktop;
555 }
556 else
557 {
558 hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfItem));
559 if (FAILED_UNEXPECTEDLY(hr))
560 return;
561 }
562
563 CComPtr<IEnumIDList> pEnumIDList;
564 hr = psfItem->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN, &pEnumIDList);
565 if (FAILED_UNEXPECTEDLY(hr))
566 return;
567
568 do
569 {
570 hr = pEnumIDList->Next(1, &pidl3, &numObj);
571 if(hr != S_OK || !numObj)
572 break;
573
574 pidl4 = ILCombine(pidl, pidl3);
575 if (pidl2 && ILIsEqual(pidl4, pidl2))
576 count += (indent - levelIndent);
577 else
578 AddComboBoxItem(pidl4, count, levelIndent);
579 count++;
580 ILFree(pidl3);
581 ILFree(pidl4);
582 } while (true);
583 }
584 }
585
586 LPITEMIDLIST CAddressEditBox::GetItemData(int index)
587 {
588 COMBOBOXEXITEMW item;
589
590 memset(&item, 0, sizeof(COMBOBOXEXITEMW));
591 item.mask = CBEIF_LPARAM;
592 item.iItem = index;
593 SendMessageW(hComboBoxEx, CBEM_GETITEMW, 0, (LPARAM)&item);
594 return (LPITEMIDLIST)item.lParam;
595 }