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