[SHELL32] -CFSDropTarget: Simplyfy the code path that handles the CFSTR_SHELLIDLIST...
[reactos.git] / reactos / dll / win32 / shell32 / droptargets / CFSDropTarget.cpp
1
2 /*
3 * file system folder drop target
4 *
5 * Copyright 1997 Marcus Meissner
6 * Copyright 1998, 1999, 2002 Juergen Schmied
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23 #include <precomp.h>
24
25 WINE_DEFAULT_DEBUG_CHANNEL (shell);
26
27 /****************************************************************************
28 * BuildPathsList
29 *
30 * Builds a list of paths like the one used in SHFileOperation from a table of
31 * PIDLs relative to the given base folder
32 */
33 static WCHAR* BuildPathsList(LPCWSTR wszBasePath, int cidl, LPCITEMIDLIST *pidls)
34 {
35 WCHAR *pwszPathsList = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR) * cidl + 1);
36 WCHAR *pwszListPos = pwszPathsList;
37
38 for (int i = 0; i < cidl; i++)
39 {
40 FileStructW* pDataW = _ILGetFileStructW(pidls[i]);
41 if (!pDataW)
42 {
43 ERR("Got garbage pidl\n");
44 continue;
45 }
46
47 PathCombineW(pwszListPos, wszBasePath, pDataW->wszName);
48 pwszListPos += wcslen(pwszListPos) + 1;
49 }
50 *pwszListPos = 0;
51 return pwszPathsList;
52 }
53
54 /****************************************************************************
55 * CFSDropTarget::CopyItems
56 *
57 * copies items to this folder
58 */
59 HRESULT WINAPI CFSDropTarget::CopyItems(IShellFolder * pSFFrom, UINT cidl,
60 LPCITEMIDLIST * apidl, BOOL bCopy)
61 {
62 LPWSTR pszSrcList;
63 HRESULT hr;
64 WCHAR wszTargetPath[MAX_PATH + 1];
65
66 wcscpy(wszTargetPath, sPathTarget);
67 //Double NULL terminate.
68 wszTargetPath[wcslen(wszTargetPath) + 1] = '\0';
69
70 TRACE ("(%p)->(%p,%u,%p)\n", this, pSFFrom, cidl, apidl);
71
72 STRRET strretFrom;
73 hr = pSFFrom->GetDisplayNameOf(NULL, SHGDN_FORPARSING, &strretFrom);
74 if (FAILED_UNEXPECTEDLY(hr))
75 return hr;
76
77 pszSrcList = BuildPathsList(strretFrom.pOleStr, cidl, apidl);
78 ERR("Source file (just the first) = %s, target path = %s\n", debugstr_w(strretFrom.pOleStr), debugstr_w(sPathTarget));
79 CoTaskMemFree(strretFrom.pOleStr);
80 if (!pszSrcList)
81 return E_OUTOFMEMORY;
82
83 SHFILEOPSTRUCTW op = {0};
84 op.pFrom = pszSrcList;
85 op.pTo = wszTargetPath;
86 op.hwnd = GetActiveWindow();
87 op.wFunc = bCopy ? FO_COPY : FO_MOVE;
88 op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR;
89
90 int res = SHFileOperationW(&op);
91
92 HeapFree(GetProcessHeap(), 0, pszSrcList);
93
94 if (res)
95 return E_FAIL;
96 else
97 return S_OK;
98 }
99
100 CFSDropTarget::CFSDropTarget():
101 cfShellIDList(0),
102 fAcceptFmt(FALSE),
103 sPathTarget(NULL)
104 {
105 }
106
107 HRESULT WINAPI CFSDropTarget::Initialize(LPWSTR PathTarget)
108 {
109 if (!PathTarget)
110 return E_UNEXPECTED;
111
112 cfShellIDList = RegisterClipboardFormatW(CFSTR_SHELLIDLIST);
113 if (!cfShellIDList)
114 return E_FAIL;
115
116 sPathTarget = (WCHAR *)SHAlloc((wcslen(PathTarget) + 1) * sizeof(WCHAR));
117 if (!sPathTarget)
118 return E_OUTOFMEMORY;
119
120 wcscpy(sPathTarget, PathTarget);
121
122 return S_OK;
123 }
124
125 CFSDropTarget::~CFSDropTarget()
126 {
127 SHFree(sPathTarget);
128 }
129
130 BOOL
131 CFSDropTarget::GetUniqueFileName(LPWSTR pwszBasePath, LPCWSTR pwszExt, LPWSTR pwszTarget, BOOL bShortcut)
132 {
133 WCHAR wszLink[40];
134
135 if (!bShortcut)
136 {
137 if (!LoadStringW(shell32_hInstance, IDS_LNK_FILE, wszLink, _countof(wszLink)))
138 wszLink[0] = L'\0';
139 }
140
141 if (!bShortcut)
142 swprintf(pwszTarget, L"%s%s%s", wszLink, pwszBasePath, pwszExt);
143 else
144 swprintf(pwszTarget, L"%s%s", pwszBasePath, pwszExt);
145
146 for (UINT i = 2; PathFileExistsW(pwszTarget); ++i)
147 {
148 if (!bShortcut)
149 swprintf(pwszTarget, L"%s%s (%u)%s", wszLink, pwszBasePath, i, pwszExt);
150 else
151 swprintf(pwszTarget, L"%s (%u)%s", pwszBasePath, i, pwszExt);
152 }
153
154 return TRUE;
155 }
156
157 /****************************************************************************
158 * IDropTarget implementation
159 */
160 BOOL CFSDropTarget::QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect)
161 {
162 /* TODO Windows does different drop effects if dragging across drives.
163 i.e., it will copy instead of move if the directories are on different disks. */
164
165 DWORD dwEffect = DROPEFFECT_MOVE;
166
167 *pdwEffect = DROPEFFECT_NONE;
168
169 if (fAcceptFmt) { /* Does our interpretation of the keystate ... */
170 *pdwEffect = KeyStateToDropEffect (dwKeyState);
171
172 if (*pdwEffect == DROPEFFECT_NONE)
173 *pdwEffect = dwEffect;
174
175 /* ... matches the desired effect ? */
176 if (dwEffect & *pdwEffect) {
177 return TRUE;
178 }
179 }
180 return FALSE;
181 }
182
183 HRESULT WINAPI CFSDropTarget::DragEnter(IDataObject *pDataObject,
184 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
185 {
186 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject);
187 FORMATETC fmt;
188 FORMATETC fmt2;
189 fAcceptFmt = FALSE;
190
191 InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL);
192 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
193
194 if (SUCCEEDED(pDataObject->QueryGetData(&fmt)))
195 fAcceptFmt = TRUE;
196 else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2)))
197 fAcceptFmt = TRUE;
198
199 QueryDrop(dwKeyState, pdwEffect);
200 return S_OK;
201 }
202
203 HRESULT WINAPI CFSDropTarget::DragOver(DWORD dwKeyState, POINTL pt,
204 DWORD *pdwEffect)
205 {
206 TRACE("(%p)\n", this);
207
208 if (!pdwEffect)
209 return E_INVALIDARG;
210
211 QueryDrop(dwKeyState, pdwEffect);
212
213 return S_OK;
214 }
215
216 HRESULT WINAPI CFSDropTarget::DragLeave()
217 {
218 TRACE("(%p)\n", this);
219
220 fAcceptFmt = FALSE;
221
222 return S_OK;
223 }
224
225 HRESULT WINAPI CFSDropTarget::Drop(IDataObject *pDataObject,
226 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
227 {
228 TRACE("(%p) object dropped, effect %u\n", this, *pdwEffect);
229
230 if (!pdwEffect)
231 return E_INVALIDARG;
232
233 QueryDrop(dwKeyState, pdwEffect);
234
235 BOOL fIsOpAsync = FALSE;
236 CComPtr<IAsyncOperation> pAsyncOperation;
237
238 if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation))))
239 {
240 if (SUCCEEDED(pAsyncOperation->GetAsyncMode(&fIsOpAsync)) && fIsOpAsync)
241 {
242 _DoDropData *data = static_cast<_DoDropData*>(HeapAlloc(GetProcessHeap(), 0, sizeof(_DoDropData)));
243 data->This = this;
244 // Need to maintain this class in case the window is closed or the class exists temporarily (when dropping onto a folder).
245 pDataObject->AddRef();
246 pAsyncOperation->StartOperation(NULL);
247 CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDataObject, &data->pStream);
248 this->AddRef();
249 data->dwKeyState = dwKeyState;
250 data->pt = pt;
251 // Need to dereference as pdweffect gets freed.
252 data->pdwEffect = *pdwEffect;
253 SHCreateThread(CFSDropTarget::_DoDropThreadProc, data, NULL, NULL);
254 return S_OK;
255 }
256 }
257 return this->_DoDrop(pDataObject, dwKeyState, pt, pdwEffect);
258 }
259
260 HRESULT WINAPI CFSDropTarget::_DoDrop(IDataObject *pDataObject,
261 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
262 {
263 TRACE("(%p) performing drop, effect %u\n", this, *pdwEffect);
264 FORMATETC fmt;
265 FORMATETC fmt2;
266 STGMEDIUM medium;
267
268 InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL);
269 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
270
271 HRESULT hr;
272 bool bCopy = TRUE;
273 bool bLinking = FALSE;
274
275 /* Figure out what drop operation we're doing */
276 if (pdwEffect)
277 {
278 TRACE("Current drop effect flag %i\n", *pdwEffect);
279 if ((*pdwEffect & DROPEFFECT_MOVE) == DROPEFFECT_MOVE)
280 bCopy = FALSE;
281 if ((*pdwEffect & DROPEFFECT_LINK) == DROPEFFECT_LINK)
282 bLinking = TRUE;
283 }
284
285 if (SUCCEEDED(pDataObject->QueryGetData(&fmt)))
286 {
287 hr = pDataObject->GetData(&fmt, &medium);
288 TRACE("CFSTR_SHELLIDLIST.\n");
289
290 /* lock the handle */
291 LPIDA lpcida = (LPIDA)GlobalLock(medium.hGlobal);
292 if (!lpcida)
293 {
294 ReleaseStgMedium(&medium);
295 return E_FAIL;
296 }
297
298 /* convert the data into pidl */
299 LPITEMIDLIST pidl;
300 LPITEMIDLIST *apidl = _ILCopyCidaToaPidl(&pidl, lpcida);
301 if (!apidl)
302 {
303 ReleaseStgMedium(&medium);
304 return E_FAIL;
305 }
306
307 CComPtr<IShellFolder> psfDesktop;
308 CComPtr<IShellFolder> psfFrom = NULL;
309
310 /* Grab the desktop shell folder */
311 hr = SHGetDesktopFolder(&psfDesktop);
312 if (FAILED(hr))
313 {
314 ERR("SHGetDesktopFolder failed\n");
315 SHFree(pidl);
316 _ILFreeaPidl(apidl, lpcida->cidl);
317 ReleaseStgMedium(&medium);
318 return E_FAIL;
319 }
320
321 /* Find source folder, this is where the clipboard data was copied from */
322 if (_ILIsDesktop(pidl))
323 {
324 /* use desktop shell folder */
325 psfFrom = psfDesktop;
326 }
327 else
328 {
329 hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfFrom));
330 if (FAILED(hr))
331 {
332 ERR("no IShellFolder\n");
333 SHFree(pidl);
334 _ILFreeaPidl(apidl, lpcida->cidl);
335 ReleaseStgMedium(&medium);
336 return E_FAIL;
337 }
338 }
339
340 if (bLinking)
341 {
342 WCHAR wszTargetPath[MAX_PATH];
343 WCHAR wszPath[MAX_PATH];
344 WCHAR wszTarget[MAX_PATH];
345
346 wcscpy(wszTargetPath, sPathTarget);
347
348 TRACE("target path = %s", debugstr_w(wszTargetPath));
349
350 /* We need to create a link for each pidl in the copied items, so step through the pidls from the clipboard */
351 for (UINT i = 0; i < lpcida->cidl; i++)
352 {
353 //Find out which file we're copying
354 STRRET strFile;
355 hr = psfFrom->GetDisplayNameOf(apidl[i], SHGDN_FORPARSING, &strFile);
356 if (FAILED(hr))
357 {
358 ERR("Error source obtaining path");
359 break;
360 }
361
362 hr = StrRetToBufW(&strFile, apidl[i], wszPath, _countof(wszPath));
363 if (FAILED(hr))
364 {
365 ERR("Error putting source path into buffer");
366 break;
367 }
368 TRACE("source path = %s", debugstr_w(wszPath));
369
370 // Creating a buffer to hold the combined path
371 WCHAR buffer_1[MAX_PATH] = L"";
372 WCHAR *lpStr1;
373 lpStr1 = buffer_1;
374
375 LPWSTR pwszFileName = PathFindFileNameW(wszPath);
376 LPWSTR pwszExt = PathFindExtensionW(wszPath);
377 LPWSTR placementPath = PathCombineW(lpStr1, sPathTarget, pwszFileName);
378 CComPtr<IPersistFile> ppf;
379
380 //Check to see if it's already a link.
381 if (!wcsicmp(pwszExt, L".lnk"))
382 {
383 //It's a link so, we create a new one which copies the old.
384 if(!GetUniqueFileName(placementPath, pwszExt, wszTarget, TRUE))
385 {
386 ERR("Error getting unique file name");
387 hr = E_FAIL;
388 break;
389 }
390 hr = IShellLink_ConstructFromPath(wszPath, IID_PPV_ARG(IPersistFile, &ppf));
391 if (FAILED(hr)) {
392 ERR("Error constructing link from file");
393 break;
394 }
395
396 hr = ppf->Save(wszTarget, FALSE);
397 if (FAILED(hr))
398 break;
399 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL);
400 }
401 else
402 {
403 //It's not a link, so build a new link using the creator class and fill it in.
404 //Create a file name for the link
405 if (!GetUniqueFileName(placementPath, L".lnk", wszTarget, TRUE))
406 {
407 ERR("Error creating unique file name");
408 hr = E_FAIL;
409 break;
410 }
411
412 CComPtr<IShellLinkW> pLink;
413 hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IShellLinkW, &pLink));
414 if (FAILED(hr)) {
415 ERR("Error instantiating IShellLinkW");
416 break;
417 }
418
419 WCHAR szDirPath[MAX_PATH], *pwszFile;
420 GetFullPathName(wszPath, MAX_PATH, szDirPath, &pwszFile);
421 if (pwszFile) pwszFile[0] = 0;
422
423 hr = pLink->SetPath(wszPath);
424 if(FAILED(hr))
425 break;
426
427 hr = pLink->SetWorkingDirectory(szDirPath);
428 if(FAILED(hr))
429 break;
430
431 hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
432 if(FAILED(hr))
433 break;
434
435 hr = ppf->Save(wszTarget, TRUE);
436 if (FAILED(hr))
437 break;
438 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL);
439 }
440 }
441 }
442 else
443 {
444 hr = this->CopyItems(psfFrom, lpcida->cidl, (LPCITEMIDLIST*)apidl, bCopy);
445 }
446
447 SHFree(pidl);
448 _ILFreeaPidl(apidl, lpcida->cidl);
449 ReleaseStgMedium(&medium);
450 }
451 else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2)))
452 {
453 FORMATETC fmt2;
454 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
455 if (SUCCEEDED(pDataObject->GetData(&fmt2, &medium)) /* && SUCCEEDED(pDataObject->GetData(&fmt2, &medium))*/)
456 {
457 WCHAR wszTargetPath[MAX_PATH + 1];
458 LPWSTR pszSrcList;
459
460 wcscpy(wszTargetPath, sPathTarget);
461 //Double NULL terminate.
462 wszTargetPath[wcslen(wszTargetPath) + 1] = '\0';
463
464 LPDROPFILES lpdf = (LPDROPFILES) GlobalLock(medium.hGlobal);
465 if (!lpdf)
466 {
467 ERR("Error locking global\n");
468 return E_FAIL;
469 }
470 pszSrcList = (LPWSTR) (((byte*) lpdf) + lpdf->pFiles);
471 ERR("Source file (just the first) = %s, target path = %s\n", debugstr_w(pszSrcList), debugstr_w(wszTargetPath));
472
473 SHFILEOPSTRUCTW op;
474 ZeroMemory(&op, sizeof(op));
475 op.pFrom = pszSrcList;
476 op.pTo = wszTargetPath;
477 op.hwnd = GetActiveWindow();
478 op.wFunc = bCopy ? FO_COPY : FO_MOVE;
479 op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR;
480 hr = SHFileOperationW(&op);
481 return hr;
482 }
483 ERR("Error calling GetData\n");
484 hr = E_FAIL;
485 }
486 else
487 {
488 ERR("No viable drop format.\n");
489 hr = E_FAIL;
490 }
491 return hr;
492 }
493
494 DWORD WINAPI CFSDropTarget::_DoDropThreadProc(LPVOID lpParameter)
495 {
496 CoInitialize(NULL);
497 _DoDropData *data = static_cast<_DoDropData*>(lpParameter);
498 CComPtr<IDataObject> pDataObject;
499 HRESULT hr = CoGetInterfaceAndReleaseStream (data->pStream, IID_PPV_ARG(IDataObject, &pDataObject));
500
501 if (SUCCEEDED(hr))
502 {
503 CComPtr<IAsyncOperation> pAsyncOperation;
504 hr = data->This->_DoDrop(pDataObject, data->dwKeyState, data->pt, &data->pdwEffect);
505 if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation))))
506 {
507 pAsyncOperation->EndOperation(hr, NULL, data->pdwEffect);
508 }
509 }
510 //Release the CFSFolder and data object holds in the copying thread.
511 data->This->Release();
512 //Release the parameter from the heap.
513 HeapFree(GetProcessHeap(), 0, data);
514 CoUninitialize();
515 return 0;
516 }
517
518 HRESULT CFSDropTarget_CreateInstance(LPWSTR sPathTarget, REFIID riid, LPVOID * ppvOut)
519 {
520 return ShellObjectCreatorInit<CFSDropTarget>(sPathTarget, riid, ppvOut);
521 }