OSDN Git Service

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