[SHELL32][SHELLEXT] Fix use of SHFileOperation results and improve log on failure...
[reactos.git] / dll / win32 / shell32 / CCopyToMenu.cpp
1 /*
2 * PROJECT: shell32
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: CopyTo implementation
5 * COPYRIGHT: Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6 */
7
8 #include "precomp.h"
9
10 WINE_DEFAULT_DEBUG_CHANNEL(shell);
11
12 HRESULT _GetCidlFromDataObject(IDataObject *pDataObject, CIDA** ppcida)
13 {
14 static CLIPFORMAT s_cfHIDA = 0;
15 if (s_cfHIDA == 0)
16 {
17 s_cfHIDA = static_cast<CLIPFORMAT>(RegisterClipboardFormatW(CFSTR_SHELLIDLIST));
18 }
19
20 FORMATETC fmt = { s_cfHIDA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
21 STGMEDIUM medium;
22
23 HRESULT hr = pDataObject->GetData(&fmt, &medium);
24 if (FAILED_UNEXPECTEDLY(hr))
25 return hr;
26
27 LPVOID lpSrc = GlobalLock(medium.hGlobal);
28 SIZE_T cbSize = GlobalSize(medium.hGlobal);
29
30 *ppcida = reinterpret_cast<CIDA *>(::CoTaskMemAlloc(cbSize));
31 if (*ppcida)
32 {
33 memcpy(*ppcida, lpSrc, cbSize);
34 hr = S_OK;
35 }
36 else
37 {
38 ERR("Out of memory\n");
39 hr = E_FAIL;
40 }
41 ReleaseStgMedium(&medium);
42 return hr;
43 }
44
45 CCopyToMenu::CCopyToMenu() :
46 m_idCmdFirst(0),
47 m_idCmdLast(0),
48 m_idCmdCopyTo(-1),
49 m_fnOldWndProc(NULL),
50 m_bIgnoreTextBoxChange(FALSE)
51 {
52 }
53
54 CCopyToMenu::~CCopyToMenu()
55 {
56 }
57
58 static LRESULT CALLBACK
59 WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
60 {
61 WCHAR szPath[MAX_PATH];
62 CCopyToMenu *this_ =
63 reinterpret_cast<CCopyToMenu *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
64
65 switch (uMsg)
66 {
67 case WM_COMMAND:
68 {
69 switch (LOWORD(wParam))
70 {
71 case IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT:
72 {
73 if (HIWORD(wParam) == EN_CHANGE)
74 {
75 if (!this_->m_bIgnoreTextBoxChange)
76 {
77 // get the text
78 GetDlgItemTextW(hwnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT, szPath, _countof(szPath));
79 StrTrimW(szPath, L" \t");
80
81 // update OK button
82 BOOL bValid = !PathIsRelative(szPath) && PathIsDirectoryW(szPath);
83 SendMessageW(hwnd, BFFM_ENABLEOK, 0, bValid);
84
85 return 0;
86 }
87
88 // reset flag
89 this_->m_bIgnoreTextBoxChange = FALSE;
90 }
91 break;
92 }
93 }
94 break;
95 }
96 }
97 return CallWindowProcW(this_->m_fnOldWndProc, hwnd, uMsg, wParam, lParam);
98 }
99
100 static int CALLBACK
101 BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
102 {
103 CCopyToMenu *this_ =
104 reinterpret_cast<CCopyToMenu *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
105
106 switch (uMsg)
107 {
108 case BFFM_INITIALIZED:
109 {
110 SetWindowLongPtr(hwnd, GWLP_USERDATA, lpData);
111 this_ = reinterpret_cast<CCopyToMenu *>(lpData);
112
113 // Select initial directory
114 SendMessageW(hwnd, BFFM_SETSELECTION, FALSE,
115 reinterpret_cast<LPARAM>(static_cast<LPCITEMIDLIST>(this_->m_pidlFolder)));
116
117 // Set caption
118 CString strCaption(MAKEINTRESOURCEW(IDS_COPYITEMS));
119 SetWindowTextW(hwnd, strCaption);
120
121 // Set OK button text
122 CString strCopy(MAKEINTRESOURCEW(IDS_COPYBUTTON));
123 SetDlgItemText(hwnd, IDOK, strCopy);
124
125 // Subclassing
126 this_->m_fnOldWndProc =
127 reinterpret_cast<WNDPROC>(
128 SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc)));
129
130 // Disable OK
131 PostMessageW(hwnd, BFFM_ENABLEOK, 0, FALSE);
132 break;
133 }
134 case BFFM_SELCHANGED:
135 {
136 WCHAR szPath[MAX_PATH];
137 LPCITEMIDLIST pidl = reinterpret_cast<LPCITEMIDLIST>(lParam);
138
139 szPath[0] = 0;
140 SHGetPathFromIDListW(pidl, szPath);
141
142 if (ILIsEqual(pidl, this_->m_pidlFolder))
143 PostMessageW(hwnd, BFFM_ENABLEOK, 0, FALSE);
144 else if (PathFileExistsW(szPath) || _ILIsDesktop(pidl))
145 PostMessageW(hwnd, BFFM_ENABLEOK, 0, TRUE);
146 else
147 PostMessageW(hwnd, BFFM_ENABLEOK, 0, FALSE);
148
149 // the text box will be updated later soon, ignore it
150 this_->m_bIgnoreTextBoxChange = TRUE;
151 break;
152 }
153 }
154
155 return FALSE;
156 }
157
158 HRESULT CCopyToMenu::DoRealCopy(LPCMINVOKECOMMANDINFO lpici, LPCITEMIDLIST pidl)
159 {
160 CComHeapPtr<CIDA> pCIDA;
161 HRESULT hr = _GetCidlFromDataObject(m_pDataObject, &pCIDA);
162 if (FAILED_UNEXPECTEDLY(hr))
163 return hr;
164
165 PCUIDLIST_ABSOLUTE pidlParent = HIDA_GetPIDLFolder(pCIDA);
166 if (!pidlParent)
167 {
168 ERR("HIDA_GetPIDLFolder failed\n");
169 return E_FAIL;
170 }
171
172 CStringW strFiles;
173 WCHAR szPath[MAX_PATH];
174 for (UINT n = 0; n < pCIDA->cidl; ++n)
175 {
176 PCUIDLIST_RELATIVE pidlRelative = HIDA_GetPIDLItem(pCIDA, n);
177 if (!pidlRelative)
178 continue;
179
180 CComHeapPtr<ITEMIDLIST> pidlCombine(ILCombine(pidlParent, pidlRelative));
181 if (!pidl)
182 return E_FAIL;
183
184 SHGetPathFromIDListW(pidlCombine, szPath);
185
186 if (n > 0)
187 strFiles += L'|';
188 strFiles += szPath;
189 }
190
191 strFiles += L'|'; // double null-terminated
192 strFiles.Replace(L'|', L'\0');
193
194 if (_ILIsDesktop(pidl))
195 SHGetSpecialFolderPathW(NULL, szPath, CSIDL_DESKTOPDIRECTORY, FALSE);
196 else
197 SHGetPathFromIDListW(pidl, szPath);
198 INT cchPath = lstrlenW(szPath);
199 if (cchPath + 1 < MAX_PATH)
200 {
201 szPath[cchPath + 1] = 0; // double null-terminated
202 }
203 else
204 {
205 ERR("Too long path\n");
206 return E_FAIL;
207 }
208
209 SHFILEOPSTRUCTW op = { lpici->hwnd };
210 op.wFunc = FO_COPY;
211 op.pFrom = strFiles;
212 op.pTo = szPath;
213 op.fFlags = FOF_ALLOWUNDO;
214 int res = SHFileOperationW(&op);
215 if (res)
216 {
217 ERR("SHFileOperationW failed with 0x%x\n", res);
218 return E_FAIL;
219 }
220 return S_OK;
221 }
222
223 CStringW CCopyToMenu::DoGetFileTitle()
224 {
225 CStringW ret = L"(file)";
226
227 CComHeapPtr<CIDA> pCIDA;
228 HRESULT hr = _GetCidlFromDataObject(m_pDataObject, &pCIDA);
229 if (FAILED_UNEXPECTEDLY(hr))
230 return ret;
231
232 PCUIDLIST_ABSOLUTE pidlParent = HIDA_GetPIDLFolder(pCIDA);
233 if (!pidlParent)
234 {
235 ERR("HIDA_GetPIDLFolder failed\n");
236 return ret;
237 }
238
239 WCHAR szPath[MAX_PATH];
240 PCUIDLIST_RELATIVE pidlRelative = HIDA_GetPIDLItem(pCIDA, 0);
241 if (!pidlRelative)
242 {
243 ERR("HIDA_GetPIDLItem failed\n");
244 return ret;
245 }
246
247 CComHeapPtr<ITEMIDLIST> pidlCombine(ILCombine(pidlParent, pidlRelative));
248
249 if (SHGetPathFromIDListW(pidlCombine, szPath))
250 ret = PathFindFileNameW(szPath);
251 else
252 ERR("Cannot get path\n");
253
254 if (pCIDA->cidl > 1)
255 ret += L" ...";
256
257 return ret;
258 }
259
260 HRESULT CCopyToMenu::DoCopyToFolder(LPCMINVOKECOMMANDINFO lpici)
261 {
262 WCHAR wszPath[MAX_PATH];
263 HRESULT hr = E_FAIL;
264
265 TRACE("DoCopyToFolder(%p)\n", lpici);
266
267 if (!SHGetPathFromIDListW(m_pidlFolder, wszPath))
268 {
269 ERR("SHGetPathFromIDListW failed\n");
270 return hr;
271 }
272
273 CStringW strFileTitle = DoGetFileTitle();
274 CStringW strTitle;
275 strTitle.Format(IDS_COPYTOTITLE, static_cast<LPCWSTR>(strFileTitle));
276
277 BROWSEINFOW info = { lpici->hwnd };
278 info.pidlRoot = NULL;
279 info.lpszTitle = strTitle;
280 info.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
281 info.lpfn = BrowseCallbackProc;
282 info.lParam = reinterpret_cast<LPARAM>(this);
283 CComHeapPtr<ITEMIDLIST> pidl(SHBrowseForFolder(&info));
284 if (pidl)
285 {
286 hr = DoRealCopy(lpici, pidl);
287 }
288
289 return hr;
290 }
291
292 HRESULT WINAPI
293 CCopyToMenu::QueryContextMenu(HMENU hMenu,
294 UINT indexMenu,
295 UINT idCmdFirst,
296 UINT idCmdLast,
297 UINT uFlags)
298 {
299 MENUITEMINFOW mii;
300 UINT Count = 0;
301
302 TRACE("CCopyToMenu::QueryContextMenu(%p, %u, %u, %u, %u)\n",
303 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
304
305 m_idCmdFirst = m_idCmdLast = idCmdFirst;
306
307 // insert separator if necessary
308 ZeroMemory(&mii, sizeof(mii));
309 mii.cbSize = sizeof(mii);
310 mii.fMask = MIIM_TYPE;
311 if (GetMenuItemInfoW(hMenu, indexMenu - 1, TRUE, &mii) &&
312 mii.fType != MFT_SEPARATOR)
313 {
314 ZeroMemory(&mii, sizeof(mii));
315 mii.cbSize = sizeof(mii);
316 mii.fMask = MIIM_TYPE;
317 mii.fType = MFT_SEPARATOR;
318 if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
319 {
320 ++indexMenu;
321 ++Count;
322 }
323 }
324
325 // insert "Copy to folder..."
326 CStringW strText(MAKEINTRESOURCEW(IDS_COPYTOMENU));
327 ZeroMemory(&mii, sizeof(mii));
328 mii.cbSize = sizeof(mii);
329 mii.fMask = MIIM_ID | MIIM_TYPE;
330 mii.fType = MFT_STRING;
331 mii.dwTypeData = strText.GetBuffer();
332 mii.cch = wcslen(mii.dwTypeData);
333 mii.wID = m_idCmdLast;
334 if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
335 {
336 m_idCmdCopyTo = m_idCmdLast++;
337 ++indexMenu;
338 ++Count;
339 }
340
341 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, Count);
342 }
343
344 HRESULT WINAPI
345 CCopyToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
346 {
347 HRESULT hr = E_FAIL;
348 TRACE("CCopyToMenu::InvokeCommand(%p)\n", lpici);
349
350 if (HIWORD(lpici->lpVerb) == 0)
351 {
352 if (m_idCmdFirst + LOWORD(lpici->lpVerb) == m_idCmdCopyTo)
353 {
354 hr = DoCopyToFolder(lpici);
355 }
356 }
357 else
358 {
359 if (::lstrcmpiA(lpici->lpVerb, "copyto") == 0)
360 {
361 hr = DoCopyToFolder(lpici);
362 }
363 }
364
365 return hr;
366 }
367
368 HRESULT WINAPI
369 CCopyToMenu::GetCommandString(UINT_PTR idCmd,
370 UINT uType,
371 UINT *pwReserved,
372 LPSTR pszName,
373 UINT cchMax)
374 {
375 FIXME("%p %lu %u %p %p %u\n", this,
376 idCmd, uType, pwReserved, pszName, cchMax);
377
378 return E_NOTIMPL;
379 }
380
381 HRESULT WINAPI
382 CCopyToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
383 {
384 TRACE("This %p uMsg %x\n", this, uMsg);
385 return E_NOTIMPL;
386 }
387
388 HRESULT WINAPI
389 CCopyToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
390 IDataObject *pdtobj, HKEY hkeyProgID)
391 {
392 m_pidlFolder.Attach(ILClone(pidlFolder));
393 m_pDataObject = pdtobj;
394 return S_OK;
395 }
396
397 HRESULT WINAPI CCopyToMenu::SetSite(IUnknown *pUnkSite)
398 {
399 m_pSite = pUnkSite;
400 return S_OK;
401 }
402
403 HRESULT WINAPI CCopyToMenu::GetSite(REFIID riid, void **ppvSite)
404 {
405 if (!m_pSite)
406 return E_FAIL;
407
408 return m_pSite->QueryInterface(riid, ppvSite);
409 }