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
33 static WCHAR
* BuildPathsList(LPCWSTR wszBasePath
, int cidl
, LPCITEMIDLIST
*pidls
)
35 WCHAR
*pwszPathsList
= (WCHAR
*)HeapAlloc(GetProcessHeap(), 0, MAX_PATH
* sizeof(WCHAR
) * cidl
+ 1);
36 WCHAR
*pwszListPos
= pwszPathsList
;
38 for (int i
= 0; i
< cidl
; i
++)
40 FileStructW
* pDataW
= _ILGetFileStructW(pidls
[i
]);
43 ERR("Got garbage pidl\n");
47 PathCombineW(pwszListPos
, wszBasePath
, pDataW
->wszName
);
48 pwszListPos
+= wcslen(pwszListPos
) + 1;
54 /****************************************************************************
55 * CFSDropTarget::CopyItems
57 * copies items to this folder
59 HRESULT WINAPI
CFSDropTarget::CopyItems(IShellFolder
* pSFFrom
, UINT cidl
,
60 LPCITEMIDLIST
* apidl
, BOOL bCopy
)
64 WCHAR wszTargetPath
[MAX_PATH
+ 1];
66 wcscpy(wszTargetPath
, sPathTarget
);
67 //Double NULL terminate.
68 wszTargetPath
[wcslen(wszTargetPath
) + 1] = '\0';
70 TRACE ("(%p)->(%p,%u,%p)\n", this, pSFFrom
, cidl
, apidl
);
73 hr
= pSFFrom
->GetDisplayNameOf(NULL
, SHGDN_FORPARSING
, &strretFrom
);
74 if (FAILED_UNEXPECTEDLY(hr
))
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
);
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
;
90 int res
= SHFileOperationW(&op
);
92 HeapFree(GetProcessHeap(), 0, pszSrcList
);
100 CFSDropTarget::CFSDropTarget():
107 HRESULT WINAPI
CFSDropTarget::Initialize(LPWSTR PathTarget
)
112 cfShellIDList
= RegisterClipboardFormatW(CFSTR_SHELLIDLIST
);
116 sPathTarget
= (WCHAR
*)SHAlloc((wcslen(PathTarget
) + 1) * sizeof(WCHAR
));
118 return E_OUTOFMEMORY
;
120 wcscpy(sPathTarget
, PathTarget
);
125 CFSDropTarget::~CFSDropTarget()
131 CFSDropTarget::GetUniqueFileName(LPWSTR pwszBasePath
, LPCWSTR pwszExt
, LPWSTR pwszTarget
, BOOL bShortcut
)
137 if (!LoadStringW(shell32_hInstance
, IDS_LNK_FILE
, wszLink
, _countof(wszLink
)))
142 swprintf(pwszTarget
, L
"%s%s%s", wszLink
, pwszBasePath
, pwszExt
);
144 swprintf(pwszTarget
, L
"%s%s", pwszBasePath
, pwszExt
);
146 for (UINT i
= 2; PathFileExistsW(pwszTarget
); ++i
)
149 swprintf(pwszTarget
, L
"%s%s (%u)%s", wszLink
, pwszBasePath
, i
, pwszExt
);
151 swprintf(pwszTarget
, L
"%s (%u)%s", pwszBasePath
, i
, pwszExt
);
157 /****************************************************************************
158 * IDropTarget implementation
160 BOOL
CFSDropTarget::QueryDrop(DWORD dwKeyState
, LPDWORD pdwEffect
)
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. */
165 DWORD dwEffect
= DROPEFFECT_MOVE
;
167 *pdwEffect
= DROPEFFECT_NONE
;
169 if (fAcceptFmt
) { /* Does our interpretation of the keystate ... */
170 *pdwEffect
= KeyStateToDropEffect (dwKeyState
);
172 if (*pdwEffect
== DROPEFFECT_NONE
)
173 *pdwEffect
= dwEffect
;
175 /* ... matches the desired effect ? */
176 if (dwEffect
& *pdwEffect
) {
183 HRESULT WINAPI
CFSDropTarget::DragEnter(IDataObject
*pDataObject
,
184 DWORD dwKeyState
, POINTL pt
, DWORD
*pdwEffect
)
186 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject
);
191 InitFormatEtc (fmt
, cfShellIDList
, TYMED_HGLOBAL
);
192 InitFormatEtc (fmt2
, CF_HDROP
, TYMED_HGLOBAL
);
194 if (SUCCEEDED(pDataObject
->QueryGetData(&fmt
)))
196 else if (SUCCEEDED(pDataObject
->QueryGetData(&fmt2
)))
199 QueryDrop(dwKeyState
, pdwEffect
);
203 HRESULT WINAPI
CFSDropTarget::DragOver(DWORD dwKeyState
, POINTL pt
,
206 TRACE("(%p)\n", this);
211 QueryDrop(dwKeyState
, pdwEffect
);
216 HRESULT WINAPI
CFSDropTarget::DragLeave()
218 TRACE("(%p)\n", this);
225 HRESULT WINAPI
CFSDropTarget::Drop(IDataObject
*pDataObject
,
226 DWORD dwKeyState
, POINTL pt
, DWORD
*pdwEffect
)
228 TRACE("(%p) object dropped, effect %u\n", this, *pdwEffect
);
233 QueryDrop(dwKeyState
, pdwEffect
);
235 BOOL fIsOpAsync
= FALSE
;
236 CComPtr
<IAsyncOperation
> pAsyncOperation
;
238 if (SUCCEEDED(pDataObject
->QueryInterface(IID_PPV_ARG(IAsyncOperation
, &pAsyncOperation
))))
240 if (SUCCEEDED(pAsyncOperation
->GetAsyncMode(&fIsOpAsync
)) && fIsOpAsync
)
242 _DoDropData
*data
= static_cast<_DoDropData
*>(HeapAlloc(GetProcessHeap(), 0, sizeof(_DoDropData
)));
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
);
249 data
->dwKeyState
= dwKeyState
;
251 // Need to dereference as pdweffect gets freed.
252 data
->pdwEffect
= *pdwEffect
;
253 SHCreateThread(CFSDropTarget::_DoDropThreadProc
, data
, NULL
, NULL
);
257 return this->_DoDrop(pDataObject
, dwKeyState
, pt
, pdwEffect
);
260 HRESULT WINAPI
CFSDropTarget::_DoDrop(IDataObject
*pDataObject
,
261 DWORD dwKeyState
, POINTL pt
, DWORD
*pdwEffect
)
263 TRACE("(%p) performing drop, effect %u\n", this, *pdwEffect
);
268 InitFormatEtc (fmt
, cfShellIDList
, TYMED_HGLOBAL
);
269 InitFormatEtc (fmt2
, CF_HDROP
, TYMED_HGLOBAL
);
273 bool bLinking
= FALSE
;
275 /* Figure out what drop operation we're doing */
278 TRACE("Current drop effect flag %i\n", *pdwEffect
);
279 if ((*pdwEffect
& DROPEFFECT_MOVE
) == DROPEFFECT_MOVE
)
281 if ((*pdwEffect
& DROPEFFECT_LINK
) == DROPEFFECT_LINK
)
285 if (SUCCEEDED(pDataObject
->QueryGetData(&fmt
)))
287 hr
= pDataObject
->GetData(&fmt
, &medium
);
288 TRACE("CFSTR_SHELLIDLIST.\n");
290 /* lock the handle */
291 LPIDA lpcida
= (LPIDA
)GlobalLock(medium
.hGlobal
);
294 ReleaseStgMedium(&medium
);
298 /* convert the data into pidl */
300 LPITEMIDLIST
*apidl
= _ILCopyCidaToaPidl(&pidl
, lpcida
);
303 ReleaseStgMedium(&medium
);
307 CComPtr
<IShellFolder
> psfDesktop
;
308 CComPtr
<IShellFolder
> psfFrom
= NULL
;
310 /* Grab the desktop shell folder */
311 hr
= SHGetDesktopFolder(&psfDesktop
);
314 ERR("SHGetDesktopFolder failed\n");
316 _ILFreeaPidl(apidl
, lpcida
->cidl
);
317 ReleaseStgMedium(&medium
);
321 /* Find source folder, this is where the clipboard data was copied from */
322 if (_ILIsDesktop(pidl
))
324 /* use desktop shell folder */
325 psfFrom
= psfDesktop
;
329 hr
= psfDesktop
->BindToObject(pidl
, NULL
, IID_PPV_ARG(IShellFolder
, &psfFrom
));
332 ERR("no IShellFolder\n");
334 _ILFreeaPidl(apidl
, lpcida
->cidl
);
335 ReleaseStgMedium(&medium
);
342 WCHAR wszTargetPath
[MAX_PATH
];
343 WCHAR wszPath
[MAX_PATH
];
344 WCHAR wszTarget
[MAX_PATH
];
346 wcscpy(wszTargetPath
, sPathTarget
);
348 TRACE("target path = %s", debugstr_w(wszTargetPath
));
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
++)
353 //Find out which file we're copying
355 hr
= psfFrom
->GetDisplayNameOf(apidl
[i
], SHGDN_FORPARSING
, &strFile
);
358 ERR("Error source obtaining path");
362 hr
= StrRetToBufW(&strFile
, apidl
[i
], wszPath
, _countof(wszPath
));
365 ERR("Error putting source path into buffer");
368 TRACE("source path = %s", debugstr_w(wszPath
));
370 // Creating a buffer to hold the combined path
371 WCHAR buffer_1
[MAX_PATH
] = L
"";
375 LPWSTR pwszFileName
= PathFindFileNameW(wszPath
);
376 LPWSTR pwszExt
= PathFindExtensionW(wszPath
);
377 LPWSTR placementPath
= PathCombineW(lpStr1
, sPathTarget
, pwszFileName
);
378 CComPtr
<IPersistFile
> ppf
;
380 //Check to see if it's already a link.
381 if (!wcsicmp(pwszExt
, L
".lnk"))
383 //It's a link so, we create a new one which copies the old.
384 if(!GetUniqueFileName(placementPath
, pwszExt
, wszTarget
, TRUE
))
386 ERR("Error getting unique file name");
390 hr
= IShellLink_ConstructFromPath(wszPath
, IID_PPV_ARG(IPersistFile
, &ppf
));
392 ERR("Error constructing link from file");
396 hr
= ppf
->Save(wszTarget
, FALSE
);
399 SHChangeNotify(SHCNE_CREATE
, SHCNF_PATHW
, wszTarget
, NULL
);
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
))
407 ERR("Error creating unique file name");
412 CComPtr
<IShellLinkW
> pLink
;
413 hr
= CShellLink::_CreatorClass::CreateInstance(NULL
, IID_PPV_ARG(IShellLinkW
, &pLink
));
415 ERR("Error instantiating IShellLinkW");
419 WCHAR szDirPath
[MAX_PATH
], *pwszFile
;
420 GetFullPathName(wszPath
, MAX_PATH
, szDirPath
, &pwszFile
);
421 if (pwszFile
) pwszFile
[0] = 0;
423 hr
= pLink
->SetPath(wszPath
);
427 hr
= pLink
->SetWorkingDirectory(szDirPath
);
431 hr
= pLink
->QueryInterface(IID_PPV_ARG(IPersistFile
, &ppf
));
435 hr
= ppf
->Save(wszTarget
, TRUE
);
438 SHChangeNotify(SHCNE_CREATE
, SHCNF_PATHW
, wszTarget
, NULL
);
444 hr
= this->CopyItems(psfFrom
, lpcida
->cidl
, (LPCITEMIDLIST
*)apidl
, bCopy
);
448 _ILFreeaPidl(apidl
, lpcida
->cidl
);
449 ReleaseStgMedium(&medium
);
451 else if (SUCCEEDED(pDataObject
->QueryGetData(&fmt2
)))
454 InitFormatEtc (fmt2
, CF_HDROP
, TYMED_HGLOBAL
);
455 if (SUCCEEDED(pDataObject
->GetData(&fmt2
, &medium
)) /* && SUCCEEDED(pDataObject->GetData(&fmt2, &medium))*/)
457 WCHAR wszTargetPath
[MAX_PATH
+ 1];
460 wcscpy(wszTargetPath
, sPathTarget
);
461 //Double NULL terminate.
462 wszTargetPath
[wcslen(wszTargetPath
) + 1] = '\0';
464 LPDROPFILES lpdf
= (LPDROPFILES
) GlobalLock(medium
.hGlobal
);
467 ERR("Error locking global\n");
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
));
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
);
483 ERR("Error calling GetData\n");
488 ERR("No viable drop format.\n");
494 DWORD WINAPI
CFSDropTarget::_DoDropThreadProc(LPVOID lpParameter
)
497 _DoDropData
*data
= static_cast<_DoDropData
*>(lpParameter
);
498 CComPtr
<IDataObject
> pDataObject
;
499 HRESULT hr
= CoGetInterfaceAndReleaseStream (data
->pStream
, IID_PPV_ARG(IDataObject
, &pDataObject
));
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
))))
507 pAsyncOperation
->EndOperation(hr
, NULL
, data
->pdwEffect
);
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
);
518 HRESULT
CFSDropTarget_CreateInstance(LPWSTR sPathTarget
, REFIID riid
, LPVOID
* ppvOut
)
520 return ShellObjectCreatorInit
<CFSDropTarget
>(sPathTarget
, riid
, ppvOut
);