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 static BOOL 261 GetPreviousMenuItemInfo(HMENU hMenu, UINT iItem, LPMENUITEMINFOW lpmii) 262 { 263 BOOL bSuccess = FALSE; 264 265 while (iItem > 0) 266 { 267 bSuccess = GetMenuItemInfoW(hMenu, --iItem, TRUE, lpmii); 268 if (bSuccess || (!bSuccess && GetLastError() != ERROR_MENU_ITEM_NOT_FOUND)) 269 break; 270 } 271 272 return bSuccess; 273 } 274 275 STDMETHODIMP 276 CCopyToMenu::QueryContextMenu(HMENU hMenu, 277 UINT indexMenu, 278 UINT idCmdFirst, 279 UINT idCmdLast, 280 UINT uFlags) 281 { 282 MENUITEMINFOW mii; 283 UINT Count = 0; 284 285 TRACE("CCopyToMenu::QueryContextMenu(%p, %u, %u, %u, %u)\n", 286 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 287 288 if (uFlags & (CMF_NOVERBS | CMF_VERBSONLY)) 289 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst); 290 291 m_idCmdFirst = m_idCmdLast = idCmdFirst; 292 293 // insert separator if necessary 294 ZeroMemory(&mii, sizeof(mii)); 295 mii.cbSize = sizeof(mii); 296 mii.fMask = MIIM_TYPE; 297 if (GetPreviousMenuItemInfo(hMenu, indexMenu, &mii) && 298 mii.fType != MFT_SEPARATOR) 299 { 300 ZeroMemory(&mii, sizeof(mii)); 301 mii.cbSize = sizeof(mii); 302 mii.fMask = MIIM_TYPE; 303 mii.fType = MFT_SEPARATOR; 304 if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) 305 { 306 ++indexMenu; 307 ++Count; 308 } 309 } 310 311 // insert "Copy to folder..." 312 CStringW strText(MAKEINTRESOURCEW(IDS_COPYTOMENU)); 313 ZeroMemory(&mii, sizeof(mii)); 314 mii.cbSize = sizeof(mii); 315 mii.fMask = MIIM_ID | MIIM_TYPE; 316 mii.fType = MFT_STRING; 317 mii.dwTypeData = strText.GetBuffer(); 318 mii.cch = wcslen(mii.dwTypeData); 319 mii.wID = m_idCmdLast; 320 if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) 321 { 322 m_idCmdAction = m_idCmdLast++; 323 ++indexMenu; 324 ++Count; 325 } 326 327 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst + Count); 328 } 329 330 STDMETHODIMP 331 CMoveToMenu::QueryContextMenu(HMENU hMenu, 332 UINT indexMenu, 333 UINT idCmdFirst, 334 UINT idCmdLast, 335 UINT uFlags) 336 { 337 MENUITEMINFOW mii; 338 UINT Count = 0; 339 340 TRACE("CMoveToMenu::QueryContextMenu(%p, %u, %u, %u, %u)\n", 341 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 342 343 if (uFlags & (CMF_NOVERBS | CMF_VERBSONLY)) 344 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst); 345 346 m_idCmdFirst = m_idCmdLast = idCmdFirst; 347 348 // insert separator if necessary 349 CStringW strCopyTo(MAKEINTRESOURCEW(IDS_COPYTOMENU)); 350 WCHAR szBuff[128]; 351 ZeroMemory(&mii, sizeof(mii)); 352 mii.cbSize = sizeof(mii); 353 mii.fMask = MIIM_TYPE; 354 mii.dwTypeData = szBuff; 355 mii.cch = _countof(szBuff); 356 if (GetPreviousMenuItemInfo(hMenu, indexMenu, &mii) && 357 mii.fType != MFT_SEPARATOR && 358 !(mii.fType == MFT_STRING && CStringW(szBuff) == strCopyTo)) 359 { 360 ZeroMemory(&mii, sizeof(mii)); 361 mii.cbSize = sizeof(mii); 362 mii.fMask = MIIM_TYPE; 363 mii.fType = MFT_SEPARATOR; 364 if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) 365 { 366 ++indexMenu; 367 ++Count; 368 } 369 } 370 371 // insert "Move to folder..." 372 CStringW strText(MAKEINTRESOURCEW(IDS_MOVETOMENU)); 373 ZeroMemory(&mii, sizeof(mii)); 374 mii.cbSize = sizeof(mii); 375 mii.fMask = MIIM_ID | MIIM_TYPE; 376 mii.fType = MFT_STRING; 377 mii.dwTypeData = strText.GetBuffer(); 378 mii.cch = wcslen(mii.dwTypeData); 379 mii.wID = m_idCmdLast; 380 if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) 381 { 382 m_idCmdAction = m_idCmdLast++; 383 ++indexMenu; 384 ++Count; 385 } 386 387 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst + Count); 388 } 389 390 STDMETHODIMP 391 CCopyMoveToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) 392 { 393 HRESULT hr = E_FAIL; 394 TRACE("CCopyMoveToMenu::InvokeCommand(%p)\n", lpici); 395 396 if (IS_INTRESOURCE(lpici->lpVerb)) 397 { 398 if (m_idCmdFirst + LOWORD(lpici->lpVerb) == m_idCmdAction) 399 hr = DoAction(lpici); 400 } 401 else 402 { 403 if (::lstrcmpiA(lpici->lpVerb, GetVerb()) == 0) 404 hr = DoAction(lpici); 405 } 406 407 return hr; 408 } 409 410 STDMETHODIMP 411 CCopyMoveToMenu::GetCommandString( 412 UINT_PTR idCmd, 413 UINT uType, 414 UINT *pwReserved, 415 LPSTR pszName, 416 UINT cchMax) 417 { 418 FIXME("%p %lu %u %p %p %u\n", this, 419 idCmd, uType, pwReserved, pszName, cchMax); 420 421 return E_NOTIMPL; 422 } 423 424 STDMETHODIMP 425 CCopyMoveToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) 426 { 427 TRACE("This %p uMsg %x\n", this, uMsg); 428 return E_NOTIMPL; 429 } 430 431 STDMETHODIMP 432 CCopyMoveToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) 433 { 434 m_pidlFolder.Attach(ILClone(pidlFolder)); 435 m_pDataObject = pdtobj; 436 return S_OK; 437 } 438 439 STDMETHODIMP 440 CCopyMoveToMenu::SetSite(IUnknown *pUnkSite) 441 { 442 m_pSite = pUnkSite; 443 return S_OK; 444 } 445 446 STDMETHODIMP 447 CCopyMoveToMenu::GetSite(REFIID riid, void **ppvSite) 448 { 449 if (!m_pSite) 450 return E_FAIL; 451 452 return m_pSite->QueryInterface(riid, ppvSite); 453 } 454