doxygen: add newline after \file
[blender.git] / intern / ghost / intern / GHOST_DropTargetX11.cpp
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2012 by the Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup GHOST
22  */
23
24 #include "GHOST_DropTargetX11.h"
25 #include "GHOST_Debug.h"
26
27 #include <ctype.h>
28 #include <assert.h>
29
30 bool GHOST_DropTargetX11::m_xdndInitialized = false;
31 DndClass GHOST_DropTargetX11::m_dndClass;
32 Atom *GHOST_DropTargetX11::m_dndTypes = NULL;
33 Atom *GHOST_DropTargetX11::m_dndActions = NULL;
34 const char *GHOST_DropTargetX11::m_dndMimeTypes[] = {"url/url", "text/uri-list", "text/plain", "application/octet-stream"};
35 int GHOST_DropTargetX11::m_refCounter = 0;
36
37 #define dndTypeURLID           0
38 #define dndTypeURIListID       1
39 #define dndTypePlainTextID     2
40 #define dndTypeOctetStreamID   3
41
42 #define dndTypeURL          m_dndTypes[dndTypeURLID]
43 #define dndTypeURIList      m_dndTypes[dndTypeURIListID]
44 #define dndTypePlainText    m_dndTypes[dndTypePlainTextID]
45 #define dndTypeOctetStream  m_dndTypes[dndTypeOctetStreamID]
46
47 void GHOST_DropTargetX11::Initialize(void)
48 {
49         Display *display = m_system->getXDisplay();
50         int dndTypesCount = sizeof(m_dndMimeTypes) / sizeof(char *);
51         int counter;
52
53         xdnd_init(&m_dndClass, display);
54
55         m_dndTypes = new Atom[dndTypesCount + 1];
56         XInternAtoms(display, (char **)m_dndMimeTypes, dndTypesCount, 0, m_dndTypes);
57         m_dndTypes[dndTypesCount] = 0;
58
59         m_dndActions = new Atom[8];
60         counter = 0;
61
62         m_dndActions[counter++] = m_dndClass.XdndActionCopy;
63         m_dndActions[counter++] = m_dndClass.XdndActionMove;
64
65 #if 0 /* Not supported yet */
66         dndActions[counter++] = dnd->XdndActionLink;
67         dndActions[counter++] = dnd->XdndActionAsk;
68         dndActions[counter++] = dnd->XdndActionPrivate;
69         dndActions[counter++] = dnd->XdndActionList;
70         dndActions[counter++] = dnd->XdndActionDescription;
71 #endif
72
73         m_dndActions[counter++] = 0;
74 }
75
76 void GHOST_DropTargetX11::Uninitialize(void)
77 {
78         xdnd_shut(&m_dndClass);
79
80         delete[] m_dndActions;
81         delete[] m_dndTypes;
82 }
83
84 GHOST_DropTargetX11::GHOST_DropTargetX11(GHOST_WindowX11 *window, GHOST_SystemX11 *system)
85         :
86         m_window(window),
87         m_system(system)
88 {
89         if (!m_xdndInitialized) {
90                 Initialize();
91                 m_xdndInitialized = true;
92                 GHOST_PRINT("XDND initialized\n");
93         }
94
95         Window wnd = window->getXWindow();
96
97         xdnd_set_dnd_aware(&m_dndClass, wnd, 0);
98         xdnd_set_type_list(&m_dndClass, wnd, m_dndTypes);
99
100         m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
101         m_refCounter++;
102 }
103
104 GHOST_DropTargetX11::~GHOST_DropTargetX11()
105 {
106         m_refCounter--;
107         if (m_refCounter == 0) {
108                 Uninitialize();
109                 m_xdndInitialized = false;
110                 GHOST_PRINT("XDND uninitialized\n");
111         }
112 }
113
114 /* based on a code from Saul Rennison
115  * http://stackoverflow.com/questions/2673207/c-c-url-decode-library */
116
117 typedef enum DecodeState_e {
118         STATE_SEARCH = 0, ///< searching for an ampersand to convert
119         STATE_CONVERTING  ///< convert the two proceeding characters from hex
120 } DecodeState_e;
121
122 void GHOST_DropTargetX11::UrlDecode(char *decodedOut, int bufferSize, const char *encodedIn)
123 {
124         unsigned int i;
125         unsigned int len = strlen(encodedIn);
126         DecodeState_e state = STATE_SEARCH;
127         int j;
128         unsigned int asciiCharacter;
129         char tempNumBuf[3] = {0};
130         bool bothDigits = true;
131
132         memset(decodedOut, 0, bufferSize);
133
134         for (i = 0; i < len; ++i) {
135                 switch (state) {
136                         case STATE_SEARCH:
137                                 if (encodedIn[i] != '%') {
138                                         strncat(decodedOut, &encodedIn[i], 1);
139                                         assert(strlen(decodedOut) < bufferSize);
140                                         break;
141                                 }
142
143                                 /* We are now converting */
144                                 state = STATE_CONVERTING;
145                                 break;
146
147                         case STATE_CONVERTING:
148                                 bothDigits = true;
149
150                                 /* Create a buffer to hold the hex. For example, if %20, this
151                                  * buffer would hold 20 (in ASCII) */
152                                 memset(tempNumBuf, 0, sizeof(tempNumBuf));
153
154                                 /* Conversion complete (i.e. don't convert again next iter) */
155                                 state = STATE_SEARCH;
156
157                                 strncpy(tempNumBuf, &encodedIn[i], 2);
158
159                                 /* Ensure both characters are hexadecimal */
160
161                                 for (j = 0; j < 2; ++j) {
162                                         if (!isxdigit(tempNumBuf[j]))
163                                                 bothDigits = false;
164                                 }
165
166                                 if (!bothDigits)
167                                         break;
168
169                                 /* Convert two hexadecimal characters into one character */
170                                 sscanf(tempNumBuf, "%x", &asciiCharacter);
171
172                                 /* Ensure we aren't going to overflow */
173                                 assert(strlen(decodedOut) < bufferSize);
174
175                                 /* Concatenate this character onto the output */
176                                 strncat(decodedOut, (char *)&asciiCharacter, 1);
177
178                                 /* Skip the next character */
179                                 i++;
180                                 break;
181                 }
182         }
183 }
184
185 char *GHOST_DropTargetX11::FileUrlDecode(char *fileUrl)
186 {
187         if (strncmp(fileUrl, "file://", 7) == 0) {
188                 /* assume one character of encoded URL can be expanded to 4 chars max */
189                 int decodedSize = 4 * strlen(fileUrl) + 1;
190                 char *decodedPath = (char *)malloc(decodedSize);
191
192                 UrlDecode(decodedPath, decodedSize, fileUrl + 7);
193
194                 return decodedPath;
195         }
196
197         return NULL;
198 }
199
200 void *GHOST_DropTargetX11::getURIListGhostData(unsigned char *dropBuffer, int dropBufferSize)
201 {
202         GHOST_TStringArray *strArray = NULL;
203         int totPaths = 0, curLength = 0;
204
205         /* count total number of file pathes in buffer */
206         for (int i = 0; i <= dropBufferSize; i++) {
207                 if (dropBuffer[i] == 0 || dropBuffer[i] == '\n' || dropBuffer[i] == '\r') {
208                         if (curLength) {
209                                 totPaths++;
210                                 curLength = 0;
211                         }
212                 }
213                 else curLength++;
214         }
215
216         strArray = (GHOST_TStringArray *)malloc(sizeof(GHOST_TStringArray));
217         strArray->count = 0;
218         strArray->strings = (GHOST_TUns8 **)malloc(totPaths * sizeof(GHOST_TUns8 *));
219
220         curLength = 0;
221         for (int i = 0; i <= dropBufferSize; i++) {
222                 if (dropBuffer[i] == 0 || dropBuffer[i] == '\n' || dropBuffer[i] == '\r') {
223                         if (curLength) {
224                                 char *curPath = (char *)malloc(curLength + 1);
225                                 char *decodedPath;
226
227                                 strncpy(curPath, (char *)dropBuffer + i - curLength, curLength);
228                                 curPath[curLength] = 0;
229
230                                 decodedPath = FileUrlDecode(curPath);
231                                 if (decodedPath) {
232                                         strArray->strings[strArray->count] = (GHOST_TUns8 *)decodedPath;
233                                         strArray->count++;
234                                 }
235
236                                 free(curPath);
237                                 curLength = 0;
238                         }
239                 }
240                 else curLength++;
241         }
242
243         return strArray;
244 }
245
246 void *GHOST_DropTargetX11::getGhostData(Atom dropType, unsigned char *dropBuffer, int dropBufferSize)
247 {
248         void *data = NULL;
249         unsigned char *tmpBuffer = (unsigned char *)malloc(dropBufferSize + 1);
250         bool needsFree = true;
251
252         /* ensure NULL-terminator */
253         memcpy(tmpBuffer, dropBuffer, dropBufferSize);
254         tmpBuffer[dropBufferSize] = 0;
255
256         if (dropType == dndTypeURIList) {
257                 m_draggedObjectType = GHOST_kDragnDropTypeFilenames;
258                 data = getURIListGhostData(tmpBuffer, dropBufferSize);
259         }
260         else if (dropType == dndTypeURL) {
261                 /* need to be tested */
262                 char *decodedPath = FileUrlDecode((char *)tmpBuffer);
263
264                 if (decodedPath) {
265                         m_draggedObjectType = GHOST_kDragnDropTypeString;
266                         data = decodedPath;
267                 }
268         }
269         else if (dropType == dndTypePlainText || dropType == dndTypeOctetStream) {
270                 m_draggedObjectType = GHOST_kDragnDropTypeString;
271                 data = tmpBuffer;
272                 needsFree = false;
273         }
274         else {
275                 m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
276         }
277
278         if (needsFree)
279                 free(tmpBuffer);
280
281         return data;
282 }
283
284 bool GHOST_DropTargetX11::GHOST_HandleClientMessage(XEvent *event)
285 {
286         Atom dropType;
287         unsigned char *dropBuffer;
288         int dropBufferSize, dropX, dropY;
289
290         if (xdnd_get_drop(m_system->getXDisplay(), event, m_dndTypes, m_dndActions,
291                           &dropBuffer, &dropBufferSize, &dropType, &dropX, &dropY))
292         {
293                 void *data = getGhostData(dropType, dropBuffer, dropBufferSize);
294
295                 if (data)
296                         m_system->pushDragDropEvent(GHOST_kEventDraggingDropDone, m_draggedObjectType, m_window, dropX, dropY, data);
297
298                 free(dropBuffer);
299
300                 m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
301
302                 return true;
303         }
304
305         return false;
306 }