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