e9e93e21b69bb5a1370b3e32a20c47d5341cf4c0
[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 if (!PathTarget)
196 return E_UNEXPECTED;
197
198 cfShellIDList = RegisterClipboardFormatW(CFSTR_SHELLIDLIST);
199 if (!cfShellIDList)
200 return E_FAIL;
201
202 sPathTarget = (WCHAR *)SHAlloc((wcslen(PathTarget) + 1) * sizeof(WCHAR));
203 if (!sPathTarget)
204 return E_OUTOFMEMORY;
205
206 wcscpy(sPathTarget, PathTarget);
207
208 return S_OK;
209 }
210
211 CFSDropTarget::~CFSDropTarget()
212 {
213 SHFree(sPathTarget);
214 }
215
216 BOOL
217 CFSDropTarget::GetUniqueFileName(LPWSTR pwszBasePath, LPCWSTR pwszExt, LPWSTR pwszTarget, BOOL bShortcut)
218 {
219 WCHAR wszLink[40];
220
221 if (!bShortcut)
222 {
223 if (!LoadStringW(shell32_hInstance, IDS_LNK_FILE, wszLink, _countof(wszLink)))
224 wszLink[0] = L'\0';
225 }
226
227 if (!bShortcut)
228 swprintf(pwszTarget, L"%s%s%s", wszLink, pwszBasePath, pwszExt);
229 else
230 swprintf(pwszTarget, L"%s%s", pwszBasePath, pwszExt);
231
232 for (UINT i = 2; PathFileExistsW(pwszTarget); ++i)
233 {
234 if (!bShortcut)
235 swprintf(pwszTarget, L"%s%s (%u)%s", wszLink, pwszBasePath, i, pwszExt);
236 else
237 swprintf(pwszTarget, L"%s (%u)%s", pwszBasePath, i, pwszExt);
238 }
239
240 return TRUE;
241 }
242
243 /****************************************************************************
244 * IDropTarget implementation
245 */
246 BOOL CFSDropTarget::QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect)
247 {
248 /* TODO Windows does different drop effects if dragging across drives.
249 i.e., it will copy instead of move if the directories are on different disks. */
250
251 DWORD dwEffect = DROPEFFECT_MOVE;
252
253 *pdwEffect = DROPEFFECT_NONE;
254
255 if (fAcceptFmt) { /* Does our interpretation of the keystate ... */
256 *pdwEffect = KeyStateToDropEffect (dwKeyState);
257
258 if (*pdwEffect == DROPEFFECT_NONE)
259 *pdwEffect = dwEffect;
260
261 /* ... matches the desired effect ? */
262 if (dwEffect & *pdwEffect) {
263 return TRUE;
264 }
265 }
266 return FALSE;
267 }
268
269 HRESULT WINAPI CFSDropTarget::DragEnter(IDataObject *pDataObject,
270 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
271 {
272 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject);
273 FORMATETC fmt;
274 FORMATETC fmt2;
275 fAcceptFmt = FALSE;
276
277 InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL);
278 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
279
280 if (SUCCEEDED(pDataObject->QueryGetData(&fmt)))
281 fAcceptFmt = TRUE;
282 else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2)))
283 fAcceptFmt = TRUE;
284
285 QueryDrop(dwKeyState, pdwEffect);
286 return S_OK;
287 }
288
289 HRESULT WINAPI CFSDropTarget::DragOver(DWORD dwKeyState, POINTL pt,
290 DWORD *pdwEffect)
291 {
292 TRACE("(%p)\n", this);
293
294 if (!pdwEffect)
295 return E_INVALIDARG;
296
297 QueryDrop(dwKeyState, pdwEffect);
298
299 return S_OK;
300 }
301
302 HRESULT WINAPI CFSDropTarget::DragLeave()
303 {
304 TRACE("(%p)\n", this);
305
306 fAcceptFmt = FALSE;
307
308 return S_OK;
309 }
310
311 HRESULT WINAPI CFSDropTarget::Drop(IDataObject *pDataObject,
312 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
313 {
314 TRACE("(%p) object dropped, effect %u\n", this, *pdwEffect);
315
316 if (!pdwEffect)
317 return E_INVALIDARG;
318
319 QueryDrop(dwKeyState, pdwEffect);
320
321 BOOL fIsOpAsync = FALSE;
322 CComPtr<IAsyncOperation> pAsyncOperation;
323
324 if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation))))
325 {
326 if (SUCCEEDED(pAsyncOperation->GetAsyncMode(&fIsOpAsync)) && fIsOpAsync)
327 {
328 _DoDropData *data = static_cast<_DoDropData*>(HeapAlloc(GetProcessHeap(), 0, sizeof(_DoDropData)));
329 data->This = this;
330 // Need to maintain this class in case the window is closed or the class exists temporarily (when dropping onto a folder).
331 pDataObject->AddRef();
332 pAsyncOperation->StartOperation(NULL);
333 CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDataObject, &data->pStream);
334 this->AddRef();
335 data->dwKeyState = dwKeyState;
336 data->pt = pt;
337 // Need to dereference as pdweffect gets freed.
338 data->pdwEffect = *pdwEffect;
339 SHCreateThread(CFSDropTarget::_DoDropThreadProc, data, NULL, NULL);
340 return S_OK;
341 }
342 }
343 return this->_DoDrop(pDataObject, dwKeyState, pt, pdwEffect);
344 }
345
346 HRESULT WINAPI CFSDropTarget::_DoDrop(IDataObject *pDataObject,
347 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
348 {
349 TRACE("(%p) performing drop, effect %u\n", this, *pdwEffect);
350 FORMATETC fmt;
351 FORMATETC fmt2;
352 STGMEDIUM medium;
353
354 InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL);
355 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
356
357 HRESULT hr;
358 bool bCopy = TRUE;
359 bool bLinking = FALSE;
360
361 /* Figure out what drop operation we're doing */
362 if (pdwEffect)
363 {
364 TRACE("Current drop effect flag %i\n", *pdwEffect);
365 if ((*pdwEffect & DROPEFFECT_MOVE) == DROPEFFECT_MOVE)
366 bCopy = FALSE;
367 if ((*pdwEffect & DROPEFFECT_LINK) == DROPEFFECT_LINK)
368 bLinking = TRUE;
369 }
370
371 if (SUCCEEDED(pDataObject->QueryGetData(&fmt)))
372 {
373 hr = pDataObject->GetData(&fmt, &medium);
374 TRACE("CFSTR_SHELLIDLIST.\n");
375
376 /* lock the handle */
377 LPIDA lpcida = (LPIDA)GlobalLock(medium.hGlobal);
378 if (!lpcida)
379 {
380 ReleaseStgMedium(&medium);
381 return E_FAIL;
382 }
383
384 /* convert the data into pidl */
385 LPITEMIDLIST pidl;
386 LPITEMIDLIST *apidl = _ILCopyCidaToaPidl(&pidl, lpcida);
387 if (!apidl)
388 {
389 ReleaseStgMedium(&medium);
390 return E_FAIL;
391 }
392
393 CComPtr<IShellFolder> psfDesktop;
394 CComPtr<IShellFolder> psfFrom = NULL;
395
396 /* Grab the desktop shell folder */
397 hr = SHGetDesktopFolder(&psfDesktop);
398 if (FAILED(hr))
399 {
400 ERR("SHGetDesktopFolder failed\n");
401 SHFree(pidl);
402 _ILFreeaPidl(apidl, lpcida->cidl);
403 ReleaseStgMedium(&medium);
404 return E_FAIL;
405 }
406
407 /* Find source folder, this is where the clipboard data was copied from */
408 if (_ILIsDesktop(pidl))
409 {
410 /* use desktop shell folder */
411 psfFrom = psfDesktop;
412 }
413 else
414 {
415 hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfFrom));
416 if (FAILED(hr))
417 {
418 ERR("no IShellFolder\n");
419 SHFree(pidl);
420 _ILFreeaPidl(apidl, lpcida->cidl);
421 ReleaseStgMedium(&medium);
422 return E_FAIL;
423 }
424 }
425
426 if (bLinking)
427 {
428 WCHAR wszTargetPath[MAX_PATH];
429 WCHAR wszPath[MAX_PATH];
430 WCHAR wszTarget[MAX_PATH];
431
432 wcscpy(wszTargetPath, sPathTarget);
433
434 TRACE("target path = %s", debugstr_w(wszTargetPath));
435
436 /* We need to create a link for each pidl in the copied items, so step through the pidls from the clipboard */
437 for (UINT i = 0; i < lpcida->cidl; i++)
438 {
439 //Find out which file we're copying
440 STRRET strFile;
441 hr = psfFrom->GetDisplayNameOf(apidl[i], SHGDN_FORPARSING, &strFile);
442 if (FAILED(hr))
443 {
444 ERR("Error source obtaining path");
445 break;
446 }
447
448 hr = StrRetToBufW(&strFile, apidl[i], wszPath, _countof(wszPath));
449 if (FAILED(hr))
450 {
451 ERR("Error putting source path into buffer");
452 break;
453 }
454 TRACE("source path = %s", debugstr_w(wszPath));
455
456 // Creating a buffer to hold the combined path
457 WCHAR buffer_1[MAX_PATH] = L"";
458 WCHAR *lpStr1;
459 lpStr1 = buffer_1;
460
461 LPWSTR pwszFileName = PathFindFileNameW(wszPath);
462 LPWSTR pwszExt = PathFindExtensionW(wszPath);
463 LPWSTR placementPath = PathCombineW(lpStr1, sPathTarget, pwszFileName);
464 CComPtr<IPersistFile> ppf;
465
466 //Check to see if it's already a link.
467 if (!wcsicmp(pwszExt, L".lnk"))
468 {
469 //It's a link so, we create a new one which copies the old.
470 if(!GetUniqueFileName(placementPath, pwszExt, wszTarget, TRUE))
471 {
472 ERR("Error getting unique file name");
473 hr = E_FAIL;
474 break;
475 }
476 hr = IShellLink_ConstructFromPath(wszPath, IID_PPV_ARG(IPersistFile, &ppf));
477 if (FAILED(hr)) {
478 ERR("Error constructing link from file");
479 break;
480 }
481
482 hr = ppf->Save(wszTarget, FALSE);
483 if (FAILED(hr))
484 break;
485 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL);
486 }
487 else
488 {
489 //It's not a link, so build a new link using the creator class and fill it in.
490 //Create a file name for the link
491 if (!GetUniqueFileName(placementPath, L".lnk", wszTarget, TRUE))
492 {
493 ERR("Error creating unique file name");
494 hr = E_FAIL;
495 break;
496 }
497
498 CComPtr<IShellLinkW> pLink;
499 hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IShellLinkW, &pLink));
500 if (FAILED(hr)) {
501 ERR("Error instantiating IShellLinkW");
502 break;
503 }
504
505 WCHAR szDirPath[MAX_PATH], *pwszFile;
506 GetFullPathName(wszPath, MAX_PATH, szDirPath, &pwszFile);
507 if (pwszFile) pwszFile[0] = 0;
508
509 hr = pLink->SetPath(wszPath);
510 if(FAILED(hr))
511 break;
512
513 hr = pLink->SetWorkingDirectory(szDirPath);
514 if(FAILED(hr))
515 break;
516
517 hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
518 if(FAILED(hr))
519 break;
520
521 hr = ppf->Save(wszTarget, TRUE);
522 if (FAILED(hr))
523 break;
524 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL);
525 }
526 }
527 }
528 else
529 {
530 hr = this->CopyItems(psfFrom, lpcida->cidl, (LPCITEMIDLIST*)apidl, bCopy);
531 }
532
533 SHFree(pidl);
534 _ILFreeaPidl(apidl, lpcida->cidl);
535 ReleaseStgMedium(&medium);
536 }
537 else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2)))
538 {
539 FORMATETC fmt2;
540 InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL);
541 if (SUCCEEDED(pDataObject->GetData(&fmt2, &medium)) /* && SUCCEEDED(pDataObject->GetData(&fmt2, &medium))*/)
542 {
543 WCHAR wszTargetPath[MAX_PATH + 1];
544 LPWSTR pszSrcList;
545
546 wcscpy(wszTargetPath, sPathTarget);
547 //Double NULL terminate.
548 wszTargetPath[wcslen(wszTargetPath) + 1] = '\0';
549
550 LPDROPFILES lpdf = (LPDROPFILES) GlobalLock(medium.hGlobal);
551 if (!lpdf)
552 {
553 ERR("Error locking global\n");
554 return E_FAIL;
555 }
556 pszSrcList = (LPWSTR) (((byte*) lpdf) + lpdf->pFiles);
557 TRACE("Source file (just the first) = %s\n", debugstr_w(pszSrcList));
558 TRACE("Target path = %s\n", debugstr_w(wszTargetPath));
559
560 SHFILEOPSTRUCTW op;
561 ZeroMemory(&op, sizeof(op));
562 op.pFrom = pszSrcList;
563 op.pTo = wszTargetPath;
564 op.hwnd = GetActiveWindow();
565 op.wFunc = bCopy ? FO_COPY : FO_MOVE;
566 op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR;
567 hr = SHFileOperationW(&op);
568 return hr;
569 }
570 ERR("Error calling GetData\n");
571 hr = E_FAIL;
572 }
573 else
574 {
575 ERR("No viable drop format.\n");
576 hr = E_FAIL;
577 }
578 return hr;
579 }
580
581 DWORD WINAPI CFSDropTarget::_DoDropThreadProc(LPVOID lpParameter)
582 {
583 CoInitialize(NULL);
584 _DoDropData *data = static_cast<_DoDropData*>(lpParameter);
585 CComPtr<IDataObject> pDataObject;
586 HRESULT hr = CoGetInterfaceAndReleaseStream (data->pStream, IID_PPV_ARG(IDataObject, &pDataObject));
587
588 if (SUCCEEDED(hr))
589 {
590 CComPtr<IAsyncOperation> pAsyncOperation;
591 hr = data->This->_DoDrop(pDataObject, data->dwKeyState, data->pt, &data->pdwEffect);
592 if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation))))
593 {
594 pAsyncOperation->EndOperation(hr, NULL, data->pdwEffect);
595 }
596 }
597 //Release the CFSFolder and data object holds in the copying thread.
598 data->This->Release();
599 //Release the parameter from the heap.
600 HeapFree(GetProcessHeap(), 0, data);
601 CoUninitialize();
602 return 0;
603 }
604
605 HRESULT CFSDropTarget_CreateInstance(LPWSTR sPathTarget, REFIID riid, LPVOID * ppvOut)
606 {
607 return ShellObjectCreatorInit<CFSDropTarget>(sPathTarget, riid, ppvOut);
608 }