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(WC_COMBOBOXEXW, this),
35     fEditWindow(WC_EDITW, this),
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 if it wasn't parsed
196      */
197     if (!pidlLastParsed)
198     {
199         hr = ParseNow(0);
200 
201         /* If the destination path doesn't exist then display an error message */
202         if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
203             return ShowFileNotFoundError(hr);
204 
205         if (!pidlLastParsed)
206             return E_FAIL;
207     }
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         if (!absolutePIDL)
394         {
395             ERR("Got no PIDL, investigate me!\n");
396             return S_OK;
397         }
398 
399         /* Fill the combobox */
400         PopulateComboBox(absolutePIDL);
401 
402         /* Find the current item in the combobox and select it */
403         CComPtr<IShellFolder> psfDesktop;
404         hr = SHGetDesktopFolder(&psfDesktop);
405         if (FAILED_UNEXPECTEDLY(hr))
406             return S_OK;
407 
408         hr = psfDesktop->GetDisplayNameOf(absolutePIDL, SHGDN_FORADDRESSBAR, &ret);
409         if (FAILED_UNEXPECTEDLY(hr))
410             return S_OK;
411 
412         hr = StrRetToBufW(&ret, absolutePIDL, buf, 4095);
413         if (FAILED_UNEXPECTEDLY(hr))
414             return S_OK;
415 
416         int index = SendMessageW(hComboBoxEx, CB_FINDSTRINGEXACT, 0, (LPARAM)buf);
417         if (index != -1)
418             SendMessageW(hComboBoxEx, CB_SETCURSEL, index, 0);
419 
420         /* Add the item that will be visible when the combobox is not expanded */
421         hr = SHBindToParent(absolutePIDL, IID_PPV_ARG(IShellFolder, &sf), &pidlChild);
422         if (FAILED_UNEXPECTEDLY(hr))
423             return hr;
424 
425         hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, &ret);
426         if (FAILED_UNEXPECTEDLY(hr))
427             return hr;
428 
429         hr = StrRetToBufW(&ret, pidlChild, buf, 4095);
430         if (FAILED_UNEXPECTEDLY(hr))
431             return hr;
432 
433         INT indexClosed, indexOpen;
434         indexClosed = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &indexOpen);
435 
436         COMBOBOXEXITEMW item = {0};
437         item.mask = CBEIF_IMAGE | CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_LPARAM;
438         item.iItem = -1;
439         item.iImage = indexClosed;
440         item.iSelectedImage = indexOpen;
441         item.pszText = buf;
442         item.lParam = reinterpret_cast<LPARAM>(absolutePIDL);
443         fCombobox.SendMessage(CBEM_SETITEM, 0, reinterpret_cast<LPARAM>(&item));
444     }
445     return S_OK;
446 }
447 
448 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetClassID(CLSID *pClassID)
449 {
450     if (pClassID == NULL)
451         return E_POINTER;
452     *pClassID = CLSID_AddressEditBox;
453     return S_OK;
454 }
455 
456 HRESULT STDMETHODCALLTYPE CAddressEditBox::IsDirty()
457 {
458     return E_NOTIMPL;
459 }
460 
461 HRESULT STDMETHODCALLTYPE CAddressEditBox::Load(IStream *pStm)
462 {
463     return E_NOTIMPL;
464 }
465 
466 HRESULT STDMETHODCALLTYPE CAddressEditBox::Save(IStream *pStm, BOOL fClearDirty)
467 {
468     return E_NOTIMPL;
469 }
470 
471 HRESULT STDMETHODCALLTYPE CAddressEditBox::GetSizeMax(ULARGE_INTEGER *pcbSize)
472 {
473     return E_NOTIMPL;
474 }
475 
476 void CAddressEditBox::PopulateComboBox(LPITEMIDLIST pidlCurrent)
477 {
478     HRESULT hr;
479     LPITEMIDLIST pidl;
480     int indent = 0;
481     int index;
482 
483     index = SendMessageW(hComboBoxEx, CB_GETCOUNT, 0, 0);
484     for (int i = 0; i < index; i++)
485         SendMessageW(hComboBoxEx, CBEM_DELETEITEM, i, 0);
486     SendMessageW(hComboBoxEx, CB_RESETCONTENT, 0, 0);
487 
488     /* Calculate the indent level. No need to clone the pidl */
489     pidl = pidlCurrent;
490     do
491     {
492         if(!pidl->mkid.cb)
493             break;
494         pidl = ILGetNext(pidl);
495         indent++;
496     } while (pidl);
497     index = indent;
498 
499     /* Add every id from the pidl in the combo box */
500     pidl = ILClone(pidlCurrent);
501     do
502     {
503         AddComboBoxItem(pidl, 0, index);
504         ILRemoveLastID(pidl);
505         index--;
506     } while (index >= 0);
507     ILFree(pidl);
508 
509     /* Add the items of the desktop */
510     FillOneLevel(0, 1, indent);
511 
512     /* Add the items of My Computer */
513     hr = SHGetSpecialFolderLocation(0, CSIDL_DRIVES, &pidl);
514     if (FAILED_UNEXPECTEDLY(hr))
515         return;
516 
517     for(LPITEMIDLIST i = GetItemData(0); i; i = GetItemData(index))
518     {
519         if (ILIsEqual(i, pidl))
520         {
521             FillOneLevel(index, 2, indent);
522             break;
523         }
524         index++;
525     }
526     ILFree(pidl);
527 }
528 
529 void CAddressEditBox::AddComboBoxItem(LPITEMIDLIST pidl, int index, int indent)
530 {
531     HRESULT hr;
532     WCHAR buf[4096];
533 
534     LPCITEMIDLIST pidlChild;
535     CComPtr<IShellFolder> sf;
536     hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &sf), &pidlChild);
537     if (FAILED_UNEXPECTEDLY(hr))
538         return;
539 
540     STRRET strret;
541     hr = sf->GetDisplayNameOf(pidlChild, SHGDN_FORADDRESSBAR, &strret);
542     if (FAILED_UNEXPECTEDLY(hr))
543         return;
544 
545     hr = StrRetToBufW(&strret, pidlChild, buf, 4095);
546     if (FAILED_UNEXPECTEDLY(hr))
547         return;
548 
549     COMBOBOXEXITEMW item = {0};
550     item.mask = CBEIF_LPARAM | CBEIF_INDENT | CBEIF_SELECTEDIMAGE | CBEIF_IMAGE | CBEIF_TEXT;
551     item.iImage = SHMapPIDLToSystemImageListIndex(sf, pidlChild, &item.iSelectedImage);
552     item.pszText = buf;
553     item.lParam = (LPARAM)(ILClone(pidl));
554     item.iIndent = indent;
555     item.iItem = index;
556     SendMessageW(hComboBoxEx, CBEM_INSERTITEMW, 0, (LPARAM)&item);
557 }
558 
559 void CAddressEditBox::FillOneLevel(int index, int levelIndent, int indent)
560 {
561     HRESULT hr;
562     ULONG numObj;
563     int count;
564     LPITEMIDLIST pidl, pidl2, pidl3, pidl4;
565 
566     count = index + 1;
567     pidl = GetItemData(index);
568     pidl2 = GetItemData(count);
569     if(pidl)
570     {
571         CComPtr<IShellFolder> psfDesktop;
572         CComPtr<IShellFolder> psfItem;
573 
574         hr = SHGetDesktopFolder(&psfDesktop);
575         if (FAILED_UNEXPECTEDLY(hr))
576             return;
577 
578         if (!pidl->mkid.cb)
579         {
580             psfItem = psfDesktop;
581         }
582         else
583         {
584             hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfItem));
585             if (FAILED_UNEXPECTEDLY(hr))
586                 return;
587         }
588 
589         CComPtr<IEnumIDList> pEnumIDList;
590         hr = psfItem->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN, &pEnumIDList);
591         if (FAILED_UNEXPECTEDLY(hr))
592             return;
593 
594         do
595         {
596             hr = pEnumIDList->Next(1, &pidl3, &numObj);
597             if(hr != S_OK || !numObj)
598                 break;
599 
600             pidl4 = ILCombine(pidl, pidl3);
601             if (pidl2 && ILIsEqual(pidl4, pidl2))
602                 count += (indent - levelIndent);
603             else
604                 AddComboBoxItem(pidl4, count, levelIndent);
605             count++;
606             ILFree(pidl3);
607             ILFree(pidl4);
608         } while (true);
609     }
610 }
611 
612 LPITEMIDLIST CAddressEditBox::GetItemData(int index)
613 {
614     COMBOBOXEXITEMW item;
615 
616     memset(&item, 0, sizeof(COMBOBOXEXITEMW));
617     item.mask = CBEIF_LPARAM;
618     item.iItem = index;
619     SendMessageW(hComboBoxEx, CBEM_GETITEMW, 0, (LPARAM)&item);
620     return (LPITEMIDLIST)item.lParam;
621 }
622