OSDN Git Service

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