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