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)
10 WINE_DEFAULT_DEBUG_CHANNEL(shell
);
12 HRESULT
_GetCidlFromDataObject(IDataObject
*pDataObject
, CIDA
** ppcida
)
14 static CLIPFORMAT s_cfHIDA
= 0;
17 s_cfHIDA
= static_cast<CLIPFORMAT
>(RegisterClipboardFormatW(CFSTR_SHELLIDLIST
));
20 FORMATETC fmt
= { s_cfHIDA
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
23 HRESULT hr
= pDataObject
->GetData(&fmt
, &medium
);
24 if (FAILED_UNEXPECTEDLY(hr
))
27 LPVOID lpSrc
= GlobalLock(medium
.hGlobal
);
28 SIZE_T cbSize
= GlobalSize(medium
.hGlobal
);
30 *ppcida
= reinterpret_cast<CIDA
*>(::CoTaskMemAlloc(cbSize
));
33 memcpy(*ppcida
, lpSrc
, cbSize
);
38 ERR("Out of memory\n");
41 ReleaseStgMedium(&medium
);
45 CCopyToMenu::CCopyToMenu() :
50 m_bIgnoreTextBoxChange(FALSE
)
54 CCopyToMenu::~CCopyToMenu()
58 static LRESULT CALLBACK
59 WindowProc(HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
61 WCHAR szPath
[MAX_PATH
];
63 reinterpret_cast<CCopyToMenu
*>(GetWindowLongPtr(hwnd
, GWLP_USERDATA
));
69 switch (LOWORD(wParam
))
71 case IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT
:
73 if (HIWORD(wParam
) == EN_CHANGE
)
75 if (!this_
->m_bIgnoreTextBoxChange
)
78 GetDlgItemTextW(hwnd
, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT
, szPath
, _countof(szPath
));
79 StrTrimW(szPath
, L
" \t");
82 BOOL bValid
= !PathIsRelative(szPath
) && PathIsDirectoryW(szPath
);
83 SendMessageW(hwnd
, BFFM_ENABLEOK
, 0, bValid
);
89 this_
->m_bIgnoreTextBoxChange
= FALSE
;
97 return CallWindowProcW(this_
->m_fnOldWndProc
, hwnd
, uMsg
, wParam
, lParam
);
101 BrowseCallbackProc(HWND hwnd
, UINT uMsg
, LPARAM lParam
, LPARAM lpData
)
104 reinterpret_cast<CCopyToMenu
*>(GetWindowLongPtr(hwnd
, GWLP_USERDATA
));
108 case BFFM_INITIALIZED
:
110 SetWindowLongPtr(hwnd
, GWLP_USERDATA
, lpData
);
111 this_
= reinterpret_cast<CCopyToMenu
*>(lpData
);
113 // Select initial directory
114 SendMessageW(hwnd
, BFFM_SETSELECTION
, FALSE
,
115 reinterpret_cast<LPARAM
>(static_cast<LPCITEMIDLIST
>(this_
->m_pidlFolder
)));
118 CString
strCaption(MAKEINTRESOURCEW(IDS_COPYITEMS
));
119 SetWindowTextW(hwnd
, strCaption
);
121 // Set OK button text
122 CString
strCopy(MAKEINTRESOURCEW(IDS_COPYBUTTON
));
123 SetDlgItemText(hwnd
, IDOK
, strCopy
);
126 this_
->m_fnOldWndProc
=
127 reinterpret_cast<WNDPROC
>(
128 SetWindowLongPtr(hwnd
, GWLP_WNDPROC
, reinterpret_cast<LONG_PTR
>(WindowProc
)));
131 PostMessageW(hwnd
, BFFM_ENABLEOK
, 0, FALSE
);
134 case BFFM_SELCHANGED
:
136 WCHAR szPath
[MAX_PATH
];
137 LPCITEMIDLIST pidl
= reinterpret_cast<LPCITEMIDLIST
>(lParam
);
140 SHGetPathFromIDListW(pidl
, szPath
);
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
);
147 PostMessageW(hwnd
, BFFM_ENABLEOK
, 0, FALSE
);
149 // the text box will be updated later soon, ignore it
150 this_
->m_bIgnoreTextBoxChange
= TRUE
;
158 HRESULT
CCopyToMenu::DoRealCopy(LPCMINVOKECOMMANDINFO lpici
, LPCITEMIDLIST pidl
)
160 CComHeapPtr
<CIDA
> pCIDA
;
161 HRESULT hr
= _GetCidlFromDataObject(m_pDataObject
, &pCIDA
);
162 if (FAILED_UNEXPECTEDLY(hr
))
165 PCUIDLIST_ABSOLUTE pidlParent
= HIDA_GetPIDLFolder(pCIDA
);
168 ERR("HIDA_GetPIDLFolder failed\n");
173 WCHAR szPath
[MAX_PATH
];
174 for (UINT n
= 0; n
< pCIDA
->cidl
; ++n
)
176 PCUIDLIST_RELATIVE pidlRelative
= HIDA_GetPIDLItem(pCIDA
, n
);
180 CComHeapPtr
<ITEMIDLIST
> pidlCombine(ILCombine(pidlParent
, pidlRelative
));
184 SHGetPathFromIDListW(pidlCombine
, szPath
);
191 strFiles
+= L
'|'; // double null-terminated
192 strFiles
.Replace(L
'|', L
'\0');
194 if (_ILIsDesktop(pidl
))
195 SHGetSpecialFolderPathW(NULL
, szPath
, CSIDL_DESKTOPDIRECTORY
, FALSE
);
197 SHGetPathFromIDListW(pidl
, szPath
);
198 INT cchPath
= lstrlenW(szPath
);
199 if (cchPath
+ 1 < MAX_PATH
)
201 szPath
[cchPath
+ 1] = 0; // double null-terminated
205 ERR("Too long path\n");
209 SHFILEOPSTRUCTW op
= { lpici
->hwnd
};
213 op
.fFlags
= FOF_ALLOWUNDO
;
214 int res
= SHFileOperationW(&op
);
217 ERR("SHFileOperationW failed with 0x%x\n", res
);
223 CStringW
CCopyToMenu::DoGetFileTitle()
225 CStringW ret
= L
"(file)";
227 CComHeapPtr
<CIDA
> pCIDA
;
228 HRESULT hr
= _GetCidlFromDataObject(m_pDataObject
, &pCIDA
);
229 if (FAILED_UNEXPECTEDLY(hr
))
232 PCUIDLIST_ABSOLUTE pidlParent
= HIDA_GetPIDLFolder(pCIDA
);
235 ERR("HIDA_GetPIDLFolder failed\n");
239 WCHAR szPath
[MAX_PATH
];
240 PCUIDLIST_RELATIVE pidlRelative
= HIDA_GetPIDLItem(pCIDA
, 0);
243 ERR("HIDA_GetPIDLItem failed\n");
247 CComHeapPtr
<ITEMIDLIST
> pidlCombine(ILCombine(pidlParent
, pidlRelative
));
249 if (SHGetPathFromIDListW(pidlCombine
, szPath
))
250 ret
= PathFindFileNameW(szPath
);
252 ERR("Cannot get path\n");
260 HRESULT
CCopyToMenu::DoCopyToFolder(LPCMINVOKECOMMANDINFO lpici
)
262 WCHAR wszPath
[MAX_PATH
];
265 TRACE("DoCopyToFolder(%p)\n", lpici
);
267 if (!SHGetPathFromIDListW(m_pidlFolder
, wszPath
))
269 ERR("SHGetPathFromIDListW failed\n");
273 CStringW strFileTitle
= DoGetFileTitle();
275 strTitle
.Format(IDS_COPYTOTITLE
, static_cast<LPCWSTR
>(strFileTitle
));
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
));
286 hr
= DoRealCopy(lpici
, pidl
);
293 CCopyToMenu::QueryContextMenu(HMENU hMenu
,
302 TRACE("CCopyToMenu::QueryContextMenu(%p, %u, %u, %u, %u)\n",
303 hMenu
, indexMenu
, idCmdFirst
, idCmdLast
, uFlags
);
305 m_idCmdFirst
= m_idCmdLast
= idCmdFirst
;
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
)
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
))
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
))
336 m_idCmdCopyTo
= m_idCmdLast
++;
341 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, Count
);
345 CCopyToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici
)
348 TRACE("CCopyToMenu::InvokeCommand(%p)\n", lpici
);
350 if (HIWORD(lpici
->lpVerb
) == 0)
352 if (m_idCmdFirst
+ LOWORD(lpici
->lpVerb
) == m_idCmdCopyTo
)
354 hr
= DoCopyToFolder(lpici
);
359 if (::lstrcmpiA(lpici
->lpVerb
, "copyto") == 0)
361 hr
= DoCopyToFolder(lpici
);
369 CCopyToMenu::GetCommandString(UINT_PTR idCmd
,
375 FIXME("%p %lu %u %p %p %u\n", this,
376 idCmd
, uType
, pwReserved
, pszName
, cchMax
);
382 CCopyToMenu::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
384 TRACE("This %p uMsg %x\n", this, uMsg
);
389 CCopyToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder
,
390 IDataObject
*pdtobj
, HKEY hkeyProgID
)
392 m_pidlFolder
.Attach(ILClone(pidlFolder
));
393 m_pDataObject
= pdtobj
;
397 HRESULT WINAPI
CCopyToMenu::SetSite(IUnknown
*pUnkSite
)
403 HRESULT WINAPI
CCopyToMenu::GetSite(REFIID riid
, void **ppvSite
)
408 return m_pSite
->QueryInterface(riid
, ppvSite
);