@@ -106,6 +106,22 @@ struct DesktopWindow
106106 // / An optional callback that will be called when the parent window is closed
107107 std::function<void ()> windowClosed;
108108
109+ // / Information about a file drop event - see setFileDropCallback()
110+ struct FileDropEvent
111+ {
112+ std::vector<std::string> filePaths;
113+ float x, y;
114+ };
115+
116+ // / This callback should return true if the operation was handled, or false
117+ // / to allow it to be passed on to other handlers.
118+ using FileDropCallback = std::function<bool (const FileDropEvent&)>;
119+
120+ // / Call this to enable file drag-and-drop support, and to set a callback
121+ // / that will be called when files are dropped onto the window. Pass an
122+ // / empty function to disable drag-and-drop.
123+ void setFileDropCallback (FileDropCallback);
124+
109125private:
110126 struct Pimpl ;
111127 std::unique_ptr<Pimpl> pimpl;
@@ -247,10 +263,63 @@ struct choc::ui::DesktopWindow::Pimpl
247263 return { x, y, w, h };
248264 }
249265
266+ void setFileDropCallback (FileDropCallback handler)
267+ {
268+ if (handler)
269+ {
270+ fileDropCallback = std::move (handler);
271+
272+ gtk_drag_dest_set (window, GTK_DEST_DEFAULT_ALL, nullptr , 0 , GDK_ACTION_COPY);
273+ gtk_drag_dest_add_uri_targets (window);
274+
275+ g_signal_connect (window, " drag-data-received" ,
276+ G_CALLBACK (+[](GtkWidget*, GdkDragContext* context,
277+ gint x, gint y, GtkSelectionData* selectionData,
278+ guint /* info*/ , guint time, gpointer userData)
279+ {
280+ static_cast <Pimpl*> (userData)
281+ ->onDragDataReceived (context, x, y, time, selectionData);
282+ }), this );
283+ }
284+ else
285+ {
286+ fileDropCallback = {};
287+ gtk_drag_dest_unset (window);
288+ }
289+ }
290+
291+ void onDragDataReceived (GdkDragContext* context, gint x, gint y,
292+ guint time, GtkSelectionData* selectionData)
293+ {
294+ FileDropEvent e;
295+ e.x = static_cast <float > (x);
296+ e.y = static_cast <float > (y);
297+
298+ if (const auto uris = gtk_selection_data_get_uris (selectionData))
299+ {
300+ for (auto uri = uris; *uri != nullptr ; ++uri)
301+ {
302+ if (auto filename = g_filename_from_uri (*uri, nullptr , nullptr ))
303+ {
304+ e.filePaths .push_back (filename);
305+ g_free (filename);
306+ }
307+ }
308+
309+ g_strfreev (uris);
310+ }
311+
312+ if (fileDropCallback)
313+ fileDropCallback (e);
314+
315+ gtk_drag_finish (context, TRUE , FALSE , time);
316+ }
317+
250318 DesktopWindow& owner;
251319 GtkWidget* window = {};
252320 GtkWidget* content = {};
253321 unsigned long destroyHandlerID = 0 ;
322+ FileDropCallback fileDropCallback;
254323};
255324
256325inline void choc::ui::setWindowsDPIAwareness () {}
@@ -387,6 +456,62 @@ struct DesktopWindow::Pimpl
387456 (int ) contentRect.size .height };
388457 }
389458
459+ void setFileDropCallback (FileDropCallback handler)
460+ {
461+ CHOC_AUTORELEASE_BEGIN
462+
463+ if (handler)
464+ {
465+ fileDropCallback = std::move (handler);
466+ auto types = objc::callClass<id> (" NSArray" , " arrayWithObject:" , objc::getNSString (" NSFilenamesPboardType" ));
467+ objc::call<void > (window, " registerForDraggedTypes:" , types);
468+ }
469+ else
470+ {
471+ fileDropCallback = {};
472+ objc::call<void > (window, " unregisterDraggedTypes" );
473+ }
474+
475+ CHOC_AUTORELEASE_END
476+ }
477+
478+ long draggingEntered ()
479+ {
480+ return fileDropCallback ? NSDragOperationCopy : NSDragOperationNone;
481+ }
482+
483+ BOOL performDragOperation (id sender)
484+ {
485+ if (fileDropCallback == nullptr )
486+ return NO;
487+
488+ FileDropEvent e;
489+
490+ {
491+ CHOC_AUTORELEASE_BEGIN
492+ auto pboard = objc::call<id> (sender, " draggingPasteboard" );
493+ auto files = objc::call<id> (pboard, " propertyListForType:" , objc::getNSString (" NSFilenamesPboardType" ));
494+
495+ auto count = objc::call<long > (files, " count" );
496+
497+ for (long i = 0 ; i < count; ++i)
498+ {
499+ auto nsPath = objc::call<id>(files, " objectAtIndex:" , i);
500+ auto utf8Path = objc::call<const char *>(nsPath, " UTF8String" );
501+ e.filePaths .push_back (utf8Path);
502+ }
503+
504+ struct NSPoint { double x = 0 , y = 0 ; };
505+ auto point = objc::call<NSPoint> (sender, " draggingLocation" );
506+ e.x = static_cast <float > (point.x );
507+ e.y = static_cast <float > (point.y );
508+
509+ CHOC_AUTORELEASE_END
510+ }
511+
512+ return fileDropCallback (e) ? YES : NO;
513+ }
514+
390515 static Pimpl& getPimplFromContext (id self)
391516 {
392517 auto view = (CHOC_OBJC_CAST_BRIDGED Pimpl*) objc_getAssociatedObject (self, " choc_window" );
@@ -402,6 +527,7 @@ struct DesktopWindow::Pimpl
402527
403528 DesktopWindow& owner;
404529 id window = {}, delegate = {}, intermediateView = {};
530+ FileDropCallback fileDropCallback;
405531
406532 struct DelegateClass
407533 {
@@ -412,6 +538,9 @@ struct DesktopWindow::Pimpl
412538 if (auto * p = objc_getProtocol (" NSWindowDelegate" ))
413539 class_addProtocol (delegateClass, p);
414540
541+ if (auto * p = objc_getProtocol (" NSDraggingDestination" ))
542+ class_addProtocol (delegateClass, p);
543+
415544 class_addMethod (delegateClass, sel_registerName (" windowShouldClose:" ),
416545 (IMP) (+[](id self, SEL, id) -> BOOL
417546 {
@@ -443,6 +572,20 @@ struct DesktopWindow::Pimpl
443572 (IMP) (+[](id, SEL, id) -> BOOL { return 0 ; }),
444573 " c@:@" );
445574
575+ class_addMethod (delegateClass, sel_registerName (" draggingEntered:" ),
576+ (IMP) (+[](id self, SEL, id) -> long
577+ {
578+ return getPimplFromContext (self).draggingEntered ();
579+ }),
580+ " l@:@" );
581+
582+ class_addMethod (delegateClass, sel_registerName (" performDragOperation:" ),
583+ (IMP) (+[](id self, SEL, id sender) -> BOOL
584+ {
585+ return getPimplFromContext (self).performDragOperation (sender);
586+ }),
587+ " B@:@" );
588+
446589 objc_registerClassPair (delegateClass);
447590 }
448591
@@ -460,6 +603,8 @@ struct DesktopWindow::Pimpl
460603 static constexpr long NSWindowStyleMaskClosable = 2 ;
461604 static constexpr long NSBackingStoreBuffered = 2 ;
462605 static constexpr long NSApplicationActivationPolicyRegular = 0 ;
606+ static constexpr long NSDragOperationNone = 0 ;
607+ static constexpr long NSDragOperationCopy = 1 ;
463608
464609 static objc::CGSize createCGSize (double w, double h) { return { (objc::CGFloat) w, (objc::CGFloat) h }; }
465610 static objc::CGRect createCGRect (choc::ui::Bounds b) { return { { (objc::CGFloat) b.x , (objc::CGFloat) b.y }, { (objc::CGFloat) b.width , (objc::CGFloat) b.height } }; }
@@ -748,11 +893,23 @@ struct DesktopWindow::Pimpl
748893 return scaleBounds ({ r.left , r.top , r.right - r.left , r.bottom - r.top }, scale);
749894 }
750895
896+ void setFileDropCallback (FileDropCallback handler)
897+ {
898+ if (auto shell32 = choc::file::DynamicLibrary (" shell32.dll" ))
899+ {
900+ typedef UINT (WINAPI *DragAcceptFilesFunc)(HWND, BOOL);
901+ auto dragAcceptFiles = reinterpret_cast <DragAcceptFilesFunc> (shell32.findFunction (" DragAcceptFiles" ));
902+ dragAcceptFiles (hwnd, handler ? TRUE : FALSE );
903+ fileDropCallback = std::move (handler);
904+ }
905+ }
906+
751907private:
752908 DesktopWindow& owner;
753909 HWNDHolder hwnd;
754910 POINT minimumSize = {}, maximumSize = {};
755911 WindowClass windowClass { L" CHOCWindow" , (WNDPROC) wndProc };
912+ FileDropCallback fileDropCallback;
756913
757914 Bounds scaleBounds (Bounds b, double scale)
758915 {
@@ -805,6 +962,45 @@ struct DesktopWindow::Pimpl
805962 owner.windowResized ();
806963 }
807964
965+ bool handleFileDrop (HANDLE hdrop)
966+ {
967+ typedef UINT (WINAPI *DragQueryFileWFunc)(HANDLE, UINT, LPWSTR, UINT);
968+ typedef BOOL (WINAPI *DragQueryPointFunc)(HANDLE, LPPOINT);
969+ typedef BOOL (WINAPI *DragFinishFunc)(HANDLE);
970+
971+ if (fileDropCallback)
972+ {
973+ if (auto shell32 = choc::file::DynamicLibrary (" shell32.dll" ))
974+ {
975+ auto dragQueryFileW = reinterpret_cast <DragQueryFileWFunc> (shell32.findFunction (" DragQueryFileW" ));
976+ auto dragQueryPoint = reinterpret_cast <DragQueryPointFunc> (shell32.findFunction (" DragQueryPoint" ));
977+ auto dragFinish = reinterpret_cast <DragFinishFunc> (shell32.findFunction (" DragFinish" ));
978+
979+ POINT pt;
980+ dragQueryPoint (hdrop, &pt);
981+ FileDropEvent e;
982+ e.x = static_cast <float > (pt.x );
983+ e.y = static_cast <float > (pt.y );
984+
985+ auto numFiles = dragQueryFileW (hdrop, 0xffffffff , nullptr , 0 );
986+
987+ for (UINT i = 0 ; i < numFiles; ++i)
988+ {
989+ auto size = dragQueryFileW (hdrop, i, nullptr , 0 );
990+ std::wstring path;
991+ path.resize (size + 2 );
992+ dragQueryFileW (hdrop, i, path.data (), size + 1 );
993+ e.filePaths .push_back (createUTF8FromUTF16 (path));
994+ }
995+
996+ dragFinish (hdrop);
997+ return fileDropCallback (e);
998+ }
999+ }
1000+
1001+ return false ;
1002+ }
1003+
8081004 static void enableNonClientDPIScaling (HWND h)
8091005 {
8101006 if (auto fn = getUser32Function<BOOL (__stdcall*)(HWND)> (" EnableNonClientDpiScaling" ))
@@ -829,6 +1025,7 @@ struct DesktopWindow::Pimpl
8291025 case WM_SIZE: if (auto w = getPimpl (h)) w->handleSizeChange (); break ;
8301026 case WM_CLOSE: if (auto w = getPimpl (h)) w->handleClose (); return 0 ;
8311027 case WM_GETMINMAXINFO: if (auto w = getPimpl (h)) w->getMinMaxInfo (*(LPMINMAXINFO) lp); return 0 ;
1028+ case WM_DROPFILES: if (auto w = getPimpl (h)) if (w->handleFileDrop ((HANDLE) wp)) return 0 ; break ;
8321029 default : break ;
8331030 }
8341031
@@ -861,6 +1058,7 @@ inline void DesktopWindow::setBounds (Bounds b) { pim
8611058inline Bounds DesktopWindow::getBounds () { return pimpl->getBounds (); }
8621059inline void DesktopWindow::centreWithSize (int w, int h) { pimpl->centreWithSize (w, h); }
8631060inline void DesktopWindow::toFront () { pimpl->toFront (); }
1061+ inline void DesktopWindow::setFileDropCallback (FileDropCallback h) { pimpl->setFileDropCallback (std::move (h)); }
8641062
8651063} // namespace choc::ui
8661064
0 commit comments