Fix #32046: GHOST_DropTargetWin32 memory leak, patch by Matt D.
[blender.git] / intern / ghost / intern / GHOST_DropTargetWin32.cpp
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file ghost/intern/GHOST_DropTargetWin32.cpp
29  *  \ingroup GHOST
30  */
31
32  
33 #include "GHOST_Debug.h"
34 #include "GHOST_DropTargetWin32.h"
35 #include <ShellApi.h>
36
37 #include "utf_winfunc.h"
38 #include "utfconv.h"
39
40 #ifdef GHOST_DEBUG
41 // utility
42 void printLastError(void);
43 #endif // GHOST_DEBUG
44
45
46 GHOST_DropTargetWin32::GHOST_DropTargetWin32(GHOST_WindowWin32 *window, GHOST_SystemWin32 *system)
47         :
48         m_window(window),
49         m_system(system)
50 {
51         m_cRef = 1;
52         m_hWnd = window->getHWND();
53         m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
54 }
55
56 GHOST_DropTargetWin32::~GHOST_DropTargetWin32()
57 {
58 }
59
60
61 /* 
62  *  IUnknown::QueryInterface
63  */
64 HRESULT __stdcall GHOST_DropTargetWin32::QueryInterface(REFIID riid, void **ppvObj)
65 {
66
67         if (!ppvObj)
68                 return E_INVALIDARG;
69         *ppvObj = NULL;
70
71         if (riid == IID_IUnknown || riid == IID_IDropTarget)
72         {
73                 AddRef();
74                 *ppvObj = (void *)this;
75                 return S_OK;
76         }
77         else {
78                 *ppvObj = 0;
79                 return E_NOINTERFACE;
80         }
81 }
82
83
84 /* 
85  *      IUnknown::AddRef 
86  */
87
88 ULONG __stdcall GHOST_DropTargetWin32::AddRef(void)
89 {
90         return ::InterlockedIncrement(&m_cRef);
91 }
92
93 /* 
94  * IUnknown::Release
95  */
96 ULONG __stdcall GHOST_DropTargetWin32::Release(void)
97 {
98         ULONG refs = ::InterlockedDecrement(&m_cRef);
99                 
100         if (refs == 0)
101         {
102                 delete this;
103                 return 0;
104         }
105         else {
106                 return refs;
107         }
108 }
109
110 /* 
111  * Implementation of IDropTarget::DragEnter
112  */
113 HRESULT __stdcall GHOST_DropTargetWin32::DragEnter(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
114 {
115         // we accept all drop by default
116         m_window->setAcceptDragOperation(true);
117         *pdwEffect = DROPEFFECT_NONE;
118         
119         m_draggedObjectType = getGhostType(pDataObject);
120         m_system->pushDragDropEvent(GHOST_kEventDraggingEntered, m_draggedObjectType, m_window, pt.x, pt.y, NULL);
121         return S_OK;
122 }
123
124 /* 
125  * Implementation of IDropTarget::DragOver
126  */
127 HRESULT __stdcall GHOST_DropTargetWin32::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
128 {
129         if (m_window->canAcceptDragOperation())
130         {
131                 *pdwEffect = allowedDropEffect(*pdwEffect);
132         }
133         else {
134                 *pdwEffect = DROPEFFECT_NONE;
135                 // *pdwEffect = DROPEFFECT_COPY; // XXX Uncomment to test drop. Drop will not be called if pdwEffect == DROPEFFECT_NONE.
136         }
137         m_system->pushDragDropEvent(GHOST_kEventDraggingUpdated, m_draggedObjectType, m_window, pt.x, pt.y, NULL);
138         return S_OK;
139 }
140
141 /* 
142  * Implementation of IDropTarget::DragLeave
143  */
144 HRESULT __stdcall GHOST_DropTargetWin32::DragLeave(void)
145 {
146         m_system->pushDragDropEvent(GHOST_kEventDraggingExited, m_draggedObjectType, m_window, 0, 0, NULL);
147         m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
148         return S_OK;
149 }
150
151 /* Implementation of IDropTarget::Drop
152  * This function will not be called if pdwEffect is set to DROPEFFECT_NONE in 
153  * the implementation of IDropTarget::DragOver
154  */
155 HRESULT __stdcall GHOST_DropTargetWin32::Drop(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
156 {
157         void *data = getGhostData(pDataObject);
158         if (m_window->canAcceptDragOperation())
159         {
160                 *pdwEffect = allowedDropEffect(*pdwEffect);
161
162         }
163         else {
164                 *pdwEffect = DROPEFFECT_NONE;
165         }
166         if (data)
167                 m_system->pushDragDropEvent(GHOST_kEventDraggingDropDone, m_draggedObjectType, m_window, pt.x, pt.y, data);
168                 
169         m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
170         return S_OK;
171 }
172
173 /* 
174  * Helpers
175  */
176  
177 DWORD GHOST_DropTargetWin32::allowedDropEffect(DWORD dwAllowed)
178 {
179         DWORD dwEffect = DROPEFFECT_NONE;
180         if (dwAllowed & DROPEFFECT_COPY)
181                 dwEffect = DROPEFFECT_COPY;
182
183         return dwEffect;
184 }
185
186 GHOST_TDragnDropTypes GHOST_DropTargetWin32::getGhostType(IDataObject *pDataObject)
187 {
188         /* Text
189          * Note: Unicode text is aviable as CF_TEXT too, the system can do the 
190          * conversion, but we do the conversion ourself with WC_NO_BEST_FIT_CHARS.
191          */
192         FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
193         if (pDataObject->QueryGetData(&fmtetc) == S_OK)
194         {
195                 return GHOST_kDragnDropTypeString;
196         }
197
198         // Filesnames
199         fmtetc.cfFormat = CF_HDROP;
200         if (pDataObject->QueryGetData(&fmtetc) == S_OK)
201         {
202                 return GHOST_kDragnDropTypeFilenames;
203         }
204
205         return GHOST_kDragnDropTypeUnknown;
206 }
207
208 void *GHOST_DropTargetWin32::getGhostData(IDataObject *pDataObject)
209 {
210         GHOST_TDragnDropTypes type = getGhostType(pDataObject);
211         switch (type)
212         {
213                 case GHOST_kDragnDropTypeFilenames:
214                         return getDropDataAsFilenames(pDataObject);
215                         break;
216                 case GHOST_kDragnDropTypeString:
217                         return getDropDataAsString(pDataObject);
218                         break;
219                 case GHOST_kDragnDropTypeBitmap:
220                         //return getDropDataAsBitmap(pDataObject);
221                         break;
222                 default:
223 #ifdef GHOST_DEBUG
224                         ::printf("\nGHOST_kDragnDropTypeUnknown");
225 #endif // GHOST_DEBUG
226                         return NULL;
227                         break;
228         }
229         return NULL;
230 }
231
232 void *GHOST_DropTargetWin32::getDropDataAsFilenames(IDataObject *pDataObject)
233 {
234         UINT totfiles, nvalid = 0;
235         WCHAR fpath[MAX_PATH];
236         char *temp_path;
237         GHOST_TStringArray *strArray = NULL;
238         FORMATETC fmtetc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
239         STGMEDIUM stgmed;
240         HDROP hdrop;
241
242         // Check if dataobject supplies the format we want.
243         // Double checking here, first in getGhostType.
244         if (pDataObject->QueryGetData(&fmtetc) == S_OK)
245         {
246                 if (pDataObject->GetData(&fmtetc, &stgmed) == S_OK)
247                 {
248                         hdrop = (HDROP) ::GlobalLock(stgmed.hGlobal);
249
250                         totfiles = ::DragQueryFileW(hdrop, -1, NULL, 0);
251                         if (!totfiles)
252                         {
253                                 ::GlobalUnlock(stgmed.hGlobal);
254                                 return NULL;
255                         }
256
257                         strArray = (GHOST_TStringArray *) ::malloc(sizeof(GHOST_TStringArray));
258                         strArray->count = 0;
259                         strArray->strings = (GHOST_TUns8 **) ::malloc(totfiles * sizeof(GHOST_TUns8 *));
260
261                         for (UINT nfile = 0; nfile < totfiles; nfile++)
262                         {
263                                 if (::DragQueryFileW(hdrop, nfile, fpath, MAX_PATH) > 0)
264                                 {
265                                         if (!(temp_path = alloc_utf_8_from_16(fpath, 0)) )
266                                         {
267                                                 continue;
268                                         } 
269                                         // Just ignore paths that could not be converted verbatim.
270
271                                         strArray->strings[nvalid] = (GHOST_TUns8 *) temp_path;
272                                         strArray->count = nvalid + 1;
273                                         nvalid++;
274                                 }
275                         }
276                         // Free up memory.
277                         ::GlobalUnlock(stgmed.hGlobal);
278                         ::ReleaseStgMedium(&stgmed);
279                         
280                         return strArray;
281                 }
282         }
283         return NULL;
284 }
285
286 void *GHOST_DropTargetWin32::getDropDataAsString(IDataObject *pDataObject)
287 {
288         char *tmp_string;
289         FORMATETC fmtetc = { CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
290         STGMEDIUM stgmed;
291
292         // Try unicode first.
293         // Check if dataobject supplies the format we want.
294         if (pDataObject->QueryGetData(&fmtetc) == S_OK)
295         {
296                 if (pDataObject->GetData(&fmtetc, &stgmed) == S_OK)
297                 {
298                         LPCWSTR wstr = (LPCWSTR) ::GlobalLock(stgmed.hGlobal);
299                         if (!(tmp_string = alloc_utf_8_from_16((wchar_t *)wstr, 0)) )
300                         {
301                                 ::GlobalUnlock(stgmed.hGlobal);
302                                 return NULL;
303                         }
304                         // Free memory
305                         ::GlobalUnlock(stgmed.hGlobal);
306                         ::ReleaseStgMedium(&stgmed);
307 #ifdef GHOST_DEBUG
308                         ::printf("\n<converted droped unicode string>\n%s\n</droped converted unicode string>\n", tmp_string);
309 #endif // GHOST_DEBUG
310                         return tmp_string;
311                 }
312         }
313
314         fmtetc.cfFormat = CF_TEXT;
315
316         if (pDataObject->QueryGetData(&fmtetc) == S_OK)
317         {
318                 if (pDataObject->GetData(&fmtetc, &stgmed) == S_OK)
319                 {
320                         char *str = (char *)::GlobalLock(stgmed.hGlobal);
321                         
322                         tmp_string = (char *)::malloc(::strlen(str) + 1);
323                         if (!tmp_string)
324                         {
325                                 ::GlobalUnlock(stgmed.hGlobal);
326                                 return NULL;
327                         }
328
329                         if (!::strcpy(tmp_string, str) )
330                         {
331                                 ::free(tmp_string);
332                                 ::GlobalUnlock(stgmed.hGlobal);
333                                 return NULL;
334                         }
335                         // Free memory
336                         ::GlobalUnlock(stgmed.hGlobal);
337                         ::ReleaseStgMedium(&stgmed);
338
339                         return tmp_string;
340                 }
341         }
342         
343         return NULL;
344 }
345
346 int GHOST_DropTargetWin32::WideCharToANSI(LPCWSTR in, char * &out)
347 {
348         int size;
349         out = NULL; //caller should free if != NULL
350
351         // Get the required size.
352         size = ::WideCharToMultiByte(CP_ACP,        //System Default Codepage
353                                      0x00000400,    // WC_NO_BEST_FIT_CHARS
354                                      in,
355                                      -1,            //-1 null terminated, makes output null terminated too.
356                                      NULL,
357                                      0,
358                                      NULL, NULL
359                                      );
360         
361         if (!size)
362         {
363 #ifdef GHOST_DEBUG
364                 ::printLastError();
365 #endif // GHOST_DEBUG
366                 return 0;
367         }
368
369         out = (char *)::malloc(size);
370         if (!out)
371         {
372                 ::printf("\nmalloc failed!!!");
373                 return 0;
374         }
375
376         size = ::WideCharToMultiByte(CP_ACP,
377                                      0x00000400,
378                                      in,
379                                      -1,
380                                      (LPSTR) out,
381                                      size,
382                                      NULL, NULL
383                                      );
384
385         if (!size)
386         {
387 #ifdef GHOST_DEBUG
388                 ::printLastError();
389 #endif //GHOST_DEBUG
390                 ::free(out);
391                 out = NULL;
392         }
393         return size;
394 }
395
396 #ifdef GHOST_DEBUG
397 void printLastError(void)
398 {
399         LPTSTR s;
400         DWORD err;
401
402         err = GetLastError();
403         if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
404                           FORMAT_MESSAGE_FROM_SYSTEM,
405                           NULL,
406                           err,
407                           0,
408                           (LPTSTR)&s,
409                           0,
410                           NULL)
411             )
412         {
413                 printf("\nLastError: (%d) %s\n", (int)err, s);
414                 LocalFree(s);
415         }
416 }
417 #endif // GHOST_DEBUG
418