From 89d0723ba3c100b8f2bc7740ad874205e2d97005 Mon Sep 17 00:00:00 2001 From: arturo Date: Wed, 10 Jul 2013 11:42:14 +0200 Subject: [PATCH] Initial drag and drop support. --- include/GLFW/glfw3.h | 30 ++++++++++ src/cocoa_window.m | 78 +++++++++++++++++++++++++- src/input.c | 17 ++++++ src/internal.h | 9 +++ src/win32_init.c | 5 +- src/win32_platform.h | 2 + src/win32_window.c | 37 ++++++++++++ src/x11_init.c | 17 +++++- src/x11_platform.h | 18 ++++++ src/x11_window.c | 130 ++++++++++++++++++++++++++++++++++++++++++- 10 files changed, 338 insertions(+), 5 deletions(-) diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index ed301997..b229bb9e 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -773,6 +773,20 @@ typedef void (* GLFWkeyfun)(GLFWwindow*,int,int,int,int); */ typedef void (* GLFWcharfun)(GLFWwindow*,unsigned int); + +/*! @brief The function signature for drop callbacks. + * + * This is the function signature for drop callbacks. + * + * @param[in] window The window that received the event. + * @param[in] string The string descriptor for the dropped object. + * + * @sa glfwSetDropCallback + * + * @ingroup input + */ +typedef void (* GLFWdropfun)(GLFWwindow*,const char*); + /*! @brief The function signature for monitor configuration callbacks. * * This is the function signature for monitor configuration callback functions. @@ -2014,6 +2028,22 @@ GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* window, GLFWcu */ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun cbfun); +/*! @brief Sets the drop callback. + * + * This function sets the drop callback of the specified window, which is + * called when an object is dropped over the window. + * + * + * @param[in] window The window whose callback to set. + * @param[in] cbfun The new drop callback, or `NULL` to remove the currently + * set callback. + * @return The previously set callback, or `NULL` if no callback was set or an + * error occurred. + * + * @ingroup input + */ +GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* window, GLFWdropfun cbfun); + /*! @brief Returns whether the specified joystick is present. * * This function returns whether the specified joystick is present. diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 4b873fe9..eca98995 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -413,6 +413,8 @@ static int translateKey(unsigned int key) @interface GLFWContentView : NSView { _GLFWwindow* window; + char * fileNamesForDrag; + int fileNamesSize; NSTrackingArea* trackingArea; } @@ -443,8 +445,15 @@ static int translateKey(unsigned int key) { window = initWindow; trackingArea = nil; - + + fileNamesForDrag = (char*)malloc(1024); + fileNamesSize = 1024; + [self updateTrackingAreas]; + + + [self registerForDraggedTypes:[NSArray arrayWithObjects: + NSFilenamesPboardType, nil]]; } return self; @@ -453,6 +462,7 @@ static int translateKey(unsigned int key) -(void)dealloc { [trackingArea release]; + free(fileNamesForDrag); [super dealloc]; } @@ -657,6 +667,72 @@ static int translateKey(unsigned int key) _glfwInputScroll(window, deltaX, deltaY); } + +// arturoc: this makes the cursor dissapear when the window is +// resized or received a drag operation +/*- (void)resetCursorRects +{ + [self discardCursorRects]; + [self addCursorRect:[self bounds] cursor:_glfw.ns.cursor]; +}*/ + +- (NSDragOperation)draggingEntered:(id )sender +{ + if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) + == NSDragOperationGeneric) { + + [self setNeedsDisplay:YES]; + + return NSDragOperationGeneric; + + } + + return NSDragOperationNone; +} + +- (BOOL)prepareForDragOperation:(id )sender { + [self setNeedsDisplay:YES]; + return YES; +} + +- (BOOL)performDragOperation:(id )sender { + NSPasteboard *zPasteboard = [sender draggingPasteboard]; + NSArray *files = [zPasteboard propertyListForType:NSFilenamesPboardType]; + + // set the first char to 0 so strcat + // starts to add from the beginning + fileNamesForDrag[0] = 0; + + int dragX = [sender draggingLocation].x; + int dragY = [sender draggingLocation].y; + + int dragSize = 1; + if ([files count]) { + NSEnumerator *filenameEnum = [files objectEnumerator]; + NSString *name; + while (name = [filenameEnum nextObject]) { + dragSize += [name length]+1; + if (dragSize > fileNamesSize){ + fileNamesSize *= 2; + fileNamesForDrag = realloc(fileNamesForDrag, fileNamesSize); + } + strcat(fileNamesForDrag, [name UTF8String]); + strcat(fileNamesForDrag, "\n"); + } + } + + int height; + _glfwPlatformGetWindowSize(window, NULL, &height); + _glfwInputCursorMotion(window, dragX, height-dragY); + _glfwInputDrop(window, fileNamesForDrag); + + return YES; +} + +- (void)concludeDragOperation:(id )sender { + [self setNeedsDisplay:YES]; +} + @end diff --git a/src/input.c b/src/input.c index 9e628f06..b7e090c6 100644 --- a/src/input.c +++ b/src/input.c @@ -211,6 +211,12 @@ void _glfwInputCursorEnter(_GLFWwindow* window, int entered) window->callbacks.cursorEnter((GLFWwindow*) window, entered); } +void _glfwInputDrop(_GLFWwindow* window, const char* dropString){ + + if (window->callbacks.drop) + window->callbacks.drop((GLFWwindow*) window, dropString); +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// @@ -394,3 +400,14 @@ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* handle, return cbfun; } +GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* handle, GLFWdropfun cbfun) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + GLFWdropfun previous; + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + + previous = window->callbacks.drop; + window->callbacks.drop = cbfun; + return previous; +} diff --git a/src/internal.h b/src/internal.h index ea95b083..08f849de 100644 --- a/src/internal.h +++ b/src/internal.h @@ -239,6 +239,7 @@ struct _GLFWwindow GLFWscrollfun scroll; GLFWkeyfun key; GLFWcharfun character; + GLFWdropfun drop; } callbacks; // This is defined in the window API's platform.h @@ -678,6 +679,14 @@ void _glfwInputMonitorChange(void); */ void _glfwInputError(int error, const char* format, ...); +/*! @brief Notifies dropped object over window. + * @param[in] window The window that received the event. + * @param[in] dropString The string descriptor of the dropped object + * description. + * @ingroup event + */ +void _glfwInputDrop(_GLFWwindow* window, const char* dropString); + //======================================================================== // Utility functions diff --git a/src/win32_init.c b/src/win32_init.c index aae46a9b..d7ed7938 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -98,7 +98,7 @@ static GLboolean initLibraries(void) _glfw.win32.dwmapi.DwmIsCompositionEnabled = (DWMISCOMPOSITIONENABLED_T) GetProcAddress(_glfw.win32.dwmapi.instance, "DwmIsCompositionEnabled"); } - + return GL_TRUE; } @@ -214,6 +214,9 @@ int _glfwPlatformInit(void) if (!_glfwInitContextAPI()) return GL_FALSE; + + _glfw.win32.dropString = (char*)malloc(1000); + _glfw.win32.dropStringSize = 1000; _glfwInitTimer(); _glfwInitJoysticks(); diff --git a/src/win32_platform.h b/src/win32_platform.h index 11d774b7..723da793 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -169,6 +169,8 @@ typedef struct _GLFWlibraryWin32 ATOM classAtom; DWORD foregroundLockTimeout; char* clipboardString; + char* dropString; + int dropStringSize; // Timer data struct { diff --git a/src/win32_window.c b/src/win32_window.c index aa2aa4bb..08108ed0 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -29,7 +29,9 @@ #include #include +#include #include +#include #define _GLFW_KEY_INVALID -2 @@ -747,6 +749,39 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, // TODO: Restore vsync if compositing was disabled break; } + case WM_DROPFILES: + { + TCHAR szName[MAX_PATH]; + HDROP hDrop = (HDROP)wParam; + POINT pt; + int numFiles = DragQueryFile(hDrop, 0xFFFFFFFF, szName, MAX_PATH); + int currentSize = 1; + int i; + char* utf8str; + DragQueryPoint(hDrop, &pt); + + // Move the mouse to the position of the drop + _glfwInputCursorMotion(window,pt.x,pt.y); + + memset(_glfw.win32.dropString, 0, _glfw.win32.dropStringSize); + for(i = 0; i < numFiles; i++) + { + DragQueryFile(hDrop, i, szName, MAX_PATH); + utf8str = _glfwCreateUTF8FromWideString((const wchar_t*)szName); + currentSize += strlen(utf8str); + if(_glfw.win32.dropStringSize < currentSize){ + _glfw.win32.dropStringSize *= 2; + _glfw.win32.dropString = (char*)realloc(_glfw.win32.dropString,_glfw.win32.dropStringSize); + } + strcat(_glfw.win32.dropString, utf8str); + strcat(_glfw.win32.dropString, "\n"); + free(utf8str); + } + + _glfwInputDrop(window,_glfw.win32.dropString); + DragFinish(hDrop); + break; + } } return DefWindowProc(hWnd, uMsg, wParam, lParam); @@ -865,6 +900,8 @@ static int createWindow(_GLFWwindow* window, window); // Pass object to WM_CREATE free(wideTitle); + + DragAcceptFiles(window->win32.handle, TRUE); if (!window->win32.handle) { diff --git a/src/x11_init.c b/src/x11_init.c index 5b477f04..22097afb 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -32,6 +32,7 @@ #include #include #include +#include // Translate an X11 key code to a GLFW key code. @@ -394,7 +395,6 @@ static void detectEWMH(void) (unsigned char**) &supportedAtoms); // See which of the atoms we support that are supported by the WM - _glfw.x11.NET_WM_STATE = getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE"); _glfw.x11.NET_WM_STATE_FULLSCREEN = @@ -539,6 +539,21 @@ static GLboolean initExtensions(void) _glfw.x11.SAVE_TARGETS = XInternAtom(_glfw.x11.display, "SAVE_TARGETS", False); + // Find or create drag and drop atoms + + //Atoms for Xdnd + _glfw.x11.XdndAware = XInternAtom(_glfw.x11.display, "XdndAware", True); + _glfw.x11.XdndEnter = XInternAtom(_glfw.x11.display, "XdndEnter", True); + _glfw.x11.XdndPosition = XInternAtom(_glfw.x11.display, "XdndPosition", True); + _glfw.x11.XdndStatus = XInternAtom(_glfw.x11.display, "XdndStatus", True); + _glfw.x11.XdndActionCopy = XInternAtom(_glfw.x11.display, "XdndActionCopy", True); + _glfw.x11.XdndDrop = XInternAtom(_glfw.x11.display, "XdndDrop", True); + _glfw.x11.XdndLeave = XInternAtom(_glfw.x11.display, "XdndLeave", True); + _glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", True); + _glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", True); + + + return GL_TRUE; } diff --git a/src/x11_platform.h b/src/x11_platform.h index e1e1e7cc..72db890e 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -119,6 +119,24 @@ typedef struct _GLFWlibraryX11 Atom NET_ACTIVE_WINDOW; Atom MOTIF_WM_HINTS; + // Atoms for Xdnd + Atom XdndAware; + Atom XdndEnter; + Atom XdndPosition; + Atom XdndStatus; + Atom XdndActionCopy; + Atom XdndDrop; + Atom XdndLeave; + Atom XdndFinished; + Atom XdndSelection; + struct{ + Window sourceWindow; + char* string; + char* type1; + char* type2; + char* type3; + } xdnd; + // Selection atoms Atom TARGETS; Atom MULTIPLE; diff --git a/src/x11_window.c b/src/x11_window.c index 0dd5f30e..78082cd0 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -306,6 +306,14 @@ static GLboolean createWindow(_GLFWwindow* window, XISelectEvents(_glfw.x11.display, window->x11.handle, &eventmask, 1); } + // Enable Xdnd + if(_glfw.x11.XdndAware!=None) + { + //Announce XDND support + Atom version=5; + XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*)&version, 1); + } + _glfwPlatformSetWindowTitle(window, wndconfig->title); XRRSelectInput(_glfw.x11.display, window->x11.handle, @@ -494,6 +502,7 @@ static void leaveFullscreenMode(_GLFWwindow* window) } } + // Process the specified X event // static void processEvent(XEvent *event) @@ -703,10 +712,128 @@ static void processEvent(XEvent *event) False, SubstructureNotifyMask | SubstructureRedirectMask, event); + + } + else if(event->xclient.message_type == _glfw.x11.XdndEnter) + { + // Xdnd Enter: the drag&drop event has started in the window, + // we could be getting the type and possible conversions here + // but since we use always string conversion we don't need + // it + } + else if(event->xclient.message_type == _glfw.x11.XdndDrop) + { + // Xdnd Drop: The drag&drop event has finished dropping on + // the window, ask to convert the selection + _glfw.x11.xdnd.sourceWindow = event->xclient.data.l[0]; + XConvertSelection(_glfw.x11.display, + _glfw.x11.XdndSelection, + _glfw.x11.UTF8_STRING, + _glfw.x11.XdndSelection, + window->x11.handle, CurrentTime); + + } + else if(event->xclient.message_type == _glfw.x11.XdndLeave) + { + + } + else if(event->xclient.message_type == _glfw.x11.XdndPosition) + { + // Xdnd Position: get coordinates of the mouse inside the window + // and update the mouse position + int absX = (event->xclient.data.l[2]>>16) & 0xFFFF; + int absY = (event->xclient.data.l[2]) & 0xFFFF; + int x; + int y; + + _glfwPlatformGetWindowPos(window,&x,&y); + + _glfwInputCursorMotion(window,absX-x,absY-y); + + // Xdnd: reply with an XDND status message + XClientMessageEvent m; + memset(&m, sizeof(m), 0); + m.type = ClientMessage; + m.display = event->xclient.display; + m.window = event->xclient.data.l[0]; + m.message_type = _glfw.x11.XdndStatus; + m.format=32; + m.data.l[0] = window->x11.handle; + m.data.l[1] = 1; // Always accept the dnd with no rectangle + m.data.l[2] = 0; // Specify an empty rectangle + m.data.l[3] = 0; + m.data.l[4] = _glfw.x11.XdndActionCopy; // We only accept copying + + XSendEvent(_glfw.x11.display, event->xclient.data.l[0], False, NoEventMask, (XEvent*)&m); + XFlush(_glfw.x11.display); } break; } + case SelectionNotify: + { + if(event->xselection.property != None) + { + // Xdnd: got a selection notification from the conversion + // we asked for, get the data and finish the d&d event + char* data; + free(_glfw.x11.xdnd.string); + _glfw.x11.xdnd.string = NULL; + int result = _glfwGetWindowProperty(event->xselection.requestor, + event->xselection.property, + event->xselection.target, + (unsigned char**) &data); + + if(result){ + // nautilus seems to add a \r at the end of the paths + // remove it so paths can be directly used + _glfw.x11.xdnd.string = malloc(strlen(data)); + char *to = _glfw.x11.xdnd.string; + const char *from = data; + const char *current = strchr(from, '\r'); + while(current) + { + int charsToCopy = current - from; + memcpy(to, from, (size_t)charsToCopy); + to += charsToCopy; + + from = current+1; + current = strchr(from, '\r'); + } + + size_t remaining = strlen(from); + + memcpy(to, from, remaining); + to += remaining; + *to = 0; + } + + XClientMessageEvent m; + memset(&m, sizeof(m), 0); + m.type = ClientMessage; + m.display = _glfw.x11.display; + m.window = _glfw.x11.xdnd.sourceWindow; + m.message_type = _glfw.x11.XdndFinished; + m.format=32; + m.data.l[0] = window->x11.handle; + m.data.l[1] = result; + m.data.l[2] = _glfw.x11.XdndActionCopy; //We only ever copy. + + // Reply that all is well. + XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.sourceWindow, False, NoEventMask, (XEvent*)&m); + + XSync(_glfw.x11.display, False); + + XFree(data); + + if(result) + { + _glfwInputDrop(window,_glfw.x11.xdnd.string); + + } + } + break; + } case MapNotify: { @@ -897,7 +1024,7 @@ unsigned long _glfwGetWindowProperty(Window window, &bytesAfter, value); - if (actualType != type) + if (type != AnyPropertyType && actualType != type) return 0; return itemCount; @@ -1001,7 +1128,6 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) if (child) { int left, top; - XTranslateCoordinates(_glfw.x11.display, window->x11.handle, child, 0, 0, &left, &top, &child);