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