diff options
author | Sergey Sharybin <sergey.vfx@gmail.com> | 2012-02-17 20:58:09 +0400 |
---|---|---|
committer | Sergey Sharybin <sergey.vfx@gmail.com> | 2012-02-17 20:58:09 +0400 |
commit | e8a1daaf9bda8f03723879d76557278b9a025c0a (patch) | |
tree | cc67b46ef3a957c893df7e288354f126823d4d5e /intern | |
parent | 7c15fb4f2c710a15e0ae7bd758f1cf74a4e97e36 (diff) |
Drag-n-drop support on Linux
This commit implements drag-n-drop support from external applications into Blender.
Used xdnd implementation from Paul Sheer.
Diffstat (limited to 'intern')
-rw-r--r-- | intern/ghost/CMakeLists.txt | 6 | ||||
-rw-r--r-- | intern/ghost/SConscript | 2 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_DropTargetX11.cpp | 310 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_DropTargetX11.h | 135 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemX11.cpp | 22 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemX11.h | 12 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowX11.cpp | 6 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowX11.h | 6 |
8 files changed, 498 insertions, 1 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 35b617e5452..3d65f4972c4 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -220,11 +220,17 @@ elseif(UNIX) intern/GHOST_SystemX11.cpp intern/GHOST_SystemPathsX11.cpp intern/GHOST_WindowX11.cpp + intern/GHOST_DropTargetX11.cpp intern/GHOST_DisplayManagerX11.h intern/GHOST_SystemX11.h intern/GHOST_SystemPathsX11.h intern/GHOST_WindowX11.h + intern/GHOST_DropTargetX11.h + ) + + list(APPEND INC + ../../extern/xdnd ) if(X11_XF86keysym_INCLUDE_PATH) diff --git a/intern/ghost/SConscript b/intern/ghost/SConscript index 68a0cbf7bdd..d83107717fc 100644 --- a/intern/ghost/SConscript +++ b/intern/ghost/SConscript @@ -42,6 +42,8 @@ elif window_system in ('linux', 'openbsd3', 'sunos5', 'freebsd7', 'freebsd8', 'f # defs += ['PREFIX=\\"/usr/local/\\"'] # XXX, make an option defs += ['WITH_X11_XINPUT'] # XXX, make an option + incs += ' #/extern/xdnd' + elif window_system in ('win32-vc', 'win32-mingw', 'cygwin', 'linuxcross', 'win64-vc'): for f in pf: try: diff --git a/intern/ghost/intern/GHOST_DropTargetX11.cpp b/intern/ghost/intern/GHOST_DropTargetX11.cpp new file mode 100644 index 00000000000..2239ac63d33 --- /dev/null +++ b/intern/ghost/intern/GHOST_DropTargetX11.cpp @@ -0,0 +1,310 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2012 by the Blender Foundation. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): Sergey Sharybin. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file ghost/intern/GHOST_DropTargetX11.cpp + * \ingroup GHOST + */ + +#include "GHOST_DropTargetX11.h" +#include "GHOST_Debug.h" + +#include <ctype.h> +#include <assert.h> + +bool GHOST_DropTargetX11::m_xdndInitialized = false; +DndClass GHOST_DropTargetX11::m_dndClass; +Atom * GHOST_DropTargetX11::m_dndTypes = NULL; +Atom * GHOST_DropTargetX11::m_dndActions = NULL; +const char *GHOST_DropTargetX11::m_dndMimeTypes[] = {"url/url", "text/uri-list", "text/plain", "application/octet-stream"}; +int GHOST_DropTargetX11::m_refCounter = 0; + +#define dndTypeURLID 0 +#define dndTypeURIListID 1 +#define dndTypePlainTextID 2 +#define dndTypeOctetStreamID 3 + +#define dndTypeURL m_dndTypes[dndTypeURLID] +#define dndTypeURIList m_dndTypes[dndTypeURIListID] +#define dndTypePlainText m_dndTypes[dndTypePlainTextID] +#define dndTypeOctetStream m_dndTypes[dndTypeOctetStreamID] + +void GHOST_DropTargetX11::Initialize(void) +{ + Display *display = m_system->getXDisplay(); + int dndTypesCount = sizeof(m_dndMimeTypes) / sizeof(char*); + int counter; + + xdnd_init(&m_dndClass, display); + + m_dndTypes = new Atom[dndTypesCount + 1]; + XInternAtoms(display, (char**)m_dndMimeTypes, dndTypesCount, 0, m_dndTypes); + m_dndTypes[dndTypesCount] = 0; + + m_dndActions = new Atom[8]; + counter = 0; + + m_dndActions[counter++] = m_dndClass.XdndActionCopy; + m_dndActions[counter++] = m_dndClass.XdndActionMove; + +#if 0 /* Not supported yet */ + dndActions[counter++] = dnd->XdndActionLink; + dndActions[counter++] = dnd->XdndActionAsk; + dndActions[counter++] = dnd->XdndActionPrivate; + dndActions[counter++] = dnd->XdndActionList; + dndActions[counter++] = dnd->XdndActionDescription; +#endif + + m_dndActions[counter++] = 0; +} + +void GHOST_DropTargetX11::Uninitialize(void) +{ + xdnd_shut(&m_dndClass); +} + +GHOST_DropTargetX11::GHOST_DropTargetX11(GHOST_WindowX11 * window, GHOST_SystemX11 * system) +: +m_window(window), +m_system(system) +{ + if (!m_xdndInitialized) { + Initialize(); + m_xdndInitialized = true; + GHOST_PRINT("XDND initialized\n"); + } + + Window wnd = window->getXWindow(); + + xdnd_set_dnd_aware(&m_dndClass, wnd, 0); + xdnd_set_type_list(&m_dndClass, wnd, m_dndTypes); + + m_draggedObjectType = GHOST_kDragnDropTypeUnknown; + m_refCounter++; +} + +GHOST_DropTargetX11::~GHOST_DropTargetX11() +{ + m_refCounter--; + if (m_refCounter == 0) { + Uninitialize(); + m_xdndInitialized = false; + GHOST_PRINT("XDND uninitialized\n"); + } +} + +/* based on a code from Saul Rennison + * http://stackoverflow.com/questions/2673207/c-c-url-decode-library */ + +typedef enum DecodeState_e { + STATE_SEARCH = 0, ///< searching for an ampersand to convert + STATE_CONVERTING ///< convert the two proceeding characters from hex +} DecodeState_e; + +void GHOST_DropTargetX11::UrlDecode(char *decodedOut, int bufferSize, const char *encodedIn) +{ + unsigned int i; + unsigned int len = strlen(encodedIn); + DecodeState_e state = STATE_SEARCH; + int j, asciiCharacter; + char tempNumBuf[3] = {0}; + bool bothDigits = true; + + memset(decodedOut, 0, bufferSize); + + for (i = 0; i < len; ++i) { + switch (state) { + case STATE_SEARCH: + if (encodedIn[i] != '%') { + strncat(decodedOut, &encodedIn[i], 1); + assert(strlen(decodedOut) < bufferSize); + break; + } + + // We are now converting + state = STATE_CONVERTING; + break; + + case STATE_CONVERTING: + bothDigits = true; + + // Create a buffer to hold the hex. For example, if %20, this + // buffer would hold 20 (in ASCII) + memset(tempNumBuf, 0, sizeof(tempNumBuf)); + + // Conversion complete (i.e. don't convert again next iter) + state = STATE_SEARCH; + + strncpy(tempNumBuf, &encodedIn[i], 2); + + // Ensure both characters are hexadecimal + + for (j = 0; j < 2; ++j) { + if (!isxdigit(tempNumBuf[j])) + bothDigits = false; + } + + if (!bothDigits) + break; + + // Convert two hexadecimal characters into one character + sscanf(tempNumBuf, "%x", &asciiCharacter); + + // Ensure we aren't going to overflow + assert(strlen(decodedOut) < bufferSize); + + // Concatenate this character onto the output + strncat(decodedOut, (char*)&asciiCharacter, 1); + + // Skip the next character + i++; + break; + } + } +} + +char *GHOST_DropTargetX11::FileUrlDecode(char *fileUrl) +{ + if(!strncpy(fileUrl, "file://", 7) == 0) { + /* assume one character of encoded URL can be expanded to 4 chars max */ + int decodedSize = 4 * strlen(fileUrl) + 1; + char *decodedPath = (char *)malloc(decodedSize); + + UrlDecode(decodedPath, decodedSize, fileUrl + 7); + + return decodedPath; + } + + return NULL; +} + +void *GHOST_DropTargetX11::getURIListGhostData(unsigned char *dropBuffer, int dropBufferSize) +{ + GHOST_TStringArray *strArray = NULL; + int totPaths = 0, curLength = 0; + + /* count total number of file pathes in buffer */ + for (int i = 0; i <= dropBufferSize; i++) { + if (dropBuffer[i] == 0 || dropBuffer[i] == '\n' || dropBuffer[i] == '\r') { + if (curLength) { + totPaths++; + curLength = 0; + } + } + else curLength++; + } + + strArray = (GHOST_TStringArray*)malloc(sizeof(GHOST_TStringArray)); + strArray->count = 0; + strArray->strings = (GHOST_TUns8**)malloc(totPaths*sizeof(GHOST_TUns8*)); + + curLength = 0; + for (int i = 0; i <= dropBufferSize; i++) { + if (dropBuffer[i] == 0 || dropBuffer[i] == '\n' || dropBuffer[i] == '\r') { + if (curLength) { + char *curPath = (char *)malloc(curLength + 1); + char *decodedPath; + + strncpy(curPath, (char*)dropBuffer + i - curLength, curLength); + curPath[curLength] = 0; + + decodedPath = FileUrlDecode(curPath); + if(decodedPath) { + strArray->strings[strArray->count] = (GHOST_TUns8*)decodedPath; + strArray->count++; + } + + free(curPath); + curLength = 0; + } + } + else curLength++; + } + + return strArray; +} + +void *GHOST_DropTargetX11::getGhostData(Atom dropType, unsigned char *dropBuffer, int dropBufferSize) +{ + void *data = NULL; + unsigned char *tmpBuffer = (unsigned char *)malloc(dropBufferSize + 1); + bool needsFree = true; + + /* ensure NULL-terminator */ + memcpy(tmpBuffer, dropBuffer, dropBufferSize); + tmpBuffer[dropBufferSize] = 0; + + if (dropType == dndTypeURIList) { + m_draggedObjectType = GHOST_kDragnDropTypeFilenames; + data = getURIListGhostData(tmpBuffer, dropBufferSize); + } + else if (dropType == dndTypeURL) { + /* need to be tested */ + char *decodedPath = FileUrlDecode((char *)tmpBuffer); + + if (decodedPath) { + m_draggedObjectType = GHOST_kDragnDropTypeString; + data = decodedPath; + } + } + else if (dropType == dndTypePlainText || dropType == dndTypeOctetStream) { + m_draggedObjectType = GHOST_kDragnDropTypeString; + data = tmpBuffer; + needsFree = false; + } + else { + m_draggedObjectType = GHOST_kDragnDropTypeUnknown; + } + + if (needsFree) + free(tmpBuffer); + + return data; +} + +bool GHOST_DropTargetX11::GHOST_HandleClientMessage(XEvent *event) +{ + Atom dropType; + unsigned char *dropBuffer; + int dropBufferSize, dropX, dropY; + + if (xdnd_get_drop(m_system->getXDisplay(), event, m_dndTypes, m_dndActions, + &dropBuffer, &dropBufferSize, &dropType, &dropX, &dropY)) + { + void *data = getGhostData(dropType, dropBuffer, dropBufferSize); + + if (data) + m_system->pushDragDropEvent(GHOST_kEventDraggingDropDone, m_draggedObjectType, m_window, dropX, dropY, data); + + free(dropBuffer); + + m_draggedObjectType = GHOST_kDragnDropTypeUnknown; + + return true; + } + + return false; +} diff --git a/intern/ghost/intern/GHOST_DropTargetX11.h b/intern/ghost/intern/GHOST_DropTargetX11.h new file mode 100644 index 00000000000..2b08b7ef59c --- /dev/null +++ b/intern/ghost/intern/GHOST_DropTargetX11.h @@ -0,0 +1,135 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2012 by the Blender Foundation. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): Sergey Sharybin. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file ghost/intern/GHOST_DropTargetWin32.h + * \ingroup GHOST + */ + +#ifndef _GHOST_DROP_TARGET_X11_H_ +#define _GHOST_DROP_TARGET_X11_H_ + +#include <GHOST_Types.h> +#include "GHOST_WindowX11.h" +#include "GHOST_SystemX11.h" + +#include "xdnd.h" + +class GHOST_DropTargetX11 +{ +public: + /** + * Constructor + * + * @param window The window to register as drop target. + * @param system The associated system. + */ + GHOST_DropTargetX11(GHOST_WindowX11 * window, GHOST_SystemX11 * system); + + /** + * Destructor + */ + ~GHOST_DropTargetX11(); + + /** + * Handler of ClientMessage X11 event + */ + bool GHOST_HandleClientMessage(XEvent *event); + + /** + * Get data to pass in event. + * It checks the type and calls specific functions for each type. + * @param dropType - type of dropped entity. + * @param dropBuffer - buffer returned from source application + * @param dropBufferSize - size of returned buffer + * @return Pointer to data. + */ + void *getGhostData(Atom dropType, unsigned char *dropBuffer, int dropBufferSize); + +private: + /* Internal helper functions */ + + /** + * Initiailize XDND and all related X atoms + */ + void Initialize(void); + + /** + * Uninitiailize XDND and all related X atoms + */ + void Uninitialize(void); + + /** + * Get data to be passed to event from text/uri-list mime type + * @param dropBuffer - buffer returned from source application + * @param dropBufferSize - size of dropped buffer + * @return pointer to newly created GHOST data + */ + void * getURIListGhostData(unsigned char *dropBuffer, int dropBufferSize); + + /** + * Decode URL (i.e. converts "file:///a%20b/test" to "file:///a b/test") + * @param decodedOut - buffer for decoded URL + * @param bufferSize - size of output buffer + * @param encodedIn - input encoded buffer to be decoded + */ + void UrlDecode(char *decodedOut, int bufferSize, const char *encodedIn); + + /** + * Fully decode file URL (i.e. converts "file:///a%20b/test" to "/a b/test") + * @param fileUrl - file path URL to be fully decoded + * @return decoded file path (resutl shold be free-d) + */ + char *FileUrlDecode(char *fileUrl); + + /* The associated GHOST_WindowWin32. */ + GHOST_WindowX11 * m_window; + /* The System. */ + GHOST_SystemX11 * m_system; + + /* Data type of the dragged object */ + GHOST_TDragnDropTypes m_draggedObjectType; + + /* is dnd stuff initialzied */ + static bool m_xdndInitialized; + + /* class holding internal stiff of xdnd library */ + static DndClass m_dndClass; + + /* list of supported types to eb draggeg into */ + static Atom * m_dndTypes; + + /* list of supported dran'n'drop actions */ + static Atom * m_dndActions; + + /* List of supported MIME types to be dragged into */ + static const char *m_dndMimeTypes[]; + + /* counter of references to global XDND structures */ + static int m_refCounter; +}; + +#endif // _GHOST_DROP_TARGET_X11_H_ diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 22c16009591..7261770771a 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -42,6 +42,8 @@ #include "GHOST_EventButton.h" #include "GHOST_EventWheel.h" #include "GHOST_DisplayManagerX11.h" +#include "GHOST_DropTargetX11.h" +#include "GHOST_EventDragnDrop.h" #ifdef WITH_INPUT_NDOF #include "GHOST_NDOFManagerX11.h" #endif @@ -709,8 +711,12 @@ GHOST_SystemX11::processEvent(XEvent *xe) } } } else { - /* Unknown client message, ignore */ + /* try to handle drag event (if there's no such events, GHOST_HandleClientMessage will return zero) */ + if (window->getDropTarget()->GHOST_HandleClientMessage(xe) == false) { + /* Unknown client message, ignore */ + } } + break; } @@ -1478,3 +1484,17 @@ void GHOST_SystemX11::putClipboard(GHOST_TInt8 *buffer, bool selection) const fprintf(stderr, "failed to own primary\n"); } } + +GHOST_TSuccess GHOST_SystemX11::pushDragDropEvent(GHOST_TEventType eventType, + GHOST_TDragnDropTypes draggedObjectType, + GHOST_IWindow* window, + int mouseX, int mouseY, + void* data) +{ + GHOST_SystemX11* system = ((GHOST_SystemX11*)getSystem()); + return system->pushEvent(new GHOST_EventDragnDrop(system->getMilliSeconds(), + eventType, + draggedObjectType, + window,mouseX,mouseY,data) + ); +} diff --git a/intern/ghost/intern/GHOST_SystemX11.h b/intern/ghost/intern/GHOST_SystemX11.h index 73d9c95e273..8e9131877d3 100644 --- a/intern/ghost/intern/GHOST_SystemX11.h +++ b/intern/ghost/intern/GHOST_SystemX11.h @@ -236,6 +236,18 @@ public: void putClipboard(GHOST_TInt8 *buffer, bool selection) const; /** + * Creates a drag'n'drop event and pushes it immediately onto the event queue. + * Called by GHOST_DropTargetX11 class. + * @param eventType The type of drag'n'drop event + * @param draggedObjectType The type object concerned (currently array of file names, string, ?bitmap) + * @param mouseX x mouse coordinate (in window coordinates) + * @param mouseY y mouse coordinate + * @param window The window on which the event occurred + * @return Indication whether the event was handled. + */ + static GHOST_TSuccess pushDragDropEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType,GHOST_IWindow* window, int mouseX, int mouseY, void* data); + + /** * @see GHOST_ISystem */ int toggleConsole(int action) { return 0; } diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp index 71e3f7b3340..ea99a9ea7b4 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cpp +++ b/intern/ghost/intern/GHOST_WindowX11.cpp @@ -32,6 +32,7 @@ #include "GHOST_WindowX11.h" #include "GHOST_SystemX11.h" +#include "GHOST_DropTargetX11.h" #include "STR_String.h" #include "GHOST_Debug.h" @@ -326,6 +327,10 @@ GHOST_WindowX11( } + /* initialize drop target for newly created window */ + m_dropTarget = new GHOST_DropTargetX11(this, m_system); + GHOST_PRINT("Set drop target\n"); + /* * One of the problem with WM-spec is that can't set a property * to a window that isn't mapped. That is why we can't "just @@ -1318,6 +1323,7 @@ GHOST_WindowX11:: } #endif + delete m_dropTarget; XDestroyWindow(m_display, m_window); XFree(m_visual); diff --git a/intern/ghost/intern/GHOST_WindowX11.h b/intern/ghost/intern/GHOST_WindowX11.h index f1146db50f8..f5cbceff7a4 100644 --- a/intern/ghost/intern/GHOST_WindowX11.h +++ b/intern/ghost/intern/GHOST_WindowX11.h @@ -45,6 +45,7 @@ class STR_String; class GHOST_SystemX11; +class GHOST_DropTargetX11; /** * X11 implementation of GHOST_IWindow. @@ -224,6 +225,9 @@ public: XIC getX11_XIC() { return m_xic; } #endif + GHOST_DropTargetX11* getDropTarget() + { return m_dropTarget; } + /* * Need this in case that we want start the window * in FullScree or Maximized state. @@ -361,6 +365,8 @@ private : /** Cache of XC_* ID's to XCursor structures */ std::map<unsigned int, Cursor> m_standard_cursors; + GHOST_DropTargetX11 * m_dropTarget; + #ifdef WITH_X11_XINPUT /* Tablet devices */ XTablet m_xtablet; |