[SHELL32] Fix for BuildPathsList, this solves some problems showing while copying...
[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 WCHAR *
34 BuildPathsList(LPCWSTR wszBasePath, int cidl, LPCITEMIDLIST *pidls, BOOL bRelative)
35 {
36 WCHAR *pwszPathsList;
37 WCHAR *pwszListPos;
38 int iPathLen, i;
39
40 iPathLen = wcslen(wszBasePath);
41 pwszPathsList = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR) * cidl + 1);
42 pwszListPos = pwszPathsList;
43
44 for (i = 0; i < cidl; i++)
45 {
46 if (!_ILIsFolder(pidls[i]) && !_ILIsValue(pidls[i]))
47 continue;
48
49 wcscpy(pwszListPos, wszBasePath);
50 pwszListPos += iPathLen;
51
52 if (_ILIsFolder(pidls[i]) && bRelative)
53 continue;
54
55 /* FIXME: abort if path too long */
56 _ILSimpleGetTextW(pidls[i], pwszListPos, MAX_PATH - iPathLen);
57 pwszListPos += wcslen(pwszListPos) + 1;
58 }
59 *pwszListPos = 0;
60 return pwszPathsList;
61 }
62
63 /****************************************************************************
64 * CFSDropTarget::CopyItems
65 *
66 * copies items to this folder
67 */
68 HRESULT WINAPI CFSDropTarget::CopyItems(IShellFolder * pSFFrom, UINT cidl,
69 LPCITEMIDLIST * apidl, BOOL bCopy)
70 {
71 CComPtr<IPersistFolder2> ppf2 = NULL;
72 WCHAR szSrcPath[MAX_PATH];
73 WCHAR szTargetPath[MAX_PATH];
74 SHFILEOPSTRUCTW op;
75 LPITEMIDLIST pidl;
76 LPWSTR pszSrc, pszTarget, pszSrcList, pszTargetList, pszFileName;
77 int res, length;
78 HRESULT hr;
79
80 TRACE ("(%p)->(%p,%u,%p)\n", this, pSFFrom, cidl, apidl);
81
82 hr = pSFFrom->QueryInterface (IID_PPV_ARG(IPersistFolder2, &ppf2));
83 if (SUCCEEDED(hr))
84 {
85 hr = ppf2->GetCurFolder(&pidl);
86 if (FAILED(hr))
87 {
88 return hr;
89 }
90
91 hr = SHGetPathFromIDListW(pidl, szSrcPath);
92 SHFree(pidl);
93
94 if (FAILED(hr))
95 return hr;
96
97 pszSrc = PathAddBackslashW(szSrcPath);
98
99 wcscpy(szTargetPath, sPathTarget);
100 pszTarget = PathAddBackslashW(szTargetPath);
101
102 pszSrcList = BuildPathsList(szSrcPath, cidl, apidl, FALSE);
103 pszTargetList = BuildPathsList(szTargetPath, cidl, apidl, TRUE);
104
105 if (!pszSrcList || !pszTargetList)
106 {
107 if (pszSrcList)
108 HeapFree(GetProcessHeap(), 0, pszSrcList);
109
110 if (pszTargetList)
111 HeapFree(GetProcessHeap(), 0, pszTargetList);
112
113 SHFree(pidl);
114 return E_OUTOFMEMORY;
115 }
116
117 ZeroMemory(&op, sizeof(op));
118 if (!pszSrcList[0])
119 {
120 /* remove trailing backslash */
121 pszSrc--;
122 pszSrc[0] = L'\0';
123 op.pFrom = szSrcPath;
124 }
125 else
126 {
127 op.pFrom = pszSrcList;
128 }
129
130 if (!pszTargetList[0])
131 {
132 /* remove trailing backslash */
133 if (pszTarget - szTargetPath > 3)
134 {
135 pszTarget--;
136 pszTarget[0] = L'\0';
137 }
138 else
139 {
140 pszTarget[1] = L'\0';
141 }
142
143 op.pTo = szTargetPath;
144 op.fFlags = 0;
145 }
146 else
147 {
148 op.pTo = pszTargetList;
149 op.fFlags = FOF_MULTIDESTFILES;
150 }
151 op.hwnd = GetActiveWindow();
152 op.wFunc = bCopy ? FO_COPY : FO_MOVE;
153 op.fFlags |= FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR;
154
155 res = SHFileOperationW(&op);
156
157 if (res == DE_SAMEFILE)
158 {
159 length = wcslen(szTargetPath);
160
161 pszFileName = wcsrchr(pszSrcList, '\\');
162 pszFileName++;
163
164 if (LoadStringW(shell32_hInstance, IDS_COPY_OF, pszTarget, MAX_PATH - length))
165 {
166 wcscat(szTargetPath, L" ");
167 }
168
169 wcscat(szTargetPath, pszFileName);
170 op.pTo = szTargetPath;
171
172 res = SHFileOperationW(&op);
173 }
174
175 HeapFree(GetProcessHeap(), 0, pszSrcList);
176 HeapFree(GetProcessHeap(), 0, pszTargetList);
177
178 if (res)
179 return E_FAIL;
180 else
181 return S_OK;
182 }
183 return E_FAIL;
184 }
185
186 CFSDropTarget::CFSDropTarget():
187 cfShellIDList(0),
188 fAcceptFmt(FALSE),
189 sPathTarget(NULL)
190 {
191 }
192
193 HRESULT WINAPI CFSDropTarget::Initialize(LPWSTR PathTarget)
194 {
195 cfShellIDList = RegisterClipboardFormatW(CFSTR_SHELLIDLIST);
196 if (!cfShellIDList)
197 return E_FAIL;
198
199 sPathTarget = (WCHAR *)SHAlloc((wcslen(PathTarget) + 1) * sizeof(WCHAR));
200 if (!sPathTarget)
201 return E_OUTOFMEMORY;
202 wcscpy(sPathTarget, PathTarget);
203
204 return S_OK;
205 }
206
207 CFSDropTarget::~CFSDropTarget()
208 {
209 SHFree(sPathTarget);
210 }
211
212 BOOL
213 CFSDropTarget::GetUniqueFileName(LPWSTR pwszBasePath, LPCWSTR pwszExt, LPWSTR pwszTarget, BOOL bShortcut)
214 {
215 WCHAR wszLink[40];
216
217 if (!bShortcut)
218 {
219 if (!LoadStringW(shell32_hInstance, IDS_LNK_FILE, wszLink, _countof(wszLink)))
220 wszLink[0] = L'\0';
221 }
222
223 if (!bShortcut)
224 swprintf(pwszTarget, L"%s%s%s", wszLink, pwszBasePath, pwszExt);
225 else
226 swprintf(pwszTarget, L"%s%s", pwszBasePath, pwszExt);
227
228 for (UINT i = 2; PathFileExistsW(pwszTarget); ++i)
229 {
230 if (!bShortcut)
231 swprintf(pwszTarget, L"%s%s (%u)%s", wszLink, pwszBasePath, i, pwszExt);
232 else
233 swprintf(pwszTarget, L"%s (%u)%s", pwszBasePath, i, pwszExt);
234 }
235
236 return TRUE;
237 }
238
239 /****************************************************************************
240 * IDropTarget implementation
241 */
242 BOOL CFSDropTarget::QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect)
243 {
244 /* TODO Windows does different drop effects if dragging across drives.
245 i.e., it will copy instead of move if the directories are on different disks. */
246
247 DWORD dwEffect = DROPEFFECT_MOVE;
248
249 *pdwEffect = DROPEFFECT_NONE;
250
251 if (fAcceptFmt) { /* Does our interpretation of the keystate ... */
252 *pdwEffect = KeyStateToDropEffect (dwKeyState);
253
254 if (*pdwEffect == DROPEFFECT_NONE)
255 *pdwEffect = dwEffect;
256
257 /* ... matches the desired effect ? */
258 if (dwEffect & *pdwEffect) {
259 return TRUE;
260 }
261 }
262 return FALSE;
263 }
264
265 HRESULT WINAPI CFSDropTarget::DragEnter(IDataObject *pDataObject,
266 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
267 {
268 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject);
269 FORMATETC fmt;
270 FORMATETC fmt2;
271 fAcceptFmt = FALSE;
272
273 InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL);
274 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
275
276 if (SUCCEEDED(pDataObject->QueryGetData(&fmt)))
277 fAcceptFmt = TRUE;
278 else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2)))
279 fAcceptFmt = TRUE;
280
281 QueryDrop(dwKeyState, pdwEffect);
282 return S_OK;
283 }
284
285 HRESULT WINAPI CFSDropTarget::DragOver(DWORD dwKeyState, POINTL pt,
286 DWORD *pdwEffect)
287 {
288 TRACE("(%p)\n", this);
289
290 if (!pdwEffect)
291 return E_INVALIDARG;
292
293 QueryDrop(dwKeyState, pdwEffect);
294
295 return S_OK;
296 }
297
298 HRESULT WINAPI CFSDropTarget::DragLeave()
299 {
300 TRACE("(%p)\n", this);
301
302 fAcceptFmt = FALSE;
303
304 return S_OK;
305 }
306
307 HRESULT WINAPI CFSDropTarget::Drop(IDataObject *pDataObject,
308 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
309 {
310 TRACE("(%p) object dropped, effect %u\n", this, *pdwEffect);
311
312 if (!pdwEffect)
313 return E_INVALIDARG;
314
315 QueryDrop(dwKeyState, pdwEffect);
316
317 BOOL fIsOpAsync = FALSE;
318 CComPtr<IAsyncOperation> pAsyncOperation;
319
320 if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation))))
321 {
322 if (SUCCEEDED(pAsyncOperation->GetAsyncMode(&fIsOpAsync)) && fIsOpAsync)
323 {
324 _DoDropData *data = static_cast<_DoDropData*>(HeapAlloc(GetProcessHeap(), 0, sizeof(_DoDropData)));
325 data->This = this;
326 // Need to maintain this class in case the window is closed or the class exists temporarily (when dropping onto a folder).
327 pDataObject->AddRef();
328 pAsyncOperation->StartOperation(NULL);
329 CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDataObject, &data->pStream);
330 this->AddRef();
331 data->dwKeyState = dwKeyState;
332 data->pt = pt;
333 // Need to dereference as pdweffect gets freed.
334 data->pdwEffect = *pdwEffect;
335 SHCreateThread(CFSDropTarget::_DoDropThreadProc, data, NULL, NULL);
336 return S_OK;
337 }
338 }
339 return this->_DoDrop(pDataObject, dwKeyState, pt, pdwEffect);
340 }
341
342 HRESULT WINAPI CFSDropTarget::_DoDrop(IDataObject *pDataObject,
343 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
344 {
345 TRACE("(%p) performing drop, effect %u\n", this, *pdwEffect);
346 FORMATETC fmt;
347 FORMATETC fmt2;
348 STGMEDIUM medium;
349
350 InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL);
351 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
352
353 HRESULT hr;
354 bool bCopy = TRUE;
355 bool bLinking = FALSE;
356
357 /* Figure out what drop operation we're doing */
358 if (pdwEffect)
359 {
360 TRACE("Current drop effect flag %i\n", *pdwEffect);
361 if ((*pdwEffect & DROPEFFECT_MOVE) == DROPEFFECT_MOVE)
362 bCopy = FALSE;
363 if ((*pdwEffect & DROPEFFECT_LINK) == DROPEFFECT_LINK)
364 bLinking = TRUE;
365 }
366
367 if (SUCCEEDED(pDataObject->QueryGetData(&fmt)))
368 {
369 hr = pDataObject->GetData(&fmt, &medium);
370 TRACE("CFSTR_SHELLIDLIST.\n");
371
372 /* lock the handle */
373 LPIDA lpcida = (LPIDA)GlobalLock(medium.hGlobal);
374 if (!lpcida)
375 {
376 ReleaseStgMedium(&medium);
377 return E_FAIL;
378 }
379
380 /* convert the data into pidl */
381 LPITEMIDLIST pidl;
382 LPITEMIDLIST *apidl = _ILCopyCidaToaPidl(&pidl, lpcida);
383 if (!apidl)
384 {
385 ReleaseStgMedium(&medium);
386 return E_FAIL;
387 }
388
389 CComPtr<IShellFolder> psfDesktop;
390 CComPtr<IShellFolder> psfFrom = NULL;
391
392 /* Grab the desktop shell folder */
393 hr = SHGetDesktopFolder(&psfDesktop);
394 if (FAILED(hr))
395 {
396 ERR("SHGetDesktopFolder failed\n");
397 SHFree(pidl);
398 _ILFreeaPidl(apidl, lpcida->cidl);
399 ReleaseStgMedium(&medium);
400 return E_FAIL;
401 }
402
403 /* Find source folder, this is where the clipboard data was copied from */
404 if (_ILIsDesktop(pidl))
405 {
406 /* use desktop shell folder */
407 psfFrom = psfDesktop;
408 }
409 else
410 {
411 hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfFrom));
412 if (FAILED(hr))
413 {
414 ERR("no IShellFolder\n");
415 SHFree(pidl);
416 _ILFreeaPidl(apidl, lpcida->cidl);
417 ReleaseStgMedium(&medium);
418 return E_FAIL;
419 }
420 }
421
422 if (bLinking)
423 {
424 WCHAR wszTargetPath[MAX_PATH];
425 WCHAR wszPath[MAX_PATH];
426 WCHAR wszTarget[MAX_PATH];
427
428 wcscpy(wszTargetPath, sPathTarget);
429
430 TRACE("target path = %s", debugstr_w(wszTargetPath));
431
432 /* We need to create a link for each pidl in the copied items, so step through the pidls from the clipboard */
433 for (UINT i = 0; i < lpcida->cidl; i++)
434 {
435 //Find out which file we're copying
436 STRRET strFile;
437 hr = psfFrom->GetDisplayNameOf(apidl[i], SHGDN_FORPARSING, &strFile);
438 if (FAILED(hr))
439 {
440 ERR("Error source obtaining path");
441 break;
442 }
443
444 hr = StrRetToBufW(&strFile, apidl[i], wszPath, _countof(wszPath));
445 if (FAILED(hr))
446 {
447 ERR("Error putting source path into buffer");
448 break;
449 }
450 TRACE("source path = %s", debugstr_w(wszPath));
451
452 // Creating a buffer to hold the combined path
453 WCHAR buffer_1[MAX_PATH] = L"";
454 WCHAR *lpStr1;
455 lpStr1 = buffer_1;
456
457 LPWSTR pwszFileName = PathFindFileNameW(wszPath);
458 LPWSTR pwszExt = PathFindExtensionW(wszPath);
459 LPWSTR placementPath = PathCombineW(lpStr1, sPathTarget, pwszFileName);
460 CComPtr<IPersistFile> ppf;
461
462 //Check to see if it's already a link.
463 if (!wcsicmp(pwszExt, L".lnk"))
464 {
465 //It's a link so, we create a new one which copies the old.
466 if(!GetUniqueFileName(placementPath, pwszExt, wszTarget, TRUE))
467 {
468 ERR("Error getting unique file name");
469 hr = E_FAIL;
470 break;
471 }
472 hr = IShellLink_ConstructFromPath(wszPath, IID_PPV_ARG(IPersistFile, &ppf));
473 if (FAILED(hr)) {
474 ERR("Error constructing link from file");
475 break;
476 }
477
478 hr = ppf->Save(wszTarget, FALSE);
479 if (FAILED(hr))
480 break;
481 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL);
482 }
483 else
484 {
485 //It's not a link, so build a new link using the creator class and fill it in.
486 //Create a file name for the link
487 if (!GetUniqueFileName(placementPath, L".lnk", wszTarget, TRUE))
488 {
489 ERR("Error creating unique file name");
490 hr = E_FAIL;
491 break;
492 }
493
494 CComPtr<IShellLinkW> pLink;
495 hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IShellLinkW, &pLink));
496 if (FAILED(hr)) {
497 ERR("Error instantiating IShellLinkW");
498 break;
499 }
500
501 WCHAR szDirPath[MAX_PATH], *pwszFile;
502 GetFullPathName(wszPath, MAX_PATH, szDirPath, &pwszFile);
503 if (pwszFile) pwszFile[0] = 0;
504
505 hr = pLink->SetPath(wszPath);
506 if(FAILED(hr))
507 break;
508
509 hr = pLink->SetWorkingDirectory(szDirPath);
510 if(FAILED(hr))
511 break;
512
513 hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
514 if(FAILED(hr))
515 break;
516
517 hr = ppf->Save(wszTarget, TRUE);
518 if (FAILED(hr))
519 break;
520 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL);
521 }
522 }
523 }
524 else
525 {
526 hr = this->CopyItems(psfFrom, lpcida->cidl, (LPCITEMIDLIST*)apidl, bCopy);
527 }
528
529 SHFree(pidl);
530 _ILFreeaPidl(apidl, lpcida->cidl);
531 ReleaseStgMedium(&medium);
532 }
533 else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2)))
534 {
535 FORMATETC fmt2;
536 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
537 if (SUCCEEDED(pDataObject->GetData(&fmt2, &medium)) /* && SUCCEEDED(pDataObject->GetData(&fmt2, &medium))*/)
538 {
539 WCHAR wszTargetPath[MAX_PATH + 1];
540 LPWSTR pszSrcList;
541
542 wcscpy(wszTargetPath, sPathTarget);
543 //Double NULL terminate.
544 wszTargetPath[wcslen(wszTargetPath) + 1] = '\0';
545
546 LPDROPFILES lpdf = (LPDROPFILES) GlobalLock(medium.hGlobal);
547 if (!lpdf)
548 {
549 ERR("Error locking global\n");
550 return E_FAIL;
551 }
552 pszSrcList = (LPWSTR) (((byte*) lpdf) + lpdf->pFiles);
553 TRACE("Source file (just the first) = %s\n", debugstr_w(pszSrcList));
554 TRACE("Target path = %s\n", debugstr_w(wszTargetPath));
555
556 SHFILEOPSTRUCTW op;
557 ZeroMemory(&op, sizeof(op));
558 op.pFrom = pszSrcList;
559 op.pTo = wszTargetPath;
560 op.hwnd = GetActiveWindow();
561 op.wFunc = bCopy ? FO_COPY : FO_MOVE;
562 op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR;
563 hr = SHFileOperationW(&op);
564 return hr;
565 }
566 ERR("Error calling GetData\n");
567 hr = E_FAIL;
568 }
569 else
570 {
571 ERR("No viable drop format.\n");
572 hr = E_FAIL;
573 }
574 return hr;
575 }
576
577 DWORD WINAPI CFSDropTarget::_DoDropThreadProc(LPVOID lpParameter)
578 {
579 CoInitialize(NULL);
580 _DoDropData *data = static_cast<_DoDropData*>(lpParameter);
581 CComPtr<IDataObject> pDataObject;
582 HRESULT hr = CoGetInterfaceAndReleaseStream (data->pStream, IID_PPV_ARG(IDataObject, &pDataObject));
583
584 if (SUCCEEDED(hr))
585 {
586 CComPtr<IAsyncOperation> pAsyncOperation;
587 hr = data->This->_DoDrop(pDataObject, data->dwKeyState, data->pt, &data->pdwEffect);
588 if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation))))
589 {
590 pAsyncOperation->EndOperation(hr, NULL, data->pdwEffect);
591 }
592 }
593 //Release the CFSFolder and data object holds in the copying thread.
594 data->This->Release();
595 //Release the parameter from the heap.
596 HeapFree(GetProcessHeap(), 0, data);
597 CoUninitialize();
598 return 0;
599 }
600
601 HRESULT CFSDropTarget_CreateInstance(LPWSTR sPathTarget, REFIID riid, LPVOID * ppvOut)
602 {
603 return ShellObjectCreatorInit<CFSDropTarget>(sPathTarget, riid, ppvOut);
604 }