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 }
596