xref: /reactos/dll/win32/shell32/CSendToMenu.cpp (revision f156f9a1)
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 
CSendToMenu()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 
~CSendToMenu()39 CSendToMenu::~CSendToMenu()
40 {
41     UnloadAllItems();
42 
43     if (m_hSubMenu)
44     {
45         DestroyMenu(m_hSubMenu);
46         m_hSubMenu = NULL;
47     }
48 }
49 
DoDrop(IDataObject * pDataObject,IDropTarget * pDropTarget)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
GetSpecialFolder(HWND hwnd,IShellFolder ** ppFolder,int csidl,PIDLIST_ABSOLUTE * ppidl)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
GetUIObjectFromPidl(HWND hwnd,PIDLIST_ABSOLUTE pidl,REFIID riid,LPVOID * ppvOut)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 
UnloadAllItems()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 
LoadAllItems(HWND hwnd)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 
InsertSendToItems(HMENU hMenu,UINT idCmdFirst,UINT Pos)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         ++idCmd;
248     }
249 
250     return idCmd - idCmdFirst;
251 }
252 
FindItemFromIdOffset(UINT IdOffset)253 CSendToMenu::SENDTO_ITEM *CSendToMenu::FindItemFromIdOffset(UINT IdOffset)
254 {
255     UINT idCmd = m_idCmdFirst + IdOffset;
256 
257     MENUITEMINFOW mii = { sizeof(mii) };
258     mii.fMask = MIIM_DATA;
259     if (GetMenuItemInfoW(m_hSubMenu, idCmd, FALSE, &mii))
260         return reinterpret_cast<SENDTO_ITEM *>(mii.dwItemData);
261 
262     ERR("GetMenuItemInfoW: %ld\n", GetLastError());
263     return NULL;
264 }
265 
DoSendToItem(SENDTO_ITEM * pItem,LPCMINVOKECOMMANDINFO lpici)266 HRESULT CSendToMenu::DoSendToItem(SENDTO_ITEM *pItem, LPCMINVOKECOMMANDINFO lpici)
267 {
268     if (!m_pDataObject)
269     {
270         ERR("!m_pDataObject\n");
271         return E_FAIL;
272     }
273 
274     HRESULT hr;
275     CComPtr<IDropTarget> pDropTarget;
276     hr = m_pSendTo->GetUIObjectOf(NULL, 1, &pItem->pidlChild, IID_IDropTarget,
277                                   NULL, (LPVOID *)&pDropTarget);
278     if (FAILED_UNEXPECTEDLY(hr))
279         return hr;
280 
281     hr = DoDrop(m_pDataObject, pDropTarget);
282     if (FAILED_UNEXPECTEDLY(hr))
283         return hr;
284 
285     return hr;
286 }
287 
288 STDMETHODIMP
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)289 CSendToMenu::QueryContextMenu(HMENU hMenu,
290                               UINT indexMenu,
291                               UINT idCmdFirst,
292                               UINT idCmdLast,
293                               UINT uFlags)
294 {
295     TRACE("%p %p %u %u %u %u\n", this,
296           hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
297 
298     if (uFlags & (CMF_NOVERBS | CMF_VERBSONLY))
299         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst);
300 
301     HMENU hSubMenu = CreateMenu();
302     if (!hSubMenu)
303     {
304         ERR("CreateMenu: %ld\n", GetLastError());
305         return E_FAIL;
306     }
307 
308     UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0);
309 
310     CStringW strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU));
311 
312     MENUITEMINFOW mii = { sizeof(mii) };
313     mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU;
314     mii.fType = MFT_STRING;
315     mii.wID = -1;
316     mii.dwTypeData = strSendTo.GetBuffer();
317     mii.cch = wcslen(mii.dwTypeData);
318     mii.fState = MFS_ENABLED;
319     mii.hSubMenu = hSubMenu;
320     if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
321     {
322         ERR("InsertMenuItemW: %ld\n", GetLastError());
323         return E_FAIL;
324     }
325 
326     HMENU hOldSubMenu = m_hSubMenu;
327     m_hSubMenu = hSubMenu;
328     DestroyMenu(hOldSubMenu);
329 
330     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst + cItems);
331 }
332 
333 STDMETHODIMP
InvokeCommand(LPCMINVOKECOMMANDINFO lpici)334 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
335 {
336     HRESULT hr = E_FAIL;
337 
338     WORD idCmd = LOWORD(lpici->lpVerb);
339     TRACE("idCmd: %d\n", idCmd);
340 
341     SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd);
342     if (pItem)
343     {
344         hr = DoSendToItem(pItem, lpici);
345     }
346 
347     TRACE("CSendToMenu::InvokeCommand %x\n", hr);
348     return hr;
349 }
350 
351 STDMETHODIMP
GetCommandString(UINT_PTR idCmd,UINT uType,UINT * pwReserved,LPSTR pszName,UINT cchMax)352 CSendToMenu::GetCommandString(UINT_PTR idCmd,
353                               UINT uType,
354                               UINT *pwReserved,
355                               LPSTR pszName,
356                               UINT cchMax)
357 {
358     FIXME("%p %lu %u %p %p %u\n", this,
359           idCmd, uType, pwReserved, pszName, cchMax);
360 
361     return E_NOTIMPL;
362 }
363 
364 STDMETHODIMP
HandleMenuMsg(UINT uMsg,WPARAM wParam,LPARAM lParam)365 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
366 {
367     return S_OK;
368 }
369 
370 STDMETHODIMP
HandleMenuMsg2(UINT uMsg,WPARAM wParam,LPARAM lParam,LRESULT * plResult)371 CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,
372                             LRESULT *plResult)
373 {
374     UINT cxSmall = GetSystemMetrics(SM_CXSMICON);
375     UINT cySmall = GetSystemMetrics(SM_CYSMICON);
376 
377     switch (uMsg)
378     {
379     case WM_MEASUREITEM:
380         {
381             MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
382             if (!lpmis || lpmis->CtlType != ODT_MENU)
383                 break;
384 
385             UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK);
386             if (lpmis->itemWidth < cxMenuCheck)
387                 lpmis->itemWidth = cxMenuCheck;
388             if (lpmis->itemHeight < cySmall)
389                 lpmis->itemHeight = cySmall;
390 
391             if (plResult)
392                 *plResult = TRUE;
393             break;
394         }
395     case WM_DRAWITEM:
396         {
397             DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
398             if (!lpdis || lpdis->CtlType != ODT_MENU)
399                 break;
400 
401             SENDTO_ITEM *pItem = reinterpret_cast<SENDTO_ITEM *>(lpdis->itemData);
402             HICON hIcon = NULL;
403             if (pItem)
404                 hIcon = pItem->hIcon;
405             if (!hIcon)
406                 break;
407 
408             const RECT& rcItem = lpdis->rcItem;
409             INT x = 4;
410             INT y = lpdis->rcItem.top;
411             y += (rcItem.bottom - rcItem.top - cySmall) / 2;
412             DrawIconEx(lpdis->hDC, x, y, hIcon, cxSmall, cySmall,
413                        0, NULL, DI_NORMAL);
414 
415             if (plResult)
416                 *plResult = TRUE;
417         }
418     }
419 
420     return S_OK;
421 }
422 
423 STDMETHODIMP
Initialize(PCIDLIST_ABSOLUTE pidlFolder,IDataObject * pdtobj,HKEY hkeyProgID)424 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
425                         IDataObject *pdtobj, HKEY hkeyProgID)
426 {
427     m_pDataObject = pdtobj;
428     return S_OK;
429 }
430