xref: /reactos/dll/win32/shell32/CSendToMenu.cpp (revision cc3672cb)
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     HRESULT hr = SHELL_GetUIObjectOfAbsoluteItem(hwnd, pidl, riid, ppvOut);
134     if (FAILED_UNEXPECTEDLY(hr))
135         return hr;
136 
137     return hr;
138 }
139 
140 void CSendToMenu::UnloadAllItems()
141 {
142     SENDTO_ITEM *pItems = m_pItems;
143     m_pItems = NULL;
144     while (pItems)
145     {
146         SENDTO_ITEM *pCurItem = pItems;
147         pItems = pItems->pNext;
148         delete pCurItem;
149     }
150 }
151 
152 HRESULT CSendToMenu::LoadAllItems(HWND hwnd)
153 {
154     UnloadAllItems();
155 
156     CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlSendTo;
157 
158     m_pSendTo.Release();
159     HRESULT hr = GetSpecialFolder(hwnd, &m_pSendTo, CSIDL_SENDTO, &pidlSendTo);
160     if (FAILED_UNEXPECTEDLY(hr))
161         return hr;
162 
163     CComPtr<IEnumIDList> pEnumIDList;
164     hr = m_pSendTo->EnumObjects(hwnd,
165                                 SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,
166                                 &pEnumIDList);
167     if (FAILED_UNEXPECTEDLY(hr))
168         return hr;
169 
170     hr = S_OK;
171     PITEMID_CHILD child;
172     while (pEnumIDList->Next(1, &child, NULL) == S_OK)
173     {
174         CComHeapPtr<ITEMID_CHILD> pidlChild(child);
175 
176         STRRET strret;
177         hr = m_pSendTo->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret);
178         if (FAILED_UNEXPECTEDLY(hr))
179             continue;
180 
181         CComHeapPtr<WCHAR> pszText;
182         hr = StrRetToStrW(&strret, pidlChild, &pszText);
183         if (FAILED_UNEXPECTEDLY(hr))
184             continue;
185 
186         CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlAbsolute;
187         pidlAbsolute.Attach(ILCombine(pidlSendTo, pidlChild));
188 
189         SHFILEINFOW fi = { NULL };
190         const UINT uFlags = SHGFI_PIDL | SHGFI_TYPENAME |
191                             SHGFI_ICON | SHGFI_SMALLICON;
192         SHGetFileInfoW(reinterpret_cast<LPWSTR>(static_cast<PIDLIST_ABSOLUTE>(pidlAbsolute)), 0,
193                        &fi, sizeof(fi), uFlags);
194 
195         SENDTO_ITEM *pNewItem =
196             new SENDTO_ITEM(pidlChild.Detach(), pszText.Detach(), fi.hIcon);
197         if (m_pItems)
198         {
199             pNewItem->pNext = m_pItems;
200         }
201         m_pItems = pNewItem;
202     }
203 
204     return hr;
205 }
206 
207 UINT CSendToMenu::InsertSendToItems(HMENU hMenu, UINT idCmdFirst, UINT Pos)
208 {
209     if (m_pItems == NULL)
210     {
211         HRESULT hr = LoadAllItems(NULL);
212         if (FAILED_UNEXPECTEDLY(hr))
213             return 0;
214     }
215 
216     m_idCmdFirst = idCmdFirst;
217 
218     UINT idCmd = idCmdFirst;
219     for (SENDTO_ITEM *pCurItem = m_pItems; pCurItem; pCurItem = pCurItem->pNext)
220     {
221         const UINT uFlags = MF_BYPOSITION | MF_STRING | MF_ENABLED;
222         if (InsertMenuW(hMenu, Pos, uFlags, idCmd, pCurItem->pszText))
223         {
224             MENUITEMINFOW mii;
225             mii.cbSize = sizeof(mii);
226             mii.fMask = MIIM_DATA | MIIM_BITMAP;
227             mii.dwItemData = reinterpret_cast<ULONG_PTR>(pCurItem);
228             mii.hbmpItem = HBMMENU_CALLBACK;
229             SetMenuItemInfoW(hMenu, idCmd, FALSE, &mii);
230             ++idCmd;
231 
232             // successful
233         }
234     }
235 
236     if (idCmd == idCmdFirst)
237     {
238         CStringW strNone(MAKEINTRESOURCEW(IDS_NONE));
239         AppendMenuW(hMenu, MF_GRAYED | MF_DISABLED | MF_STRING, idCmd, strNone);
240         ++idCmd;
241     }
242 
243     return idCmd - idCmdFirst;
244 }
245 
246 CSendToMenu::SENDTO_ITEM *CSendToMenu::FindItemFromIdOffset(UINT IdOffset)
247 {
248     UINT idCmd = m_idCmdFirst + IdOffset;
249 
250     MENUITEMINFOW mii = { sizeof(mii) };
251     mii.fMask = MIIM_DATA;
252     if (GetMenuItemInfoW(m_hSubMenu, idCmd, FALSE, &mii))
253         return reinterpret_cast<SENDTO_ITEM *>(mii.dwItemData);
254 
255     ERR("GetMenuItemInfoW: %ld\n", GetLastError());
256     return NULL;
257 }
258 
259 HRESULT CSendToMenu::DoSendToItem(SENDTO_ITEM *pItem, LPCMINVOKECOMMANDINFO lpici)
260 {
261     if (!m_pDataObject)
262     {
263         ERR("!m_pDataObject\n");
264         return E_FAIL;
265     }
266 
267     HRESULT hr;
268     CComPtr<IDropTarget> pDropTarget;
269     hr = m_pSendTo->GetUIObjectOf(NULL, 1, &pItem->pidlChild, IID_IDropTarget,
270                                   NULL, (LPVOID *)&pDropTarget);
271     if (FAILED_UNEXPECTEDLY(hr))
272         return hr;
273 
274     hr = DoDrop(m_pDataObject, pDropTarget);
275     if (FAILED_UNEXPECTEDLY(hr))
276         return hr;
277 
278     return hr;
279 }
280 
281 STDMETHODIMP
282 CSendToMenu::QueryContextMenu(HMENU hMenu,
283                               UINT indexMenu,
284                               UINT idCmdFirst,
285                               UINT idCmdLast,
286                               UINT uFlags)
287 {
288     TRACE("%p %p %u %u %u %u\n", this,
289           hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
290 
291     if (uFlags & (CMF_NOVERBS | CMF_VERBSONLY))
292         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst);
293 
294     HMENU hSubMenu = CreateMenu();
295     if (!hSubMenu)
296     {
297         ERR("CreateMenu: %ld\n", GetLastError());
298         return E_FAIL;
299     }
300 
301     UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0);
302 
303     CStringW strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU));
304 
305     MENUITEMINFOW mii = { sizeof(mii) };
306     mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU;
307     mii.fType = MFT_STRING;
308     mii.wID = -1;
309     mii.dwTypeData = strSendTo.GetBuffer();
310     mii.cch = wcslen(mii.dwTypeData);
311     mii.fState = MFS_ENABLED;
312     mii.hSubMenu = hSubMenu;
313     if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
314     {
315         ERR("InsertMenuItemW: %ld\n", GetLastError());
316         return E_FAIL;
317     }
318 
319     HMENU hOldSubMenu = m_hSubMenu;
320     m_hSubMenu = hSubMenu;
321     DestroyMenu(hOldSubMenu);
322 
323     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst + cItems);
324 }
325 
326 STDMETHODIMP
327 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
328 {
329     HRESULT hr = E_FAIL;
330 
331     WORD idCmd = LOWORD(lpici->lpVerb);
332     TRACE("idCmd: %d\n", idCmd);
333 
334     SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd);
335     if (pItem)
336     {
337         hr = DoSendToItem(pItem, lpici);
338     }
339 
340     TRACE("CSendToMenu::InvokeCommand %x\n", hr);
341     return hr;
342 }
343 
344 STDMETHODIMP
345 CSendToMenu::GetCommandString(UINT_PTR idCmd,
346                               UINT uType,
347                               UINT *pwReserved,
348                               LPSTR pszName,
349                               UINT cchMax)
350 {
351     FIXME("%p %lu %u %p %p %u\n", this,
352           idCmd, uType, pwReserved, pszName, cchMax);
353 
354     return E_NOTIMPL;
355 }
356 
357 STDMETHODIMP
358 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
359 {
360     return S_OK;
361 }
362 
363 STDMETHODIMP
364 CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,
365                             LRESULT *plResult)
366 {
367     UINT cxSmall = GetSystemMetrics(SM_CXSMICON);
368     UINT cySmall = GetSystemMetrics(SM_CYSMICON);
369 
370     switch (uMsg)
371     {
372     case WM_MEASUREITEM:
373         {
374             MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
375             if (!lpmis || lpmis->CtlType != ODT_MENU)
376                 break;
377 
378             UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK);
379             if (lpmis->itemWidth < cxMenuCheck)
380                 lpmis->itemWidth = cxMenuCheck;
381             if (lpmis->itemHeight < cySmall)
382                 lpmis->itemHeight = cySmall;
383 
384             if (plResult)
385                 *plResult = TRUE;
386             break;
387         }
388     case WM_DRAWITEM:
389         {
390             DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
391             if (!lpdis || lpdis->CtlType != ODT_MENU)
392                 break;
393 
394             SENDTO_ITEM *pItem = reinterpret_cast<SENDTO_ITEM *>(lpdis->itemData);
395             HICON hIcon = NULL;
396             if (pItem)
397                 hIcon = pItem->hIcon;
398             if (!hIcon)
399                 break;
400 
401             const RECT& rcItem = lpdis->rcItem;
402             INT x = 4;
403             INT y = lpdis->rcItem.top;
404             y += (rcItem.bottom - rcItem.top - cySmall) / 2;
405             DrawIconEx(lpdis->hDC, x, y, hIcon, cxSmall, cySmall,
406                        0, NULL, DI_NORMAL);
407 
408             if (plResult)
409                 *plResult = TRUE;
410         }
411     }
412 
413     return S_OK;
414 }
415 
416 STDMETHODIMP
417 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
418                         IDataObject *pdtobj, HKEY hkeyProgID)
419 {
420     m_pDataObject = pdtobj;
421     return S_OK;
422 }
423