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