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