1 /*
2  * PROJECT:     shell32
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        dll/win32/shell32/CDefViewBckgrndMenu.cpp
5  * PURPOSE:     background context menu of the CDefView
6  * PROGRAMMERS: Giannis Adamopoulos
7  */
8 
9 #include <precomp.h>
10 
11 WINE_DEFAULT_DEBUG_CHANNEL(shell);
12 
13 class CDefViewBckgrndMenu :
14     public CComObjectRootEx<CComMultiThreadModelNoCS>,
15     public IContextMenu3,
16     public IObjectWithSite
17 {
18     private:
19         CComPtr<IUnknown> m_site;
20         CComPtr<IShellFolder> m_psf;
21         CComPtr<IContextMenu> m_folderCM;
22 
23         UINT m_idCmdFirst;
24         UINT m_LastFolderCMId;
25 
26         BOOL _bIsDesktopBrowserMenu();
27         BOOL _bCanPaste();
28     public:
29         CDefViewBckgrndMenu();
30         ~CDefViewBckgrndMenu();
31         HRESULT Initialize(IShellFolder* psf);
32 
33         // IContextMenu
34         virtual HRESULT WINAPI QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
35         virtual HRESULT WINAPI InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi);
36         virtual HRESULT WINAPI GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen);
37 
38         // IContextMenu2
39         virtual HRESULT WINAPI HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam);
40 
41         // IContextMenu3
42         virtual HRESULT WINAPI HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult);
43 
44         // IObjectWithSite
45         virtual HRESULT STDMETHODCALLTYPE SetSite(IUnknown *pUnkSite);
46         virtual HRESULT STDMETHODCALLTYPE GetSite(REFIID riid, void **ppvSite);
47 
48         BEGIN_COM_MAP(CDefViewBckgrndMenu)
49         COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
50         COM_INTERFACE_ENTRY_IID(IID_IContextMenu2, IContextMenu2)
51         COM_INTERFACE_ENTRY_IID(IID_IContextMenu3, IContextMenu3)
52         COM_INTERFACE_ENTRY_IID(IID_IObjectWithSite, IObjectWithSite)
53         END_COM_MAP()
54 };
55 
56 CDefViewBckgrndMenu::CDefViewBckgrndMenu()
57 {
58     m_idCmdFirst = 0;
59     m_LastFolderCMId = 0;
60 }
61 
62 CDefViewBckgrndMenu::~CDefViewBckgrndMenu()
63 {
64 }
65 
66 BOOL CDefViewBckgrndMenu::_bIsDesktopBrowserMenu()
67 {
68     if (!m_site)
69         return FALSE;
70 
71     /* Get a pointer to the shell browser */
72     CComPtr<IShellView> psv;
73     HRESULT hr = IUnknown_QueryService(m_site, SID_IFolderView, IID_PPV_ARG(IShellView, &psv));
74     if (FAILED_UNEXPECTEDLY(hr))
75         return FALSE;
76 
77     FOLDERSETTINGS FolderSettings;
78     hr = psv->GetCurrentInfo(&FolderSettings);
79     if (FAILED_UNEXPECTEDLY(hr))
80         return FALSE;
81 
82     return ((FolderSettings.fFlags & FWF_DESKTOP) == FWF_DESKTOP);
83 }
84 
85 BOOL CDefViewBckgrndMenu::_bCanPaste()
86 {
87     /* If the folder doesn't have a drop target we can't paste */
88     CComPtr<IDropTarget> pdt;
89     HRESULT hr = m_psf->CreateViewObject(NULL, IID_PPV_ARG(IDropTarget, &pdt));
90     if (FAILED(hr))
91         return FALSE;
92 
93     /* We can only paste if CFSTR_SHELLIDLIST is present in the clipboard */
94     CComPtr<IDataObject> pDataObj;
95     hr = OleGetClipboard(&pDataObj);
96     if (FAILED(hr))
97         return FALSE;
98 
99     STGMEDIUM medium;
100     FORMATETC formatetc;
101 
102     /* Set the FORMATETC structure*/
103     InitFormatEtc(formatetc, RegisterClipboardFormatW(CFSTR_SHELLIDLIST), TYMED_HGLOBAL);
104     hr = pDataObj->GetData(&formatetc, &medium);
105     if (FAILED(hr))
106         return FALSE;
107 
108     ReleaseStgMedium(&medium);
109     return TRUE;
110 }
111 
112 HRESULT
113 CDefViewBckgrndMenu::Initialize(IShellFolder* psf)
114 {
115     m_psf = psf;
116 
117     /* Get the context menu of the folder. Do it here because someone may call
118        InvokeCommand without calling QueryContextMenu. It is fine if this fails */
119     m_psf->CreateViewObject(NULL, IID_PPV_ARG(IContextMenu, &m_folderCM));
120 
121     return S_OK;
122 }
123 
124 HRESULT
125 WINAPI
126 CDefViewBckgrndMenu::SetSite(IUnknown *pUnkSite)
127 {
128     m_site = pUnkSite;
129 
130     if(m_folderCM)
131         IUnknown_SetSite(m_folderCM, pUnkSite);
132 
133     return S_OK;
134 }
135 
136 HRESULT
137 WINAPI
138 CDefViewBckgrndMenu::GetSite(REFIID riid, void **ppvSite)
139 {
140     if (!m_site)
141         return E_FAIL;
142 
143     return m_site->QueryInterface(riid, ppvSite);
144 }
145 
146 HRESULT
147 WINAPI
148 CDefViewBckgrndMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
149 {
150     HRESULT hr;
151     HMENU hMenuPart;
152     UINT cIds = 0;
153 
154     /* This is something the implementations of IContextMenu should never really do.
155        However CDefViewBckgrndMenu is more or less an overengineering result, its code could really be part of the
156        CDefView. Given this, I think that abusing the interface here is not that bad since only CDefView is the ony
157        user of this class. Here we need to do two things to keep things as simple as possible.
158        First we want the menu part added by the shell folder to be the first to add so as to make as few id translations
159        as possible. Second, we want to add the default part of the background menu without shifted ids, so as
160        to let the CDefView fill some parts like filling the arrange modes or checking the view mode. In order
161        for that to work we need to save idCmdFirst because our caller will pass id offsets to InvokeCommand.
162        This makes it impossible to concatenate the CDefViewBckgrndMenu with other menus since it abuses IContextMenu
163        but as stated above, its sole user is CDefView and should really be that way. */
164     m_idCmdFirst = idCmdFirst;
165 
166     /* Let the shell folder add any items it wants to add in the background context menu */
167     if (m_folderCM)
168     {
169         hr = m_folderCM->QueryContextMenu(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
170         if (SUCCEEDED(hr))
171         {
172             m_LastFolderCMId = LOWORD(hr);
173             cIds = m_LastFolderCMId;
174         }
175         else
176         {
177             WARN("QueryContextMenu failed!\n");
178         }
179     }
180     else
181     {
182         WARN("GetUIObjectOf didn't give any context menu!\n");
183     }
184 
185     /* Load the default part of the background context menu */
186     hMenuPart = LoadMenuW(shell32_hInstance, L"MENU_002");
187     if (hMenuPart)
188     {
189         /* Don't show the view submenu for the desktop */
190         if (_bIsDesktopBrowserMenu())
191         {
192             DeleteMenu(hMenuPart, FCIDM_SHVIEW_VIEW, MF_BYCOMMAND);
193         }
194 
195         /* Disable the paste options if it is not possible */
196         if (!_bCanPaste())
197         {
198             EnableMenuItem(hMenuPart, FCIDM_SHVIEW_INSERT, MF_BYCOMMAND | MF_GRAYED);
199             EnableMenuItem(hMenuPart, FCIDM_SHVIEW_INSERTLINK, MF_BYCOMMAND | MF_GRAYED);
200         }
201 
202         /* merge general background context menu in */
203         Shell_MergeMenus(hMenu, GetSubMenu(hMenuPart, 0), indexMenu, 0, idCmdLast, MM_DONTREMOVESEPS | MM_SUBMENUSHAVEIDS | MM_ADDSEPARATOR);
204         DestroyMenu(hMenuPart);
205     }
206     else
207     {
208         ERR("Failed to load menu from resource!\n");
209     }
210 
211     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
212 }
213 
214 HRESULT
215 WINAPI
216 CDefViewBckgrndMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
217 {
218     UINT idCmd = LOWORD(lpcmi->lpVerb);
219 
220     if (HIWORD(lpcmi->lpVerb) && !strcmp(lpcmi->lpVerb, CMDSTR_VIEWLISTA))
221     {
222         idCmd = FCIDM_SHVIEW_LISTVIEW;
223     }
224     else if (HIWORD(lpcmi->lpVerb) && !strcmp(lpcmi->lpVerb, CMDSTR_VIEWDETAILSA))
225     {
226         idCmd = FCIDM_SHVIEW_REPORTVIEW;
227     }
228     else if(HIWORD(lpcmi->lpVerb) != 0 || idCmd < m_LastFolderCMId)
229     {
230         if (m_folderCM)
231         {
232             return m_folderCM->InvokeCommand(lpcmi);
233         }
234         WARN("m_folderCM is NULL!\n");
235         return E_NOTIMPL;
236     }
237     else
238     {
239         /* The default part of the background menu doesn't have shifted ids so we need to convert the id offset to the real id */
240         idCmd += m_idCmdFirst;
241     }
242 
243     /* The commands that are handled by the def view are forwarded to it */
244     switch (idCmd)
245     {
246     case FCIDM_SHVIEW_INSERT:
247     case FCIDM_SHVIEW_INSERTLINK:
248         if (m_folderCM)
249         {
250             lpcmi->lpVerb = MAKEINTRESOURCEA(idCmd);
251             return m_folderCM->InvokeCommand(lpcmi);
252         }
253         WARN("m_folderCM is NULL!\n");
254         return E_NOTIMPL;
255     case FCIDM_SHVIEW_BIGICON:
256     case FCIDM_SHVIEW_SMALLICON:
257     case FCIDM_SHVIEW_LISTVIEW:
258     case FCIDM_SHVIEW_REPORTVIEW:
259     case 0x30: /* FIX IDS in resource files */
260     case 0x31:
261     case 0x32:
262     case 0x33:
263     case FCIDM_SHVIEW_AUTOARRANGE:
264     case FCIDM_SHVIEW_SNAPTOGRID:
265     case FCIDM_SHVIEW_REFRESH:
266     case FCIDM_SHVIEW_ALIGNTOGRID:
267         if (!m_site)
268             return E_FAIL;
269 
270         /* Get a pointer to the shell browser */
271         CComPtr<IShellView> psv;
272         HRESULT hr = IUnknown_QueryService(m_site, SID_IFolderView, IID_PPV_ARG(IShellView, &psv));
273         if (FAILED_UNEXPECTEDLY(hr))
274             return hr;
275 
276         HWND hwndSV = NULL;
277         if (SUCCEEDED(psv->GetWindow(&hwndSV)))
278             SendMessageW(hwndSV, WM_COMMAND, MAKEWPARAM(idCmd, 0), 0);
279         return S_OK;
280     }
281 
282     ERR("Got unknown command id %ul\n", LOWORD(lpcmi->lpVerb));
283     return E_FAIL;
284 }
285 
286 HRESULT
287 WINAPI
288 CDefViewBckgrndMenu::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
289 {
290     if (m_folderCM)
291     {
292         return m_folderCM->GetCommandString(idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
293     }
294 
295     return E_NOTIMPL;
296 }
297 
298 HRESULT
299 WINAPI
300 CDefViewBckgrndMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
301 {
302     if(m_folderCM)
303     {
304         CComPtr<IContextMenu2> pfolderCM2;
305         HRESULT hr = m_folderCM->QueryInterface(IID_PPV_ARG(IContextMenu2, &pfolderCM2));
306         if (FAILED_UNEXPECTEDLY(hr))
307             return hr;
308 
309         return pfolderCM2->HandleMenuMsg(uMsg, wParam, lParam);
310     }
311 
312     return E_NOTIMPL;
313 }
314 
315 HRESULT
316 WINAPI
317 CDefViewBckgrndMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
318 {
319     if(m_folderCM)
320     {
321         CComPtr<IContextMenu3> pfolderCM3;
322         HRESULT hr = m_folderCM->QueryInterface(IID_PPV_ARG(IContextMenu3, &pfolderCM3));
323         if (FAILED_UNEXPECTEDLY(hr))
324             return hr;
325 
326         return pfolderCM3->HandleMenuMsg2(uMsg, wParam, lParam, plResult);
327     }
328 
329     return E_NOTIMPL;
330 }
331 
332 
333 HRESULT
334 CDefViewBckgrndMenu_CreateInstance(IShellFolder* psf, REFIID riid, void **ppv)
335 {
336     return ShellObjectCreatorInit<CDefViewBckgrndMenu>(psf, riid, ppv);
337 }
338