Skip to content

Commit 522fd99

Browse files
committed
Added file drag-and-drop to DesktopWindow
1 parent d1c30ff commit 522fd99

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

choc/gui/choc_DesktopWindow.h

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
109125
private:
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

256325
inline 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+
751907
private:
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
8611058
inline Bounds DesktopWindow::getBounds() { return pimpl->getBounds(); }
8621059
inline void DesktopWindow::centreWithSize (int w, int h) { pimpl->centreWithSize (w, h); }
8631060
inline 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

Comments
 (0)