xref: /reactos/dll/win32/shell32/CSendToMenu.cpp (revision ede7a20a)
1 /*
2  * provides SendTo shell item service
3  *
4  * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include "precomp.h"
22 
23 WINE_DEFAULT_DEBUG_CHANNEL(shell);
24 
25 CSendToMenu::CSendToMenu()
26     : m_hSubMenu(NULL)
27     , m_pItems(NULL)
28     , m_idCmdFirst(0)
29 {
30     HRESULT hr = SHGetDesktopFolder(&m_pDesktop);
31     if (FAILED(hr))
32     {
33         ERR("SHGetDesktopFolder: %08lX\n", hr);
34     }
35 
36     GetSpecialFolder(NULL, &m_pSendTo, CSIDL_SENDTO);
37 }
38 
39 CSendToMenu::~CSendToMenu()
40 {
41     UnloadAllItems();
42 
43     if (m_hSubMenu)
44     {
45         DestroyMenu(m_hSubMenu);
46         m_hSubMenu = NULL;
47     }
48 }
49 
50 HRESULT CSendToMenu::DoDrop(IDataObject *pDataObject, IDropTarget *pDropTarget)
51 {
52     DWORD dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK;
53 
54     BOOL bShift = (GetAsyncKeyState(VK_SHIFT) < 0);
55     BOOL bCtrl = (GetAsyncKeyState(VK_CONTROL) < 0);
56 
57     // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY.
58     // (We have to translate a SendTo action to a Drop action)
59     DWORD dwKeyState = MK_LBUTTON;
60     if (bShift && bCtrl)
61         dwKeyState |= MK_SHIFT | MK_CONTROL;
62     else if (!bShift)
63         dwKeyState |= MK_CONTROL;
64     if (bCtrl)
65         dwKeyState |= MK_SHIFT;
66 
67     POINTL ptl = { 0, 0 };
68     HRESULT hr = pDropTarget->DragEnter(pDataObject, dwKeyState, ptl, &dwEffect);
69     if (FAILED_UNEXPECTEDLY(hr))
70     {
71         pDropTarget->DragLeave();
72         return hr;
73     }
74 
75     if (dwEffect == DROPEFFECT_NONE)
76     {
77         ERR("DROPEFFECT_NONE\n");
78         pDropTarget->DragLeave();
79         return E_FAIL;
80     }
81 
82     // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY.
83     // (We have to translate a SendTo action to a Drop action)
84     if (bShift && bCtrl)
85         dwEffect = DROPEFFECT_LINK;
86     else if (!bShift)
87         dwEffect = DROPEFFECT_MOVE;
88     else
89         dwEffect = DROPEFFECT_COPY;
90 
91     hr = pDropTarget->Drop(pDataObject, dwKeyState, ptl, &dwEffect);
92     if (FAILED_UNEXPECTEDLY(hr))
93         return hr;
94 
95     return hr;
96 }
97 
98 // get an IShellFolder from CSIDL
99 HRESULT
100 CSendToMenu::GetSpecialFolder(HWND hwnd, IShellFolder **ppFolder,
101                               int csidl, PIDLIST_ABSOLUTE *ppidl)
102 {
103     if (!ppFolder)
104         return E_POINTER;
105     *ppFolder = NULL;
106 
107     if (ppidl)
108         *ppidl = NULL;
109 
110     CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidl;
111     HRESULT hr = SHGetSpecialFolderLocation(hwnd, csidl, &pidl);
112     if (FAILED_UNEXPECTEDLY(hr))
113         return hr;
114 
115     IShellFolder *pFolder = NULL;
116     hr = m_pDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &pFolder));
117 
118     if (ppidl)
119         *ppidl = pidl.Detach();
120 
121     if (FAILED_UNEXPECTEDLY(hr))
122         return hr;
123 
124     *ppFolder = pFolder;
125     return hr;
126 }
127 
128 // get a UI object from PIDL
129 HRESULT CSendToMenu::GetUIObjectFromPidl(HWND hwnd, PIDLIST_ABSOLUTE pidl,
130                                          REFIID riid, LPVOID *ppvOut)
131 {
132     *ppvOut = NULL;
133 
134     PCITEMID_CHILD pidlLast;
135     CComPtr<IShellFolder> pFolder;
136     HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pFolder), &pidlLast);
137     if (FAILED_UNEXPECTEDLY(hr))
138         return hr;
139 
140     hr = pFolder->GetUIObjectOf(hwnd, 1, &pidlLast, riid, NULL, ppvOut);
141     if (FAILED_UNEXPECTEDLY(hr))
142         return hr;
143 
144     return hr;
145 }
146 
147 void CSendToMenu::UnloadAllItems()
148 {
149     SENDTO_ITEM *pItems = m_pItems;
150     m_pItems = NULL;
151     while (pItems)
152     {
153         SENDTO_ITEM *pCurItem = pItems;
154         pItems = pItems->pNext;
155         delete pCurItem;
156     }
157 }
158 
159 HRESULT CSendToMenu::LoadAllItems(HWND hwnd)
160 {
161     UnloadAllItems();
162 
163     CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlSendTo;
164 
165     m_pSendTo.Release();
166     HRESULT hr = GetSpecialFolder(hwnd, &m_pSendTo, CSIDL_SENDTO, &pidlSendTo);
167     if (FAILED_UNEXPECTEDLY(hr))
168         return hr;
169 
170     CComPtr<IEnumIDList> pEnumIDList;
171     hr = m_pSendTo->EnumObjects(hwnd,
172                                 SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,
173                                 &pEnumIDList);
174     if (FAILED_UNEXPECTEDLY(hr))
175         return hr;
176 
177     hr = S_OK;
178     PITEMID_CHILD child;
179     while (pEnumIDList->Next(1, &child, NULL) == S_OK)
180     {
181         CComHeapPtr<ITEMID_CHILD> pidlChild(child);
182 
183         STRRET strret;
184         hr = m_pSendTo->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret);
185         if (FAILED_UNEXPECTEDLY(hr))
186             continue;
187 
188         CComHeapPtr<WCHAR> pszText;
189         hr = StrRetToStrW(&strret, pidlChild, &pszText);
190         if (FAILED_UNEXPECTEDLY(hr))
191             continue;
192 
193         CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlAbsolute;
194         pidlAbsolute.Attach(ILCombine(pidlSendTo, pidlChild));
195 
196         SHFILEINFOW fi = { NULL };
197         const UINT uFlags = SHGFI_PIDL | SHGFI_TYPENAME |
198                             SHGFI_ICON | SHGFI_SMALLICON;
199         SHGetFileInfoW(reinterpret_cast<LPWSTR>(static_cast<PIDLIST_ABSOLUTE>(pidlAbsolute)), 0,
200                        &fi, sizeof(fi), uFlags);
201 
202         SENDTO_ITEM *pNewItem =
203             new SENDTO_ITEM(pidlChild.Detach(), pszText.Detach(), fi.hIcon);
204         if (m_pItems)
205         {
206             pNewItem->pNext = m_pItems;
207         }
208         m_pItems = pNewItem;
209     }
210 
211     return hr;
212 }
213 
214 UINT CSendToMenu::InsertSendToItems(HMENU hMenu, UINT idCmdFirst, UINT Pos)
215 {
216     if (m_pItems == NULL)
217     {
218         HRESULT hr = LoadAllItems(NULL);
219         if (FAILED_UNEXPECTEDLY(hr))
220             return 0;
221     }
222 
223     m_idCmdFirst = idCmdFirst;
224 
225     UINT idCmd = idCmdFirst;
226     for (SENDTO_ITEM *pCurItem = m_pItems; pCurItem; pCurItem = pCurItem->pNext)
227     {
228         const UINT uFlags = MF_BYPOSITION | MF_STRING | MF_ENABLED;
229         if (InsertMenuW(hMenu, Pos, uFlags, idCmd, pCurItem->pszText))
230         {
231             MENUITEMINFOW mii;
232             mii.cbSize = sizeof(mii);
233             mii.fMask = MIIM_DATA | MIIM_BITMAP;
234             mii.dwItemData = reinterpret_cast<ULONG_PTR>(pCurItem);
235             mii.hbmpItem = HBMMENU_CALLBACK;
236             SetMenuItemInfoW(hMenu, idCmd, FALSE, &mii);
237             ++idCmd;
238 
239             // successful
240         }
241     }
242 
243     if (idCmd == idCmdFirst)
244     {
245         CStringW strNone(MAKEINTRESOURCEW(IDS_NONE));
246         AppendMenuW(hMenu, MF_GRAYED | MF_DISABLED | MF_STRING, idCmd, strNone);
247     }
248 
249     return idCmd - idCmdFirst;
250 }
251 
252 CSendToMenu::SENDTO_ITEM *CSendToMenu::FindItemFromIdOffset(UINT IdOffset)
253 {
254     UINT idCmd = m_idCmdFirst + IdOffset;
255 
256     MENUITEMINFOW mii = { sizeof(mii) };
257     mii.fMask = MIIM_DATA;
258     if (GetMenuItemInfoW(m_hSubMenu, idCmd, FALSE, &mii))
259         return reinterpret_cast<SENDTO_ITEM *>(mii.dwItemData);
260 
261     ERR("GetMenuItemInfoW: %ld\n", GetLastError());
262     return NULL;
263 }
264 
265 HRESULT CSendToMenu::DoSendToItem(SENDTO_ITEM *pItem, LPCMINVOKECOMMANDINFO lpici)
266 {
267     if (!m_pDataObject)
268     {
269         ERR("!m_pDataObject\n");
270         return E_FAIL;
271     }
272 
273     HRESULT hr;
274     CComPtr<IDropTarget> pDropTarget;
275     hr = m_pSendTo->GetUIObjectOf(NULL, 1, &pItem->pidlChild, IID_IDropTarget,
276                                   NULL, (LPVOID *)&pDropTarget);
277     if (FAILED_UNEXPECTEDLY(hr))
278         return hr;
279 
280     hr = DoDrop(m_pDataObject, pDropTarget);
281     if (FAILED_UNEXPECTEDLY(hr))
282         return hr;
283 
284     return hr;
285 }
286 
287 STDMETHODIMP
288 CSendToMenu::QueryContextMenu(HMENU hMenu,
289                               UINT indexMenu,
290                               UINT idCmdFirst,
291                               UINT idCmdLast,
292                               UINT uFlags)
293 {
294     TRACE("%p %p %u %u %u %u\n", this,
295           hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
296 
297     HMENU hSubMenu = CreateMenu();
298     if (!hSubMenu)
299     {
300         ERR("CreateMenu: %ld\n", GetLastError());
301         return E_FAIL;
302     }
303 
304     UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0);
305 
306     CStringW strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU));
307 
308     MENUITEMINFOW mii = { sizeof(mii) };
309     mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU;
310     mii.fType = MFT_STRING;
311     mii.wID = -1;
312     mii.dwTypeData = strSendTo.GetBuffer();
313     mii.cch = wcslen(mii.dwTypeData);
314     mii.fState = MFS_ENABLED;
315     mii.hSubMenu = hSubMenu;
316     if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
317     {
318         ERR("InsertMenuItemW: %ld\n", GetLastError());
319         return E_FAIL;
320     }
321 
322     HMENU hOldSubMenu = m_hSubMenu;
323     m_hSubMenu = hSubMenu;
324     DestroyMenu(hOldSubMenu);
325 
326     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cItems);
327 }
328 
329 STDMETHODIMP
330 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
331 {
332     HRESULT hr = E_FAIL;
333 
334     WORD idCmd = LOWORD(lpici->lpVerb);
335     TRACE("idCmd: %d\n", idCmd);
336 
337     SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd);
338     if (pItem)
339     {
340         hr = DoSendToItem(pItem, lpici);
341     }
342 
343     TRACE("CSendToMenu::InvokeCommand %x\n", hr);
344     return hr;
345 }
346 
347 STDMETHODIMP
348 CSendToMenu::GetCommandString(UINT_PTR idCmd,
349                               UINT uType,
350                               UINT *pwReserved,
351                               LPSTR pszName,
352                               UINT cchMax)
353 {
354     FIXME("%p %lu %u %p %p %u\n", this,
355           idCmd, uType, pwReserved, pszName, cchMax);
356 
357     return E_NOTIMPL;
358 }
359 
360 STDMETHODIMP
361 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
362 {
363     return S_OK;
364 }
365 
366 STDMETHODIMP
367 CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,
368                             LRESULT *plResult)
369 {
370     UINT cxSmall = GetSystemMetrics(SM_CXSMICON);
371     UINT cySmall = GetSystemMetrics(SM_CYSMICON);
372 
373     switch (uMsg)
374     {
375     case WM_MEASUREITEM:
376         {
377             MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
378             if (!lpmis || lpmis->CtlType != ODT_MENU)
379                 break;
380 
381             UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK);
382             if (lpmis->itemWidth < cxMenuCheck)
383                 lpmis->itemWidth = cxMenuCheck;
384             if (lpmis->itemHeight < cySmall)
385                 lpmis->itemHeight = cySmall;
386 
387             if (plResult)
388                 *plResult = TRUE;
389             break;
390         }
391     case WM_DRAWITEM:
392         {
393             DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
394             if (!lpdis || lpdis->CtlType != ODT_MENU)
395                 break;
396 
397             SENDTO_ITEM *pItem = reinterpret_cast<SENDTO_ITEM *>(lpdis->itemData);
398             HICON hIcon = NULL;
399             if (pItem)
400                 hIcon = pItem->hIcon;
401             if (!hIcon)
402                 break;
403 
404             const RECT& rcItem = lpdis->rcItem;
405             INT x = 4;
406             INT y = lpdis->rcItem.top;
407             y += (rcItem.bottom - rcItem.top - cySmall) / 2;
408             DrawIconEx(lpdis->hDC, x, y, hIcon, cxSmall, cySmall,
409                        0, NULL, DI_NORMAL);
410 
411             if (plResult)
412                 *plResult = TRUE;
413         }
414     }
415 
416     return S_OK;
417 }
418 
419 STDMETHODIMP
420 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
421                         IDataObject *pdtobj, HKEY hkeyProgID)
422 {
423     m_pDataObject = pdtobj;
424     return S_OK;
425 }
426