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