OSDN Git Service

Merge with stable
[winmerge-jp/winmerge-jp.git] / Src / DropHandler.cpp
1 #include <StdAfx.h>
2 #include "DropHandler.h"
3 #include <memory>
4 #include <ShlObj.h>
5 #include <comip.h>
6 #include "paths.h"
7 #include "Environment.h"
8 #include "unicoder.h"
9 #include "WaitStatusCursor.h"
10
11 namespace
12 {
13         struct HandleDeleter {
14                 typedef HANDLE pointer;
15                 void operator()(HANDLE h) { if (h && h != INVALID_HANDLE_VALUE) ::CloseHandle(h); }
16         };
17
18         typedef std::unique_ptr<HANDLE, HandleDeleter> unique_handle;
19         typedef _com_ptr_t<_com_IIID<IFileOperation, &__uuidof(IFileOperation)>> IFileOperationPtr;
20         typedef _com_ptr_t<_com_IIID<IShellItem, &__uuidof(IShellItem)>> IShellItemPtr;
21
22         bool CopyFileOrFolder(const String& src, const String& dst)
23         {
24                 std::vector<TCHAR> srcpath(src.length() + 2, 0);
25                 std::vector<TCHAR> dstpath(dst.length() + 2, 0);
26                 memcpy(&srcpath[0], src.c_str(), src.length() * sizeof(TCHAR));
27                 memcpy(&dstpath[0], dst.c_str(), dst.length() * sizeof(TCHAR));
28                 SHFILEOPSTRUCT fileop = { 0, FO_COPY, &srcpath[0], &dstpath[0], FOF_NOCONFIRMATION, 0, 0, 0 };
29                 return SHFileOperation(&fileop) == 0;
30         }
31
32         //
33         //      OnDropFiles code from CDropEdit
34         //      Copyright 1997 Chris Losinger
35         //
36         //      shortcut expansion code modified from :
37         //      CShortcut, 1996 Rob Warner
38         //
39
40         std::vector<String> GetDroppedFiles(HDROP dropInfo)
41         {
42                 std::vector<String> files;
43                 // Get the number of pathnames that have been dropped
44                 UINT wNumFilesDropped = DragQueryFile(dropInfo, 0xFFFFFFFF, NULL, 0);
45                 UINT fileCount = 0;
46
47                 // get all file names. but we'll only need the first one.
48                 for (WORD x = 0; x < wNumFilesDropped; x++)
49                 {
50                         // Get the number of bytes required by the file's full pathname
51                         UINT wPathnameSize = DragQueryFile(dropInfo, x, NULL, 0);
52
53                         // Allocate memory to contain full pathname & zero byte
54                         wPathnameSize += 1;
55                         std::unique_ptr<TCHAR[]> npszFile(new TCHAR[wPathnameSize]);
56
57                         // Copy the pathname into the buffer
58                         DragQueryFile(dropInfo, x, npszFile.get(), wPathnameSize);
59
60                         files.push_back(npszFile.get());
61                 }
62                 return files;
63         }
64
65         std::vector<String> FilterFiles(const std::vector<String>& files_src)
66         {
67                 std::vector<String> files(files_src);
68                 TCHAR szTempPath[MAX_PATH];
69                 TCHAR szTempPathShort[MAX_PATH];
70                 GetTempPath(sizeof(szTempPath) / sizeof(szTempPath[0]), szTempPath);
71                 GetShortPathName(szTempPath, szTempPathShort, sizeof(szTempPathShort) / sizeof(szTempPathShort[0]));
72
73                 for (UINT i = 0; i < files.size(); i++)
74                 {
75                         if (paths_IsShortcut(files[i]))
76                         {
77                                 // if this was a shortcut, we need to expand it to the target path
78                                 String expandedFile = ExpandShortcut(files[i]);
79
80                                 // if that worked, we should have a real file name
81                                 if (!expandedFile.empty())
82                                         files[i] = expandedFile;
83                         }
84                         else if (paths_IsDecendant(files[i], szTempPath) || paths_IsDecendant(files[i], szTempPathShort))
85                         {
86                                 String tmpdir = env_GetTempChildPath();
87                                 CopyFileOrFolder(files[i], tmpdir);
88                                 files[i] = paths_ConcatPath(tmpdir, paths_FindFileName(files[i]));
89                         }
90                 }
91                 return files;
92         }
93
94         HRESULT IStream_WriteToFile(IStream *pStream, const String& filename)
95         {
96                 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL));
97                 if (hFile.get() == INVALID_HANDLE_VALUE)
98                         return E_FAIL;
99                 for (;;)
100                 {
101                         char buf[65536];
102                         DWORD dwWritten;
103                         ULONG size = 0;
104                         HRESULT hr = pStream->Read(buf, sizeof(buf), &size);
105                         if (FAILED(hr))
106                                 return hr;
107                         if (size == 0)
108                                 break;
109                         WriteFile(hFile.get(), buf, size, &dwWritten, NULL);
110                 }
111                 return S_OK;
112         }
113
114         HRESULT HGLOBAL_WriteToFile(HGLOBAL hGlobal, const String& filename)
115         {
116                 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL));
117                 if (hFile.get() == INVALID_HANDLE_VALUE)
118                         return E_FAIL;
119                 char *p = static_cast<char *>(GlobalLock(hGlobal));
120                 if (!p)
121                         return E_FAIL;
122                 SIZE_T size = GlobalSize(hGlobal);
123                 while (size > 0)
124                 {
125                         DWORD dwWritten;
126                         if (WriteFile(hFile.get(), p, (size > INT_MAX) ? INT_MAX : static_cast<DWORD>(size), &dwWritten, NULL) == FALSE)
127                         {
128                                 GlobalUnlock(hGlobal);
129                                 return E_FAIL;
130                         }
131                         p += dwWritten;
132                         size -= dwWritten;
133                 }
134                 GlobalUnlock(hGlobal);
135                 return S_OK;
136         }
137
138         HRESULT SetFileWriteTime(const String& filename, const FILETIME& writetime)
139         {
140                 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
141                 if (hFile.get() == INVALID_HANDLE_VALUE)
142                         return E_FAIL;
143                 return SetFileTime(hFile.get(), NULL, NULL, &writetime) ? S_OK : E_FAIL;
144         }
145
146         HRESULT GetFileItemsFromIDataObject_CF_HDROP(IDataObject *pDataObj, std::vector<String>& files)
147         {
148                 FORMATETC fmtetc_cf_hdrop = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
149                 STGMEDIUM medium = { 0 };
150                 HRESULT hr;
151                 if ((hr = pDataObj->GetData(&fmtetc_cf_hdrop, &medium)) == S_OK)
152                 {
153                         HDROP hDrop = (HDROP)GlobalLock(medium.hGlobal);
154                         if (hDrop)
155                         {
156                                 files = FilterFiles(GetDroppedFiles(hDrop));
157                                 GlobalUnlock(medium.hGlobal);
158                         }
159                         ReleaseStgMedium(&medium);
160                 }
161                 return hr;
162         }
163
164 #define HIDA_GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
165 #define HIDA_GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
166
167         HRESULT GetFileItemsFromIDataObject_ShellIDList(IDataObject *pDataObj, std::vector<String>& root_files)
168         {
169                 String tmpdir;
170                 FORMATETC fmtetc_filedescriptor = { RegisterClipboardFormat(CFSTR_SHELLIDLIST), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
171                 STGMEDIUM medium = { 0 };
172                 HRESULT hr;
173                 if ((hr = pDataObj->GetData(&fmtetc_filedescriptor, &medium)) == S_OK)
174                 {
175                         CIDA *pcida = (CIDA *)GlobalLock(medium.hGlobal);
176                         if (pcida)
177                         {
178                                 LPCITEMIDLIST pidlParent = HIDA_GetPIDLFolder(pcida);
179                                 for (unsigned i = 0; i < pcida->cidl; ++i)
180                                 {
181                                         IShellItemPtr pShellItem;
182                                         if (SUCCEEDED(hr = SHCreateShellItem(pidlParent, NULL, HIDA_GetPIDLItem(pcida, i), &pShellItem)))
183                                         {
184                                                 SFGAOF sfgaof = 0;
185                                                 if (SUCCEEDED(hr = pShellItem->GetAttributes(SFGAO_FOLDER, &sfgaof)) && (sfgaof & SFGAO_FOLDER))
186                                                 {
187                                                         // Folder item
188                                                         wchar_t *pPath = NULL;
189                                                         if (SUCCEEDED(hr = pShellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pPath)))
190                                                         {
191                                                                 root_files.push_back(ucr::toTString(pPath));
192                                                                 CoTaskMemFree(pPath);
193                                                         }
194                                                 }
195                                                 else
196                                                 {
197                                                         // File item
198                                                         IFileOperationPtr pFileOperation;
199                                                         if (SUCCEEDED(hr = pFileOperation.CreateInstance(CLSID_FileOperation, NULL, CLSCTX_ALL)))
200                                                         {
201                                                                 if (tmpdir.empty())
202                                                                         tmpdir = env_GetTempChildPath();
203                                                                 pFileOperation->SetOperationFlags(0);
204                                                                 PIDLIST_ABSOLUTE pidlDest;
205                                                                 if (SUCCEEDED(hr = SHParseDisplayName(ucr::toUTF16(tmpdir).c_str(), NULL, &pidlDest, 0, NULL)))
206                                                                 {
207                                                                         IShellItemPtr pShellItemDest;
208                                                                         SHCreateShellItem(NULL, NULL, pidlDest, &pShellItemDest);
209                                                                         pFileOperation->CopyItem(pShellItem, pShellItemDest, NULL, NULL);
210                                                                         if (SUCCEEDED(hr = pFileOperation->PerformOperations()))
211                                                                         {
212                                                                                 wchar_t *pName;
213                                                                                 if (SUCCEEDED(hr = pShellItem->GetDisplayName(SIGDN_PARENTRELATIVEPARSING, &pName)))
214                                                                                 {
215                                                                                         root_files.push_back(paths_ConcatPath(tmpdir, ucr::toTString(pName)));
216                                                                                         CoTaskMemFree(pName);
217                                                                                 }
218                                                                         }
219                                                                 }
220                                                         }
221                                                 }
222                                         }
223                                 }
224                                 GlobalUnlock(medium.hGlobal);
225                         }
226                         ReleaseStgMedium(&medium);
227                 }
228                 return hr;
229         }
230
231         HRESULT ExtractFileItemFromIDataObject_FileContents(IDataObject *pDataObj, int lindex, const String& filepath)
232         {
233                 FORMATETC fmtetc_filecontents = { RegisterClipboardFormat(CFSTR_FILECONTENTS), NULL, DVASPECT_CONTENT, lindex, TYMED_HGLOBAL | TYMED_ISTREAM };
234                 STGMEDIUM medium = { 0 };
235                 HRESULT hr;
236                 if ((hr = pDataObj->GetData(&fmtetc_filecontents, &medium)) == S_OK)
237                 {
238                         hr = E_FAIL;
239                         if (medium.tymed == TYMED_HGLOBAL)
240                                 hr = HGLOBAL_WriteToFile(medium.hGlobal, filepath);
241                         else if (medium.tymed == TYMED_ISTREAM)
242                                 hr = IStream_WriteToFile(medium.pstm, filepath);
243                         ReleaseStgMedium(&medium);
244                 }
245                 return hr;
246         }
247
248         HRESULT GetFileItemsFromIDataObject_FileDescriptor(IDataObject *pDataObj, std::vector<String>& root_files)
249         {
250                 String tmpdir = env_GetTempChildPath();
251                 FORMATETC fmtetc_filedescriptor = { RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
252                 STGMEDIUM medium = { 0 };
253                 HRESULT hr;
254                 if ((hr = pDataObj->GetData(&fmtetc_filedescriptor, &medium)) == S_OK)
255                 {
256                         FILEGROUPDESCRIPTOR *file_group_descriptor = (FILEGROUPDESCRIPTOR *)GlobalLock(medium.hGlobal);
257                         if (file_group_descriptor)
258                         {
259                                 for (unsigned i = 0; i < file_group_descriptor->cItems; ++i)
260                                 {
261                                         String filename = file_group_descriptor->fgd[i].cFileName;
262                                         String filepath = paths_ConcatPath(tmpdir, filename);
263                                         if (file_group_descriptor->fgd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
264                                                 paths_CreateIfNeeded(filepath);
265                                         else
266                                         {
267                                                 ExtractFileItemFromIDataObject_FileContents(pDataObj, i, filepath);
268                                                 if (file_group_descriptor->fgd[i].dwFlags & FD_WRITESTIME)
269                                                         SetFileWriteTime(filepath, file_group_descriptor->fgd[i].ftLastWriteTime);
270                                         }
271                                         if (filename.find('\\') == String::npos)
272                                                 root_files.push_back(filepath);
273                                 }
274                                 GlobalUnlock(medium.hGlobal);
275                         }
276                         ReleaseStgMedium(&medium);
277                 }
278                 return hr;
279         }
280
281 }
282
283 DropHandler::DropHandler(std::function<void(const std::vector<String>&)> callback) 
284         : m_cRef(0), m_callback(callback)
285 {
286 }
287
288 DropHandler::~DropHandler()
289 {
290 }
291
292 HRESULT STDMETHODCALLTYPE DropHandler::QueryInterface(REFIID riid, void **ppvObject)
293 {
294         if (!IsEqualIID(riid, IID_IUnknown) && !IsEqualIID(riid, IID_IDropTarget))
295         {
296                 *ppvObject = NULL;
297                 return E_NOINTERFACE;
298         }
299         *ppvObject = static_cast<IDropTarget *>(this);
300         AddRef();
301         return S_OK;
302 }
303
304 ULONG STDMETHODCALLTYPE DropHandler::AddRef(void)
305 {
306         return InterlockedIncrement(&m_cRef);
307 }
308
309 ULONG STDMETHODCALLTYPE DropHandler::Release(void)
310 {
311         if (InterlockedDecrement(&m_cRef) == 0) {
312                 delete this;
313                 return 0;
314         }
315         return m_cRef;
316 }
317
318 HRESULT STDMETHODCALLTYPE DropHandler::DragEnter(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
319 {
320         FORMATETC fmtetc_cf_hdrop = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
321         FORMATETC fmtetc_shellidlist = { RegisterClipboardFormat(CFSTR_SHELLIDLIST), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
322         FORMATETC fmtetc_filedescriptor = { RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
323         if (pDataObj->QueryGetData(&fmtetc_cf_hdrop) == S_OK ||
324             pDataObj->QueryGetData(&fmtetc_shellidlist) == S_OK ||
325             pDataObj->QueryGetData(&fmtetc_filedescriptor) == S_OK)
326         {
327                 *pdwEffect = DROPEFFECT_COPY;
328                 return S_OK;
329         }
330         *pdwEffect = DROPEFFECT_NONE;
331         return DRAGDROP_S_CANCEL;
332 }
333
334 HRESULT STDMETHODCALLTYPE DropHandler::DragOver(DWORD, POINTL, DWORD *)
335 {
336         return S_OK;
337 }
338
339 HRESULT STDMETHODCALLTYPE DropHandler::DragLeave(void)
340 {
341         return S_OK;
342 }
343
344 HRESULT DropHandler::Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
345 {
346         bool ok = false;
347         WaitStatusCursor waitstatus(_T("Copying files..."));
348         std::vector<String> files;
349         FORMATETC fmtetc_cf_hdrop = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
350         FORMATETC fmtetc_shellidlist = { RegisterClipboardFormat(CFSTR_SHELLIDLIST), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
351         FORMATETC fmtetc_filedescriptor = { RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
352         if (pDataObj->QueryGetData(&fmtetc_cf_hdrop) == S_OK &&
353                 GetFileItemsFromIDataObject_CF_HDROP(pDataObj, files) == S_OK && files.size() > 0)
354                 ok = true;
355         else if (pDataObj->QueryGetData(&fmtetc_shellidlist) == S_OK &&
356                 GetFileItemsFromIDataObject_ShellIDList(pDataObj, files) == S_OK && files.size() > 0)
357                 ok = true;
358         else if (pDataObj->QueryGetData(&fmtetc_filedescriptor) == S_OK &&
359                 GetFileItemsFromIDataObject_FileDescriptor(pDataObj, files) == S_OK && files.size() > 0)
360                 ok = true;
361         if (files.size() > 3)
362                 files.resize(3);
363         if (!files.empty())
364                 m_callback(files);
365         return S_OK;
366 }