[SHELL32] CDrivesFolder: Implement the eject and disconnect menu items. CORE-13841
[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 *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 }