[SHELL32] Initial SendTo implementation (#2021)
[reactos.git] / dll / win32 / shell32 / CSendToMenu.cpp
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 #define INITGUID
23 #include <guiddef.h>
24
25 #define MAX_ITEM_COUNT 64
26
27 WINE_DEFAULT_DEBUG_CHANNEL(shell);
28
29 DEFINE_GUID(CLSID_SendToMenu, 0x7BA4C740, 0x9E81, 0x11CF,
30 0x99, 0xD3, 0x00, 0xAA, 0x00, 0x4A, 0xE8, 0x37);
31
32 CSendToMenu::CSendToMenu()
33 : m_hSubMenu(NULL)
34 , m_pItems(NULL)
35 , m_idCmdFirst(0)
36 {
37 SHGetDesktopFolder(&m_pDesktop);
38 m_pSendTo = GetSpecialFolder(NULL, CSIDL_SENDTO);
39 }
40
41 CSendToMenu::~CSendToMenu()
42 {
43 UnloadAllItems();
44
45 if (m_hSubMenu)
46 {
47 DestroyMenu(m_hSubMenu);
48 m_hSubMenu = NULL;
49 }
50 }
51
52 HRESULT CSendToMenu::DoDrop(IDataObject *pDataObject, IDropTarget *pDropTarget)
53 {
54 DWORD dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK;
55
56 BOOL bShift = (GetAsyncKeyState(VK_SHIFT) < 0);
57 BOOL bCtrl = (GetAsyncKeyState(VK_CONTROL) < 0);
58
59 // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY.
60 // (We have to translate a SendTo action to a Drop action)
61 DWORD dwKeyState = MK_LBUTTON;
62 if (bShift && bCtrl)
63 dwKeyState |= MK_SHIFT | MK_CONTROL;
64 else if (!bShift)
65 dwKeyState |= MK_CONTROL;
66 if (bCtrl)
67 dwKeyState |= MK_SHIFT;
68
69 POINTL ptl = { 0, 0 };
70 HRESULT hr = pDropTarget->DragEnter(pDataObject, dwKeyState, ptl, &dwEffect);
71 if (SUCCEEDED(hr) && dwEffect != DROPEFFECT_NONE)
72 {
73 // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY.
74 // (We have to translate a SendTo action to a Drop action)
75 if (bShift && bCtrl)
76 dwEffect = DROPEFFECT_LINK;
77 else if (!bShift)
78 dwEffect = DROPEFFECT_MOVE;
79 else
80 dwEffect = DROPEFFECT_COPY;
81
82 hr = pDropTarget->Drop(pDataObject, dwKeyState, ptl, &dwEffect);
83 }
84 else
85 {
86 ERR("DragEnter: %08lX\n", hr);
87 pDropTarget->DragLeave();
88 }
89
90 return hr;
91 }
92
93 // get an IShellFolder from CSIDL
94 IShellFolder *
95 CSendToMenu::GetSpecialFolder(HWND hwnd, int csidl, LPITEMIDLIST *ppidl)
96 {
97 if (ppidl)
98 *ppidl = NULL;
99
100 if (!m_pDesktop)
101 {
102 SHGetDesktopFolder(&m_pDesktop);
103 if (!m_pDesktop)
104 {
105 ERR("SHGetDesktopFolder\n");
106 return NULL;
107 }
108 }
109
110 LPITEMIDLIST pidl = NULL;
111 HRESULT hr = SHGetSpecialFolderLocation(hwnd, csidl, &pidl);
112 if (FAILED(hr))
113 {
114 ERR("SHGetSpecialFolderLocation: %08lX\n", hr);
115 return NULL;
116 }
117
118 IShellFolder *pFolder = NULL;
119 hr = m_pDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &pFolder));
120
121 if (ppidl)
122 *ppidl = pidl;
123 else
124 CoTaskMemFree(pidl);
125
126 if (SUCCEEDED(hr))
127 return pFolder;
128
129 ERR("BindToObject: %08lX\n", hr);
130 return NULL;
131 }
132
133 // get a UI object from PIDL
134 HRESULT CSendToMenu::GetUIObjectFromPidl(HWND hwnd, LPITEMIDLIST pidl,
135 REFIID riid, LPVOID *ppvOut)
136 {
137 *ppvOut = NULL;
138
139 LPCITEMIDLIST pidlLast;
140 CComPtr<IShellFolder> pFolder;
141 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pFolder), &pidlLast);
142 if (FAILED(hr))
143 {
144 ERR("SHBindToParent: %08lX\n", hr);
145 return hr;
146 }
147
148 hr = pFolder->GetUIObjectOf(hwnd, 1, &pidlLast, riid, NULL, ppvOut);
149 if (FAILED(hr))
150 {
151 ERR("GetUIObjectOf: %08lX\n", hr);
152 }
153 return hr;
154 }
155
156 void CSendToMenu::UnloadItem(SENDTO_ITEM *pItem)
157 {
158 if (!pItem)
159 return;
160
161 CoTaskMemFree(pItem->pidlChild);
162 CoTaskMemFree(pItem->pszText);
163 DestroyIcon(pItem->hIcon);
164 HeapFree(GetProcessHeap(), 0, pItem);
165 }
166
167 void CSendToMenu::UnloadAllItems()
168 {
169 SENDTO_ITEM *pItems = m_pItems;
170 m_pItems = NULL;
171 while (pItems)
172 {
173 SENDTO_ITEM *pCurItem = pItems;
174 pItems = pItems->pNext;
175 UnloadItem(pCurItem);
176 }
177 }
178
179 BOOL CSendToMenu::LoadAllItems(HWND hwnd)
180 {
181 UnloadAllItems();
182
183 LPITEMIDLIST pidlSendTo;
184 m_pSendTo = GetSpecialFolder(hwnd, CSIDL_SENDTO, &pidlSendTo);
185 if (!m_pSendTo)
186 {
187 ERR("GetSpecialFolder\n");
188 return FALSE;
189 }
190
191 HRESULT hr;
192 CComPtr<IEnumIDList> pEnumIDList;
193 hr = m_pSendTo->EnumObjects(hwnd,
194 SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,
195 &pEnumIDList);
196 if (FAILED(hr))
197 {
198 ERR("EnumObjects: %08lX\n", hr);
199 ILFree(pidlSendTo);
200 return FALSE;
201 }
202
203 BOOL bOK = TRUE;
204 LPITEMIDLIST pidlChild;
205 UINT nCount = 0;
206 while (pEnumIDList->Next(1, &pidlChild, NULL) == S_OK)
207 {
208 SENDTO_ITEM *pNewItem = (SENDTO_ITEM *)HeapAlloc(GetProcessHeap(),
209 HEAP_ZERO_MEMORY,
210 sizeof(SENDTO_ITEM));
211 if (!pNewItem)
212 {
213 ERR("HeapAlloc\n");
214 bOK = FALSE;
215 CoTaskMemFree(pidlChild);
216 break;
217 }
218
219 STRRET strret;
220 hr = m_pSendTo->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret);
221 if (SUCCEEDED(hr))
222 {
223 LPWSTR pszText = NULL;
224 hr = StrRetToStrW(&strret, pidlChild, &pszText);
225 if (SUCCEEDED(hr))
226 {
227 LPITEMIDLIST pidlAbsolute = ILCombine(pidlSendTo, pidlChild);
228
229 SHFILEINFOW fi = { NULL };
230 const UINT uFlags = SHGFI_PIDL | SHGFI_TYPENAME |
231 SHGFI_ICON | SHGFI_SMALLICON;
232 SHGetFileInfoW((LPCWSTR)pidlAbsolute, 0, &fi, sizeof(fi), uFlags);
233
234 ILFree(pidlAbsolute);
235
236 pNewItem->pidlChild = pidlChild;
237 pNewItem->pszText = pszText;
238 pNewItem->hIcon = fi.hIcon;
239 if (m_pItems)
240 {
241 pNewItem->pNext = m_pItems;
242 }
243 m_pItems = pNewItem;
244
245 // successful
246 ++nCount;
247 if (nCount >= MAX_ITEM_COUNT)
248 {
249 break;
250 }
251 continue;
252 }
253 else
254 {
255 ERR("StrRetToStrW: %08lX\n", hr);
256 }
257 }
258 else
259 {
260 ERR("GetDisplayNameOf: %08lX\n", hr);
261 }
262
263 UnloadItem(pNewItem);
264 CoTaskMemFree(pidlChild);
265 }
266
267 ILFree(pidlSendTo);
268
269 return bOK;
270 }
271
272 UINT CSendToMenu::InsertSendToItems(HMENU hMenu, UINT idCmdFirst, UINT Pos)
273 {
274 if (m_pItems == NULL)
275 {
276 if (!LoadAllItems(NULL))
277 {
278 ERR("LoadAllItems\n");
279 return 0;
280 }
281 }
282
283 m_idCmdFirst = idCmdFirst;
284
285 UINT idCmd = idCmdFirst;
286 UINT nCount = 0;
287 for (SENDTO_ITEM *pCurItem = m_pItems; pCurItem; pCurItem = pCurItem->pNext)
288 {
289 const UINT uFlags = MF_BYPOSITION | MF_STRING | MF_ENABLED;
290 if (InsertMenuW(hMenu, Pos, uFlags, idCmd, pCurItem->pszText))
291 {
292 MENUITEMINFOW mii;
293 mii.cbSize = sizeof(mii);
294 mii.fMask = MIIM_DATA | MIIM_BITMAP;
295 mii.dwItemData = (ULONG_PTR)pCurItem;
296 mii.hbmpItem = HBMMENU_CALLBACK;
297 SetMenuItemInfoW(hMenu, idCmd, FALSE, &mii);
298 ++idCmd;
299
300 // successful
301 ++nCount;
302 if (nCount >= MAX_ITEM_COUNT)
303 {
304 break;
305 }
306 }
307 }
308
309 if (idCmd == idCmdFirst)
310 {
311 WCHAR szNone[64] = L"(None)";
312 LoadStringW(shell32_hInstance, IDS_NONE, szNone, _countof(szNone));
313
314 AppendMenuW(hMenu, MF_GRAYED | MF_DISABLED | MF_STRING, idCmd, szNone);
315 }
316
317 return idCmd - idCmdFirst;
318 }
319
320 CSendToMenu::SENDTO_ITEM *CSendToMenu::FindItemFromIdOffset(UINT IdOffset)
321 {
322 UINT idCmd = m_idCmdFirst + IdOffset;
323
324 MENUITEMINFOW mii = { sizeof(mii) };
325 mii.fMask = MIIM_DATA;
326 if (GetMenuItemInfoW(m_hSubMenu, idCmd, FALSE, &mii))
327 return (SENDTO_ITEM *)mii.dwItemData;
328
329 ERR("GetMenuItemInfoW\n");
330 return NULL;
331 }
332
333 HRESULT CSendToMenu::DoSendToItem(SENDTO_ITEM *pItem, LPCMINVOKECOMMANDINFO lpici)
334 {
335 if (!m_pDataObject)
336 {
337 ERR("!m_pDataObject\n");
338 return E_FAIL;
339 }
340
341 HRESULT hr;
342 CComPtr<IDropTarget> pDropTarget;
343 LPITEMIDLIST pidlChild = pItem->pidlChild;
344 hr = m_pSendTo->GetUIObjectOf(NULL, 1, &pidlChild, IID_IDropTarget,
345 NULL, (LPVOID *)&pDropTarget);
346 if (SUCCEEDED(hr))
347 {
348 hr = DoDrop(m_pDataObject, pDropTarget);
349 }
350 else
351 {
352 ERR("GetUIObjectOf: %08lX\n", hr);
353 }
354
355 return hr;
356 }
357
358 STDMETHODIMP CSendToMenu::SetSite(IUnknown *pUnkSite)
359 {
360 m_pSite = pUnkSite;
361 return S_OK;
362 }
363
364 STDMETHODIMP CSendToMenu::GetSite(REFIID riid, void **ppvSite)
365 {
366 if (!m_pSite)
367 return E_FAIL;
368
369 return m_pSite->QueryInterface(riid, ppvSite);
370 }
371
372 STDMETHODIMP
373 CSendToMenu::QueryContextMenu(HMENU hMenu,
374 UINT indexMenu,
375 UINT idCmdFirst,
376 UINT idCmdLast,
377 UINT uFlags)
378 {
379 TRACE("%p %p %u %u %u %u\n", this,
380 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
381
382 WCHAR wszSendTo[64];
383 if (!LoadStringW(shell32_hInstance, IDS_SENDTO,
384 wszSendTo, _countof(wszSendTo)))
385 {
386 ERR("IDS_SENDTO\n");
387 return E_FAIL;
388 }
389
390 HMENU hSubMenu = CreateMenu();
391 if (!hSubMenu)
392 {
393 ERR("CreateMenu\n");
394 return E_FAIL;
395 }
396
397 UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0);
398
399 MENUITEMINFOW mii;
400 ZeroMemory(&mii, sizeof(mii));
401 mii.cbSize = sizeof(mii);
402 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU;
403 mii.fType = MFT_STRING;
404 mii.wID = -1;
405 mii.dwTypeData = wszSendTo;
406 mii.cch = wcslen(mii.dwTypeData);
407 mii.fState = MFS_ENABLED;
408 mii.hSubMenu = hSubMenu;
409 if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
410 {
411 ERR("InsertMenuItemW\n");
412 return E_FAIL;
413 }
414
415 HMENU hOldSubMenu = m_hSubMenu;
416 m_hSubMenu = hSubMenu;
417 DestroyMenu(hOldSubMenu);
418
419 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cItems);
420 }
421
422 STDMETHODIMP
423 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
424 {
425 HRESULT hr = E_FAIL;
426
427 WORD idCmd = LOWORD(lpici->lpVerb);
428 TRACE("idCmd: %d\n", idCmd);
429
430 SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd);
431 if (pItem)
432 {
433 hr = DoSendToItem(pItem, lpici);
434 }
435 else
436 {
437 ERR("FindItemFromIdOffset\n");
438 }
439
440 TRACE("CSendToMenu::InvokeCommand %x\n", hr);
441 return hr;
442 }
443
444 STDMETHODIMP
445 CSendToMenu::GetCommandString(UINT_PTR idCmd,
446 UINT uType,
447 UINT *pwReserved,
448 LPSTR pszName,
449 UINT cchMax)
450 {
451 FIXME("%p %lu %u %p %p %u\n", this,
452 idCmd, uType, pwReserved, pszName, cchMax);
453
454 return E_NOTIMPL;
455 }
456
457 STDMETHODIMP
458 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
459 {
460 return S_OK;
461 }
462
463 STDMETHODIMP
464 CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,
465 LRESULT *plResult)
466 {
467 UINT cxSmall = GetSystemMetrics(SM_CXSMICON);
468 UINT cySmall = GetSystemMetrics(SM_CYSMICON);
469
470 switch (uMsg)
471 {
472 case WM_MEASUREITEM:
473 {
474 MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
475 if (!lpmis || lpmis->CtlType != ODT_MENU)
476 break;
477
478 UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK);
479 if (lpmis->itemWidth < cxMenuCheck)
480 lpmis->itemWidth = cxMenuCheck;
481 if (lpmis->itemHeight < cySmall)
482 lpmis->itemHeight = cySmall;
483
484 if (plResult)
485 *plResult = TRUE;
486 break;
487 }
488 case WM_DRAWITEM:
489 {
490 DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
491 if (!lpdis || lpdis->CtlType != ODT_MENU)
492 break;
493
494 SENDTO_ITEM *pItem = (SENDTO_ITEM *)lpdis->itemData;
495 HICON hIcon = NULL;
496 if (pItem)
497 hIcon = pItem->hIcon;
498 if (!hIcon)
499 break;
500
501 RECT rcItem = lpdis->rcItem;
502 DrawIconEx(lpdis->hDC, 2,
503 lpdis->rcItem.top + (rcItem.bottom - rcItem.top - 16) / 2,
504 hIcon, cxSmall, cySmall, 0, NULL, DI_NORMAL);
505
506 if (plResult)
507 *plResult = TRUE;
508 }
509 }
510
511 return S_OK;
512 }
513
514 STDMETHODIMP
515 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
516 IDataObject *pdtobj, HKEY hkeyProgID)
517 {
518 m_pDataObject = pdtobj;
519 return S_OK;
520 }