Reactos
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
23WINE_DEFAULT_DEBUG_CHANNEL(shell);
24
25CSendToMenu::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
39CSendToMenu::~CSendToMenu()
40{
41 UnloadAllItems();
42
43 if (m_hSubMenu)
44 {
45 DestroyMenu(m_hSubMenu);
46 m_hSubMenu = NULL;
47 }
48}
49
50HRESULT 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
99HRESULT
100CSendToMenu::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
129HRESULT 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
140void 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
152HRESULT 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
207UINT 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
246CSendToMenu::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
259HRESULT 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
281STDMETHODIMP
282CSendToMenu::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
326STDMETHODIMP
327CSendToMenu::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
344STDMETHODIMP
345CSendToMenu::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
357STDMETHODIMP
358CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
359{
360 return S_OK;
361}
362
363STDMETHODIMP
364CSendToMenu::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
416STDMETHODIMP
417CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
418 IDataObject *pdtobj, HKEY hkeyProgID)
419{
420 m_pDataObject = pdtobj;
421 return S_OK;
422}