3 * file system folder drop target
5 * Copyright 1997 Marcus Meissner
6 * Copyright 1998, 1999, 2002 Juergen Schmied
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.
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.
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
25 WINE_DEFAULT_DEBUG_CHANNEL (shell
);
27 /****************************************************************************
30 * Builds a list of paths like the one used in SHFileOperation from a table of
31 * PIDLs relative to the given base folder
34 BuildPathsList(LPCWSTR wszBasePath
, int cidl
, LPCITEMIDLIST
*pidls
, BOOL bRelative
)
40 iPathLen
= wcslen(wszBasePath
);
41 pwszPathsList
= (WCHAR
*)HeapAlloc(GetProcessHeap(), 0, MAX_PATH
* sizeof(WCHAR
) * cidl
+ 1);
42 pwszListPos
= pwszPathsList
;
44 for (i
= 0; i
< cidl
; i
++)
46 if (!_ILIsFolder(pidls
[i
]) && !_ILIsValue(pidls
[i
]))
49 wcscpy(pwszListPos
, wszBasePath
);
50 pwszListPos
+= iPathLen
;
52 if (_ILIsFolder(pidls
[i
]) && bRelative
)
55 /* FIXME: abort if path too long */
56 _ILSimpleGetTextW(pidls
[i
], pwszListPos
, MAX_PATH
- iPathLen
);
57 pwszListPos
+= wcslen(pwszListPos
) + 1;
63 /****************************************************************************
64 * CFSDropTarget::CopyItems
66 * copies items to this folder
68 HRESULT WINAPI
CFSDropTarget::CopyItems(IShellFolder
* pSFFrom
, UINT cidl
,
69 LPCITEMIDLIST
* apidl
, BOOL bCopy
)
71 CComPtr
<IPersistFolder2
> ppf2
= NULL
;
72 WCHAR szSrcPath
[MAX_PATH
];
73 WCHAR szTargetPath
[MAX_PATH
];
76 LPWSTR pszSrc
, pszTarget
, pszSrcList
, pszTargetList
, pszFileName
;
80 TRACE ("(%p)->(%p,%u,%p)\n", this, pSFFrom
, cidl
, apidl
);
82 hr
= pSFFrom
->QueryInterface (IID_PPV_ARG(IPersistFolder2
, &ppf2
));
85 hr
= ppf2
->GetCurFolder(&pidl
);
91 hr
= SHGetPathFromIDListW(pidl
, szSrcPath
);
97 pszSrc
= PathAddBackslashW(szSrcPath
);
99 wcscpy(szTargetPath
, sPathTarget
);
100 pszTarget
= PathAddBackslashW(szTargetPath
);
102 pszSrcList
= BuildPathsList(szSrcPath
, cidl
, apidl
, FALSE
);
103 pszTargetList
= BuildPathsList(szTargetPath
, cidl
, apidl
, TRUE
);
105 if (!pszSrcList
|| !pszTargetList
)
108 HeapFree(GetProcessHeap(), 0, pszSrcList
);
111 HeapFree(GetProcessHeap(), 0, pszTargetList
);
114 return E_OUTOFMEMORY
;
117 ZeroMemory(&op
, sizeof(op
));
120 /* remove trailing backslash */
123 op
.pFrom
= szSrcPath
;
127 op
.pFrom
= pszSrcList
;
130 if (!pszTargetList
[0])
132 /* remove trailing backslash */
133 if (pszTarget
- szTargetPath
> 3)
136 pszTarget
[0] = L
'\0';
140 pszTarget
[1] = L
'\0';
143 op
.pTo
= szTargetPath
;
148 op
.pTo
= pszTargetList
;
149 op
.fFlags
= FOF_MULTIDESTFILES
;
151 op
.hwnd
= GetActiveWindow();
152 op
.wFunc
= bCopy
? FO_COPY
: FO_MOVE
;
153 op
.fFlags
|= FOF_ALLOWUNDO
| FOF_NOCONFIRMMKDIR
;
155 res
= SHFileOperationW(&op
);
157 if (res
== DE_SAMEFILE
)
159 length
= wcslen(szTargetPath
);
161 pszFileName
= wcsrchr(pszSrcList
, '\\');
164 if (LoadStringW(shell32_hInstance
, IDS_COPY_OF
, pszTarget
, MAX_PATH
- length
))
166 wcscat(szTargetPath
, L
" ");
169 wcscat(szTargetPath
, pszFileName
);
170 op
.pTo
= szTargetPath
;
172 res
= SHFileOperationW(&op
);
175 HeapFree(GetProcessHeap(), 0, pszSrcList
);
176 HeapFree(GetProcessHeap(), 0, pszTargetList
);
186 CFSDropTarget::CFSDropTarget():
193 HRESULT WINAPI
CFSDropTarget::Initialize(LPWSTR PathTarget
)
195 cfShellIDList
= RegisterClipboardFormatW(CFSTR_SHELLIDLIST
);
199 sPathTarget
= (WCHAR
*)SHAlloc((wcslen(PathTarget
) + 1) * sizeof(WCHAR
));
201 return E_OUTOFMEMORY
;
202 wcscpy(sPathTarget
, PathTarget
);
207 CFSDropTarget::~CFSDropTarget()
213 CFSDropTarget::GetUniqueFileName(LPWSTR pwszBasePath
, LPCWSTR pwszExt
, LPWSTR pwszTarget
, BOOL bShortcut
)
219 if (!LoadStringW(shell32_hInstance
, IDS_LNK_FILE
, wszLink
, _countof(wszLink
)))
224 swprintf(pwszTarget
, L
"%s%s%s", wszLink
, pwszBasePath
, pwszExt
);
226 swprintf(pwszTarget
, L
"%s%s", pwszBasePath
, pwszExt
);
228 for (UINT i
= 2; PathFileExistsW(pwszTarget
); ++i
)
231 swprintf(pwszTarget
, L
"%s%s (%u)%s", wszLink
, pwszBasePath
, i
, pwszExt
);
233 swprintf(pwszTarget
, L
"%s (%u)%s", pwszBasePath
, i
, pwszExt
);
239 /****************************************************************************
240 * IDropTarget implementation
242 BOOL
CFSDropTarget::QueryDrop(DWORD dwKeyState
, LPDWORD pdwEffect
)
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. */
247 DWORD dwEffect
= DROPEFFECT_MOVE
;
249 *pdwEffect
= DROPEFFECT_NONE
;
251 if (fAcceptFmt
) { /* Does our interpretation of the keystate ... */
252 *pdwEffect
= KeyStateToDropEffect (dwKeyState
);
254 if (*pdwEffect
== DROPEFFECT_NONE
)
255 *pdwEffect
= dwEffect
;
257 /* ... matches the desired effect ? */
258 if (dwEffect
& *pdwEffect
) {
265 HRESULT WINAPI
CFSDropTarget::DragEnter(IDataObject
*pDataObject
,
266 DWORD dwKeyState
, POINTL pt
, DWORD
*pdwEffect
)
268 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject
);
273 InitFormatEtc (fmt
, cfShellIDList
, TYMED_HGLOBAL
);
274 InitFormatEtc (fmt2
, CF_HDROP
, TYMED_HGLOBAL
);
276 if (SUCCEEDED(pDataObject
->QueryGetData(&fmt
)))
278 else if (SUCCEEDED(pDataObject
->QueryGetData(&fmt2
)))
281 QueryDrop(dwKeyState
, pdwEffect
);
285 HRESULT WINAPI
CFSDropTarget::DragOver(DWORD dwKeyState
, POINTL pt
,
288 TRACE("(%p)\n", this);
293 QueryDrop(dwKeyState
, pdwEffect
);
298 HRESULT WINAPI
CFSDropTarget::DragLeave()
300 TRACE("(%p)\n", this);
307 HRESULT WINAPI
CFSDropTarget::Drop(IDataObject
*pDataObject
,
308 DWORD dwKeyState
, POINTL pt
, DWORD
*pdwEffect
)
310 TRACE("(%p) object dropped, effect %u\n", this, *pdwEffect
);
315 QueryDrop(dwKeyState
, pdwEffect
);
317 BOOL fIsOpAsync
= FALSE
;
318 CComPtr
<IAsyncOperation
> pAsyncOperation
;
320 if (SUCCEEDED(pDataObject
->QueryInterface(IID_PPV_ARG(IAsyncOperation
, &pAsyncOperation
))))
322 if (SUCCEEDED(pAsyncOperation
->GetAsyncMode(&fIsOpAsync
)) && fIsOpAsync
)
324 _DoDropData
*data
= static_cast<_DoDropData
*>(HeapAlloc(GetProcessHeap(), 0, sizeof(_DoDropData
)));
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
);
331 data
->dwKeyState
= dwKeyState
;
333 // Need to dereference as pdweffect gets freed.
334 data
->pdwEffect
= *pdwEffect
;
335 SHCreateThread(CFSDropTarget::_DoDropThreadProc
, data
, NULL
, NULL
);
339 return this->_DoDrop(pDataObject
, dwKeyState
, pt
, pdwEffect
);
342 HRESULT WINAPI
CFSDropTarget::_DoDrop(IDataObject
*pDataObject
,
343 DWORD dwKeyState
, POINTL pt
, DWORD
*pdwEffect
)
345 TRACE("(%p) performing drop, effect %u\n", this, *pdwEffect
);
350 InitFormatEtc (fmt
, cfShellIDList
, TYMED_HGLOBAL
);
351 InitFormatEtc (fmt2
, CF_HDROP
, TYMED_HGLOBAL
);
355 bool bLinking
= FALSE
;
357 /* Figure out what drop operation we're doing */
360 TRACE("Current drop effect flag %i\n", *pdwEffect
);
361 if ((*pdwEffect
& DROPEFFECT_MOVE
) == DROPEFFECT_MOVE
)
363 if ((*pdwEffect
& DROPEFFECT_LINK
) == DROPEFFECT_LINK
)
367 if (SUCCEEDED(pDataObject
->QueryGetData(&fmt
)))
369 hr
= pDataObject
->GetData(&fmt
, &medium
);
370 TRACE("CFSTR_SHELLIDLIST.\n");
372 /* lock the handle */
373 LPIDA lpcida
= (LPIDA
)GlobalLock(medium
.hGlobal
);
376 ReleaseStgMedium(&medium
);
380 /* convert the data into pidl */
382 LPITEMIDLIST
*apidl
= _ILCopyCidaToaPidl(&pidl
, lpcida
);
385 ReleaseStgMedium(&medium
);
389 CComPtr
<IShellFolder
> psfDesktop
;
390 CComPtr
<IShellFolder
> psfFrom
= NULL
;
392 /* Grab the desktop shell folder */
393 hr
= SHGetDesktopFolder(&psfDesktop
);
396 ERR("SHGetDesktopFolder failed\n");
398 _ILFreeaPidl(apidl
, lpcida
->cidl
);
399 ReleaseStgMedium(&medium
);
403 /* Find source folder, this is where the clipboard data was copied from */
404 if (_ILIsDesktop(pidl
))
406 /* use desktop shell folder */
407 psfFrom
= psfDesktop
;
411 hr
= psfDesktop
->BindToObject(pidl
, NULL
, IID_PPV_ARG(IShellFolder
, &psfFrom
));
414 ERR("no IShellFolder\n");
416 _ILFreeaPidl(apidl
, lpcida
->cidl
);
417 ReleaseStgMedium(&medium
);
424 WCHAR wszTargetPath
[MAX_PATH
];
425 WCHAR wszPath
[MAX_PATH
];
426 WCHAR wszTarget
[MAX_PATH
];
428 wcscpy(wszTargetPath
, sPathTarget
);
430 TRACE("target path = %s", debugstr_w(wszTargetPath
));
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
++)
435 //Find out which file we're copying
437 hr
= psfFrom
->GetDisplayNameOf(apidl
[i
], SHGDN_FORPARSING
, &strFile
);
440 ERR("Error source obtaining path");
444 hr
= StrRetToBufW(&strFile
, apidl
[i
], wszPath
, _countof(wszPath
));
447 ERR("Error putting source path into buffer");
450 TRACE("source path = %s", debugstr_w(wszPath
));
452 // Creating a buffer to hold the combined path
453 WCHAR buffer_1
[MAX_PATH
] = L
"";
457 LPWSTR pwszFileName
= PathFindFileNameW(wszPath
);
458 LPWSTR pwszExt
= PathFindExtensionW(wszPath
);
459 LPWSTR placementPath
= PathCombineW(lpStr1
, sPathTarget
, pwszFileName
);
460 CComPtr
<IPersistFile
> ppf
;
462 //Check to see if it's already a link.
463 if (!wcsicmp(pwszExt
, L
".lnk"))
465 //It's a link so, we create a new one which copies the old.
466 if(!GetUniqueFileName(placementPath
, pwszExt
, wszTarget
, TRUE
))
468 ERR("Error getting unique file name");
472 hr
= IShellLink_ConstructFromPath(wszPath
, IID_PPV_ARG(IPersistFile
, &ppf
));
474 ERR("Error constructing link from file");
478 hr
= ppf
->Save(wszTarget
, FALSE
);
481 SHChangeNotify(SHCNE_CREATE
, SHCNF_PATHW
, wszTarget
, NULL
);
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
))
489 ERR("Error creating unique file name");
494 CComPtr
<IShellLinkW
> pLink
;
495 hr
= CShellLink::_CreatorClass::CreateInstance(NULL
, IID_PPV_ARG(IShellLinkW
, &pLink
));
497 ERR("Error instantiating IShellLinkW");
501 WCHAR szDirPath
[MAX_PATH
], *pwszFile
;
502 GetFullPathName(wszPath
, MAX_PATH
, szDirPath
, &pwszFile
);
503 if (pwszFile
) pwszFile
[0] = 0;
505 hr
= pLink
->SetPath(wszPath
);
509 hr
= pLink
->SetWorkingDirectory(szDirPath
);
513 hr
= pLink
->QueryInterface(IID_PPV_ARG(IPersistFile
, &ppf
));
517 hr
= ppf
->Save(wszTarget
, TRUE
);
520 SHChangeNotify(SHCNE_CREATE
, SHCNF_PATHW
, wszTarget
, NULL
);
526 hr
= this->CopyItems(psfFrom
, lpcida
->cidl
, (LPCITEMIDLIST
*)apidl
, bCopy
);
530 _ILFreeaPidl(apidl
, lpcida
->cidl
);
531 ReleaseStgMedium(&medium
);
533 else if (SUCCEEDED(pDataObject
->QueryGetData(&fmt2
)))
536 InitFormatEtc (fmt2
, CF_HDROP
, TYMED_HGLOBAL
);
537 if (SUCCEEDED(pDataObject
->GetData(&fmt2
, &medium
)) /* && SUCCEEDED(pDataObject->GetData(&fmt2, &medium))*/)
539 WCHAR wszTargetPath
[MAX_PATH
+ 1];
542 wcscpy(wszTargetPath
, sPathTarget
);
543 //Double NULL terminate.
544 wszTargetPath
[wcslen(wszTargetPath
) + 1] = '\0';
546 LPDROPFILES lpdf
= (LPDROPFILES
) GlobalLock(medium
.hGlobal
);
549 ERR("Error locking global\n");
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
));
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
);
566 ERR("Error calling GetData\n");
571 ERR("No viable drop format.\n");
577 DWORD WINAPI
CFSDropTarget::_DoDropThreadProc(LPVOID lpParameter
)
580 _DoDropData
*data
= static_cast<_DoDropData
*>(lpParameter
);
581 CComPtr
<IDataObject
> pDataObject
;
582 HRESULT hr
= CoGetInterfaceAndReleaseStream (data
->pStream
, IID_PPV_ARG(IDataObject
, &pDataObject
));
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
))))
590 pAsyncOperation
->EndOperation(hr
, NULL
, data
->pdwEffect
);
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
);
601 HRESULT
CFSDropTarget_CreateInstance(LPWSTR sPathTarget
, REFIID riid
, LPVOID
* ppvOut
)
603 return ShellObjectCreatorInit
<CFSDropTarget
>(sPathTarget
, riid
, ppvOut
);