1 /*
2  *  Band site menu
3  *
4  *  Copyright 2007  Herv� Poussineua
5  *  Copyright 2009  Andrew Hill
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include "shellbars.h"
23 
24 #include <browseui_undoc.h>
25 
26 /* The menu consists of 3 parts. The first is loaded from the resources,
27    the second is populated with the classes of the CATID_DeskBand comcat
28    and the third part consists of the entries for each CISFBand in the band side.
29    The first 5 ids are reserved for the resource menu, the following ids will be
30    for the CATID_DeskBand classes and the rest for the CISFBands.
31    The ids for the CISFBand menu items are not continuous, in this range
32    each menu id is calculated by adding the band id to the last id for the CATID_DeskBand range */
33 #define FIRST_COMCAT_MENU_ID 0x5
34 
35 CBandSiteMenu::CBandSiteMenu():
36     m_hmenu(NULL),
37     m_DesktopPidl(NULL),
38     m_QLaunchPidl(NULL)
39 {
40 }
41 
42 CBandSiteMenu::~CBandSiteMenu()
43 {
44     if (m_hmenu)
45         DestroyMenu(m_hmenu);
46 
47     m_BandSite = NULL;
48 }
49 
50 HRESULT WINAPI CBandSiteMenu::FinalConstruct()
51 {
52     return S_OK;
53 }
54 
55 HRESULT CBandSiteMenu::_CreateMenuPart()
56 {
57     WCHAR wszBandName[MAX_PATH];
58     WCHAR wszBandGUID[MAX_PATH];
59     WCHAR wRegKey[MAX_PATH];
60     UINT cBands;
61     DWORD dwDataSize;
62     CATID category = CATID_DeskBand;
63     HMENU hmenuToolbars;
64     DWORD dwRead;
65     CComPtr<IEnumGUID> pEnumGUID;
66     HRESULT hr;
67 
68     if (m_hmenu)
69         DestroyMenu(m_hmenu);
70 
71     /* Load the template we will fill in */
72     m_hmenu = LoadMenuW(GetModuleHandleW(L"browseui.dll"), MAKEINTRESOURCEW(IDM_TASKBAR_TOOLBARS));
73     if (!m_hmenu)
74         return HRESULT_FROM_WIN32(GetLastError());
75 
76     /* Get the handle of the submenu where the available items will be shown */
77     hmenuToolbars = GetSubMenu(m_hmenu, 0);
78 
79     /* Create the category enumerator */
80     hr = SHEnumClassesOfCategories(1, &category, 0, NULL, &pEnumGUID);
81     if (FAILED_UNEXPECTEDLY(hr))
82         return hr;
83 
84     m_ComCatGuids.RemoveAll();
85 
86     /* Enumerate the classes in the  CATID_DeskBand category */
87     cBands = 0;
88     do
89     {
90         GUID iter;
91         pEnumGUID->Next(1, &iter, &dwRead);
92         if (!dwRead)
93             continue;
94 
95         if (!StringFromGUID2(iter, wszBandGUID, MAX_PATH))
96             continue;
97 
98         /* Get the band name */
99         StringCchPrintfW(wRegKey, MAX_PATH, L"CLSID\\%s", wszBandGUID);
100         dwDataSize = MAX_PATH;
101         SHGetValue(HKEY_CLASSES_ROOT, wRegKey, NULL, NULL, wszBandName, &dwDataSize);
102 
103         /* Insert it */
104         InsertMenu(hmenuToolbars, cBands, MF_BYPOSITION, m_ComCatGuids.GetSize() + FIRST_COMCAT_MENU_ID, wszBandName);
105         m_ComCatGuids.Add(iter);
106         cBands++;
107     }
108     while (dwRead > 0);
109 
110     return S_OK;
111 }
112 
113 HRESULT CBandSiteMenu::_CreateNewISFBand(HWND hwnd, REFIID riid, void** ppv)
114 {
115     WCHAR path[MAX_PATH];
116     WCHAR message[256];
117     BROWSEINFOW bi = { hwnd, NULL, path };
118 
119     if (LoadStringW(GetModuleHandleW(L"browseui.dll"), IDS_BROWSEFORNEWTOOLAR, message, _countof(message)))
120         bi.lpszTitle = message;
121     else
122         bi.lpszTitle = L"Choose a folder";
123 
124     CComHeapPtr<ITEMIDLIST> pidlSelected;
125     pidlSelected.Attach(SHBrowseForFolderW(&bi));
126     if (pidlSelected == NULL)
127         return S_FALSE;
128 
129     CComPtr<IShellFolderBand> pISFB;
130     HRESULT hr = CISFBand_CreateInstance(IID_IShellFolderBand, (PVOID*)&pISFB);
131     if (FAILED_UNEXPECTEDLY(hr))
132         return hr;
133 
134     hr = pISFB->InitializeSFB(NULL, pidlSelected);
135     if (FAILED_UNEXPECTEDLY(hr))
136         return hr;
137 
138     return pISFB->QueryInterface(riid, ppv);
139 }
140 
141 LPITEMIDLIST CBandSiteMenu::_GetQLaunchPidl(BOOL refresh)
142 {
143     if (m_QLaunchPidl != NULL)
144     {
145         if (refresh)
146             m_QLaunchPidl.Free();
147         else
148             return m_QLaunchPidl;
149     }
150 
151     WCHAR buffer[MAX_PATH];
152     HRESULT hr = SHGetFolderPathAndSubDirW(0, CSIDL_APPDATA, NULL, 0, L"Microsoft\\Internet Explorer\\Quick Launch", buffer);
153     if (FAILED_UNEXPECTEDLY(hr))
154         return NULL;
155 
156     m_QLaunchPidl.Attach(ILCreateFromPathW(buffer));
157     return m_QLaunchPidl;
158 }
159 
160 HRESULT CBandSiteMenu::_CreateBuiltInISFBand(UINT uID, REFIID riid, void** ppv)
161 {
162     LPITEMIDLIST pidl;
163     HRESULT hr;
164 
165     switch (uID)
166     {
167         case IDM_TASKBAR_TOOLBARS_DESKTOP:
168         {
169             if (m_DesktopPidl != NULL)
170                 m_DesktopPidl.Free();
171 
172             hr = SHGetFolderLocation(0, CSIDL_DESKTOP, NULL, 0, &m_DesktopPidl);
173             if (FAILED_UNEXPECTEDLY(hr))
174                 return hr;
175 
176             pidl = m_DesktopPidl;
177             break;
178         }
179         case IDM_TASKBAR_TOOLBARS_QUICKLAUNCH:
180         {
181             pidl = _GetQLaunchPidl(true);
182             break;
183         }
184     }
185 
186     if (pidl == NULL)
187         return E_FAIL;
188 
189     CComPtr<IShellFolderBand> pISFB;
190     hr = CISFBand_CreateInstance(IID_IShellFolderBand, (PVOID*)&pISFB);
191     if (FAILED_UNEXPECTEDLY(hr))
192         return hr;
193 
194     hr = pISFB->InitializeSFB(NULL, pidl);
195     if (FAILED_UNEXPECTEDLY(hr))
196         return hr;
197 
198     /* HACK! We shouldn't pass ISFB_STATE_QLINKSMODE and CISFBand shouldn't handle it! */
199     if (uID == IDM_TASKBAR_TOOLBARS_QUICKLAUNCH)
200     {
201         BANDINFOSFB bisfb = {ISFB_MASK_STATE, ISFB_STATE_QLINKSMODE, ISFB_STATE_QLINKSMODE};
202         pISFB->SetBandInfoSFB(&bisfb);
203     }
204 
205     return pISFB->QueryInterface(riid, ppv);
206 }
207 
208 HRESULT CBandSiteMenu::_AddISFBandToMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, IUnknown* pBand, DWORD dwBandID, UINT *newMenuId)
209 {
210     CComPtr<IShellFolderBand> psfb;
211     HRESULT hr = pBand->QueryInterface(IID_PPV_ARG(IShellFolderBand, &psfb));
212     if (FAILED_UNEXPECTEDLY(hr))
213         return hr;
214 
215     BANDINFOSFB bi = {ISFB_MASK_IDLIST};
216     hr = psfb->GetBandInfoSFB(&bi);
217     if (FAILED_UNEXPECTEDLY(hr))
218         return hr;
219 
220     CComHeapPtr<ITEMIDLIST> pidl(bi.pidl);
221     if (!pidl)
222     {
223         ERR("Failed to get the pidl of the CISFBand\n");
224         return E_OUTOFMEMORY;
225     }
226 
227     WCHAR buffer[MAX_PATH];
228     hr = ILGetDisplayNameEx(NULL, pidl, buffer, ILGDN_INFOLDER) ? S_OK : E_FAIL;
229     if (FAILED_UNEXPECTEDLY(hr))
230         return hr;
231 
232     UINT id = idCmdFirst + m_ComCatGuids.GetSize() + FIRST_COMCAT_MENU_ID + dwBandID;
233     if (id >= idCmdLast)
234         return E_FAIL;
235 
236     *newMenuId = id;
237     InsertMenu(hmenu, indexMenu, MF_BYPOSITION, id, buffer);
238     return S_OK;
239 }
240 
241 UINT CBandSiteMenu::_GetMenuIdFromISFBand(IUnknown *pBand)
242 {
243     CComPtr<IShellFolderBand> psfb;
244     HRESULT hr = pBand->QueryInterface(IID_PPV_ARG(IShellFolderBand, &psfb));
245     if (FAILED_UNEXPECTEDLY(hr))
246         return UINT_MAX;
247 
248     BANDINFOSFB bi = {ISFB_MASK_IDLIST};
249     hr = psfb->GetBandInfoSFB(&bi);
250     if (FAILED_UNEXPECTEDLY(hr))
251         return UINT_MAX;
252 
253     CComHeapPtr<ITEMIDLIST> pidl(bi.pidl);
254     if (!pidl)
255     {
256         ERR("Failed to get the pidl of the CISFBand\n");
257         return UINT_MAX;
258     }
259 
260     if (pidl->mkid.cb == 0)
261     {
262         return IDM_TASKBAR_TOOLBARS_DESKTOP;
263     }
264 
265     CComPtr<IShellFolder> psfDesktop;
266     hr = SHGetDesktopFolder(&psfDesktop);
267     if (FAILED_UNEXPECTEDLY(hr))
268         return UINT_MAX;
269 
270     LPITEMIDLIST _QLaunchPidl = _GetQLaunchPidl(false);
271     if (_QLaunchPidl == NULL)
272         return UINT_MAX;
273 
274     hr = psfDesktop->CompareIDs(0, pidl, _QLaunchPidl);
275     if (FAILED_UNEXPECTEDLY(hr))
276         return UINT_MAX;
277 
278     if (HRESULT_CODE(hr) == 0)
279         return IDM_TASKBAR_TOOLBARS_QUICKLAUNCH;
280 
281     return UINT_MAX;
282 }
283 
284 UINT CBandSiteMenu::_GetBandIdFromClsid(CLSID* pclsid)
285 {
286     CComPtr<IPersist> pBand;
287     CLSID BandCLSID;
288     DWORD dwBandID;
289 
290     for (UINT uBand = 0; SUCCEEDED(m_BandSite->EnumBands(uBand, &dwBandID)); uBand++)
291     {
292         if (FAILED(m_BandSite->GetBandObject(dwBandID, IID_PPV_ARG(IPersist, &pBand))))
293             continue;
294 
295         if (FAILED_UNEXPECTEDLY(pBand->GetClassID(&BandCLSID)))
296             continue;
297 
298         if (IsEqualGUID(*pclsid, BandCLSID))
299             return dwBandID;
300     }
301 
302     return UINT_MAX;
303 }
304 
305 UINT CBandSiteMenu::_GetBandIdForBuiltinISFBand(UINT uID)
306 {
307     CComPtr<IPersist> pBand;
308     CLSID BandCLSID;
309     DWORD dwBandID;
310 
311     for (UINT uBand = 0; SUCCEEDED(m_BandSite->EnumBands(uBand, &dwBandID)); uBand++)
312     {
313         if (FAILED(m_BandSite->GetBandObject(dwBandID, IID_PPV_ARG(IPersist, &pBand))))
314             continue;
315 
316         if (FAILED_UNEXPECTEDLY(pBand->GetClassID(&BandCLSID)))
317             continue;
318 
319         if (!IsEqualGUID(BandCLSID, CLSID_ISFBand))
320             continue;
321 
322         UINT menuID = _GetMenuIdFromISFBand(pBand);
323         if (menuID == uID)
324             return dwBandID;
325     }
326 
327     return UINT_MAX;
328 }
329 
330 HRESULT STDMETHODCALLTYPE CBandSiteMenu::SetOwner(IUnknown *pOwner)
331 {
332     TRACE("CBandSiteMenu::SetOwner(%p, %p)\n", this, pOwner);
333 
334     /* Cache the menu that will be merged every time QueryContextMenu is called */
335     _CreateMenuPart();
336 
337     return pOwner->QueryInterface(IID_PPV_ARG(IBandSite, &m_BandSite));
338 }
339 
340 HRESULT STDMETHODCALLTYPE CBandSiteMenu::QueryContextMenu(
341     HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
342 {
343     CComPtr<IPersist> pBand;
344     CLSID BandCLSID;
345     DWORD dwBandID;
346     UINT idMax;
347 
348     TRACE("CBandSiteMenu::QueryContextMenu(%p, %p, %u, %u, %u, 0x%x)\n", this, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
349 
350     /* First Merge the menu with the available bands */
351     idMax = Shell_MergeMenus(hmenu, m_hmenu, indexMenu, idCmdFirst, idCmdLast, MM_DONTREMOVESEPS | MM_SUBMENUSHAVEIDS);
352 
353     HMENU hmenuToolbars = GetSubMenu(hmenu, indexMenu);
354 
355     /* Enumerate all present bands and mark them as checked in the menu */
356     for (UINT uBand = 0; SUCCEEDED(m_BandSite->EnumBands(uBand, &dwBandID)); uBand++)
357     {
358         if (FAILED(m_BandSite->GetBandObject(dwBandID, IID_PPV_ARG(IPersist, &pBand))))
359             continue;
360 
361         if (FAILED_UNEXPECTEDLY(pBand->GetClassID(&BandCLSID)))
362             continue;
363 
364         UINT menuID;
365         if (IsEqualGUID(BandCLSID, CLSID_ISFBand))
366         {
367             menuID = _GetMenuIdFromISFBand(pBand);
368             if (menuID == UINT_MAX)
369             {
370                 HRESULT hr;
371                 hr = _AddISFBandToMenu(hmenuToolbars, 0, idCmdFirst, idCmdLast, pBand, dwBandID, &menuID);
372                 if (SUCCEEDED(hr) && menuID > idMax)
373                     idMax = menuID;
374                 menuID -= idCmdFirst;
375             }
376         }
377         else
378         {
379             int i = m_ComCatGuids.Find(BandCLSID);
380             menuID = (i == -1 ? UINT_MAX : i + FIRST_COMCAT_MENU_ID);
381         }
382 
383         if (menuID != UINT_MAX)
384             CheckMenuItem(hmenuToolbars, menuID + idCmdFirst, MF_CHECKED);
385     }
386 
387     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(idMax - idCmdFirst +1));
388 }
389 
390 HRESULT CBandSiteMenu::_ShowToolbarError(HRESULT hRet)
391 {
392     WCHAR szText[260];
393     WCHAR szTitle[256];
394 
395     if (!LoadStringW(GetModuleHandleW(L"browseui.dll"), IDS_TOOLBAR_ERR_TEXT, szText, _countof(szText)))
396         StringCchCopyW(szText, _countof(szText), L"Cannot create toolbar.");
397 
398     if (!LoadStringW(GetModuleHandleW(L"browseui.dll"), IDS_TOOLBAR_ERR_TITLE, szTitle, _countof(szTitle)))
399         StringCchCopyW(szTitle, _countof(szTitle), L"Toolbar");
400 
401     MessageBoxW(NULL, szText, szTitle, MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
402     return hRet;
403 }
404 
405 HRESULT STDMETHODCALLTYPE CBandSiteMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
406 {
407     HRESULT hRet;
408     DWORD dwBandID;
409 
410     /* FIXME: do we need to handle this and how? */
411     if (HIWORD(lpici->lpVerb) != NULL)
412         return E_FAIL;
413 
414     UINT uID = LOWORD(lpici->lpVerb);
415     if (uID == IDM_TASKBAR_TOOLBARS_NEW)
416     {
417         CComPtr<IDeskBand> pDeskBand;
418         hRet = _CreateNewISFBand(lpici->hwnd, IID_PPV_ARG(IDeskBand, &pDeskBand));
419         if (FAILED_UNEXPECTEDLY(hRet))
420             return hRet;
421 
422         hRet = m_BandSite->AddBand(pDeskBand);
423         if (FAILED_UNEXPECTEDLY(hRet))
424             return hRet;
425 
426         return S_OK;
427     }
428     else if (uID > (UINT) m_ComCatGuids.GetSize() + FIRST_COMCAT_MENU_ID )
429     {
430         dwBandID = uID - (m_ComCatGuids.GetSize() + FIRST_COMCAT_MENU_ID );
431 
432         m_BandSite->RemoveBand(dwBandID);
433 
434         return S_OK;
435     }
436     else if (uID == IDM_TASKBAR_TOOLBARS_DESKTOP || uID == IDM_TASKBAR_TOOLBARS_QUICKLAUNCH)
437     {
438         dwBandID = _GetBandIdForBuiltinISFBand(uID);
439         if (dwBandID != UINT_MAX)
440         {
441             m_BandSite->RemoveBand(dwBandID);
442         }
443         else
444         {
445             CComPtr<IDeskBand> pDeskBand;
446             hRet = _CreateBuiltInISFBand(uID, IID_PPV_ARG(IDeskBand, &pDeskBand));
447             if (FAILED_UNEXPECTEDLY(hRet))
448                 return _ShowToolbarError(hRet);
449 
450             hRet = m_BandSite->AddBand(pDeskBand);
451             if (FAILED_UNEXPECTEDLY(hRet))
452                 return _ShowToolbarError(hRet);
453         }
454         return S_OK;
455     }
456 
457     /* Get the GUID of the item that was clicked */
458     GUID *pguidToolbar = &m_ComCatGuids[uID - FIRST_COMCAT_MENU_ID];
459     if (!pguidToolbar)
460         return E_FAIL;
461 
462     /* Try to find if a band with a guid is present. If it is, remove it and return */
463     dwBandID = _GetBandIdFromClsid(pguidToolbar);
464     if (dwBandID != UINT_MAX)
465     {
466         /* We found it, remove it */
467         m_BandSite->RemoveBand(dwBandID);
468     }
469     else
470     {
471         /* It is not present. Add it. */
472         CComPtr<IDeskBand> pDeskBand;
473         hRet = CoCreateInstance(*pguidToolbar, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IDeskBand, &pDeskBand));
474         if (FAILED_UNEXPECTEDLY(hRet))
475             return hRet;
476 
477         hRet = m_BandSite->AddBand(pDeskBand);
478         if (FAILED_UNEXPECTEDLY(hRet))
479             return hRet;
480     }
481 
482     return S_OK;
483 }
484 
485 HRESULT STDMETHODCALLTYPE CBandSiteMenu::GetCommandString(UINT_PTR idCmd, UINT uType,
486     UINT *pwReserved, LPSTR pszName, UINT cchMax)
487 {
488     FIXME("CBandSiteMenu::GetCommandString is UNIMPLEMENTED (%p, %p, %u, %p, %p, %u)\n", this, idCmd, uType, pwReserved, pszName, cchMax);
489     return E_NOTIMPL;
490 }
491 
492 HRESULT STDMETHODCALLTYPE CBandSiteMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
493 {
494     FIXME("CBandSiteMenu::HandleMenuMsg is UNIMPLEMENTED (%p, %u, %p, %p)\n", this, uMsg, wParam, lParam);
495     return E_NOTIMPL;
496 }
497 
498 HRESULT STDMETHODCALLTYPE CBandSiteMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
499 {
500     FIXME("CBandSiteMenu::HandleMenuMsg2 is UNIMPLEMENTED(%p, %u, %p, %p, %p)\n", this, uMsg, wParam, lParam, plResult);
501     return E_NOTIMPL;
502 }
503 
504 extern "C"
505 HRESULT WINAPI RSHELL_CBandSiteMenu_CreateInstance(REFIID riid, void **ppv)
506 {
507     return ShellObjectCreator<CBandSiteMenu>(riid, ppv);
508 }
509