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