diff --git a/IGraphics/IGraphics.h b/IGraphics/IGraphics.h
index e0f16ba25..e12144b25 100644
--- a/IGraphics/IGraphics.h
+++ b/IGraphics/IGraphics.h
@@ -865,6 +865,12 @@ class IGraphics
* @return /c true on success */
virtual bool SetFilePathInClipboard(const char* path) { return false; }
+ /** Initiate an drag-n-drop operation of an existing file, to be dropped outside of the current window
+ * @param path A CString that contains a path to a file on disk
+ * @param iconBounds The area where the icon should appear
+ * @return /c true on success */
+ virtual bool InitiateExternalFileDragDrop(const char* path, const IRECT& iconBounds) { return false; };
+
/** Call this if you modify control tool tips at runtime. \todo explain */
virtual void UpdateTooltips() = 0;
diff --git a/IGraphics/Platforms/IGraphicsMac.h b/IGraphics/Platforms/IGraphicsMac.h
index 39f1eea7e..9f33bb0c8 100644
--- a/IGraphics/Platforms/IGraphicsMac.h
+++ b/IGraphics/Platforms/IGraphicsMac.h
@@ -69,6 +69,8 @@ class IGraphicsMac final : public IGRAPHICS_DRAW_CLASS
bool SetTextInClipboard(const char* str) override;
bool SetFilePathInClipboard(const char* path) override;
+ bool InitiateExternalFileDragDrop(const char* path, const IRECT& iconBounds) override API_AVAILABLE(macos(10.13));
+
float MeasureText(const IText& text, const char* str, IRECT& bounds) const override;
EUIAppearance GetUIAppearance() const override;
diff --git a/IGraphics/Platforms/IGraphicsMac.mm b/IGraphics/Platforms/IGraphicsMac.mm
index 92730bf8b..dffcdc436 100644
--- a/IGraphics/Platforms/IGraphicsMac.mm
+++ b/IGraphics/Platforms/IGraphicsMac.mm
@@ -662,6 +662,28 @@ static int GetSystemVersion()
return (bool)success;
}
+bool IGraphicsMac::InitiateExternalFileDragDrop(const char* path, const IRECT& iconBounds)
+{
+ NSPasteboardItem* pasteboardItem = [[NSPasteboardItem alloc] init];
+ NSURL* fileURL = [NSURL fileURLWithPath: [NSString stringWithUTF8String: path]];
+ [pasteboardItem setString:fileURL.absoluteString forType:NSPasteboardTypeFileURL];
+
+ NSDraggingItem* draggingItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteboardItem];
+ NSRect draggingFrame = ToNSRect(this, iconBounds);
+ NSImage* iconImage = [[NSWorkspace sharedWorkspace] iconForFile:fileURL.path];
+ [iconImage setSize:NSMakeSize(64, 64)];
+ [draggingItem setDraggingFrame:draggingFrame contents: iconImage];
+
+ IGRAPHICS_VIEW* view = (IGRAPHICS_VIEW*) mView;
+ NSDraggingSession* draggingSession = [view beginDraggingSessionWithItems:@[draggingItem] event:[NSApp currentEvent] source: view];
+ draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
+ draggingSession.draggingFormation = NSDraggingFormationNone;
+
+ ReleaseMouseCapture();
+
+ return true;
+}
+
EUIAppearance IGraphicsMac::GetUIAppearance() const
{
if (@available(macOS 10.14, *)) {
diff --git a/IGraphics/Platforms/IGraphicsMac_view.h b/IGraphics/Platforms/IGraphicsMac_view.h
index 430a717a3..f4a011120 100644
--- a/IGraphics/Platforms/IGraphicsMac_view.h
+++ b/IGraphics/Platforms/IGraphicsMac_view.h
@@ -113,7 +113,7 @@ using namespace igraphics;
#define VIEW_BASE NSView
#endif
-@interface IGRAPHICS_VIEW : VIEW_BASE
+@interface IGRAPHICS_VIEW : VIEW_BASE
{
CVDisplayLinkRef mDisplayLink;
dispatch_source_t mDisplaySource;
@@ -174,6 +174,7 @@ using namespace igraphics;
//drag-and-drop
- (NSDragOperation) draggingEntered: (id ) sender;
- (BOOL) performDragOperation: (id) sender;
+- (NSDragOperation)draggingSession:(NSDraggingSession*) session sourceOperationMaskForDraggingContext:(NSDraggingContext)context;
//
- (void) setMouseCursor: (ECursor) cursorType;
@end
diff --git a/IGraphics/Platforms/IGraphicsMac_view.mm b/IGraphics/Platforms/IGraphicsMac_view.mm
index aa097d568..f97bb5a8e 100644
--- a/IGraphics/Platforms/IGraphicsMac_view.mm
+++ b/IGraphics/Platforms/IGraphicsMac_view.mm
@@ -1285,6 +1285,11 @@ - (BOOL) performDragOperation: (id) sender
return YES;
}
+- (NSDragOperation)draggingSession:(NSDraggingSession*) session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
+{
+ return NSDragOperationCopy;
+}
+
#ifdef IGRAPHICS_METAL
- (void) frameDidChange:(NSNotification*) pNotification
{
diff --git a/IGraphics/Platforms/IGraphicsWin.cpp b/IGraphics/Platforms/IGraphicsWin.cpp
index 84281cca5..e2433439b 100755
--- a/IGraphics/Platforms/IGraphicsWin.cpp
+++ b/IGraphics/Platforms/IGraphicsWin.cpp
@@ -16,6 +16,7 @@
#include "IPlugParameter.h"
#include "IGraphicsWin.h"
+#include "IGraphicsWin_dnd.h"
#include "IPopupMenuControl.h"
#include "IPlugPaths.h"
@@ -1953,6 +1954,30 @@ bool IGraphicsWin::SetFilePathInClipboard(const char* path)
return result;
}
+bool IGraphicsWin::InitiateExternalFileDragDrop(const char* path, const IRECT& /*iconBounds*/)
+{
+ using namespace DragAndDropHelpers;
+ OleInitialize(nullptr);
+
+ FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+
+ DataObject* dataObj = new DataObject(&format, path);
+ DropSource* dropSource = new DropSource();
+
+ DWORD dropEffect;
+ HRESULT ret = DoDragDrop(dataObj, dropSource, DROPEFFECT_COPY, &dropEffect);
+ bool success = SUCCEEDED(ret);
+
+ dataObj->Release();
+ dropSource->Release();
+
+ OleUninitialize();
+
+ ReleaseMouseCapture();
+
+ return success;
+}
+
static HFONT GetHFont(const char* fontName, int weight, bool italic, bool underline, DWORD quality = DEFAULT_QUALITY, bool enumerate = false)
{
HDC hdc = GetDC(NULL);
diff --git a/IGraphics/Platforms/IGraphicsWin.h b/IGraphics/Platforms/IGraphicsWin.h
index 8de807435..cf8035a9e 100644
--- a/IGraphics/Platforms/IGraphicsWin.h
+++ b/IGraphics/Platforms/IGraphicsWin.h
@@ -77,6 +77,8 @@ class IGraphicsWin final : public IGRAPHICS_DRAW_CLASS
bool SetTextInClipboard(const char* str) override;
bool SetFilePathInClipboard(const char* path) override;
+ bool InitiateExternalFileDragDrop(const char* path, const IRECT& iconBounds) override;
+
bool PlatformSupportsMultiTouch() const override;
static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
diff --git a/IGraphics/Platforms/IGraphicsWin_dnd.h b/IGraphics/Platforms/IGraphicsWin_dnd.h
new file mode 100644
index 000000000..70650667c
--- /dev/null
+++ b/IGraphics/Platforms/IGraphicsWin_dnd.h
@@ -0,0 +1,313 @@
+/*
+ ==============================================================================
+
+ This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
+
+ See LICENSE.txt for more info.
+
+ ==============================================================================
+*/
+
+#pragma once
+
+#include "IPlugLogger.h"
+
+BEGIN_IPLUG_NAMESPACE
+BEGIN_IGRAPHICS_NAMESPACE
+
+namespace DragAndDropHelpers
+{
+class DropSource : public IDropSource
+{
+public:
+ DropSource() {}
+ ~DropSource()
+ {
+ assert(mRefCount == 0);
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID refiid, void** resultHandle) override
+ {
+ if (refiid == IID_IDropSource)
+ {
+ *resultHandle = this;
+ AddRef();
+ return S_OK;
+ }
+ *resultHandle = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef () override { return ++mRefCount; }
+ ULONG STDMETHODCALLTYPE Release() override
+ {
+ int refCount = --mRefCount;
+ assert(refCount >= 0);
+ if (refCount <= 0)
+ {
+ delete this;
+ }
+ return refCount;
+ }
+
+ // IDropSource methods
+ HRESULT STDMETHODCALLTYPE QueryContinueDrag(BOOL escapeKeyPressed, DWORD grfKeyState) override
+ {
+ if (escapeKeyPressed == TRUE)
+ {
+ DBGMSG("DropSource: escapeKeyPressed, abort dnd\n");
+ return DRAGDROP_S_CANCEL;
+ }
+
+ if ((grfKeyState & MK_LBUTTON) == 0)
+ {
+ return DRAGDROP_S_DROP;
+ }
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE GiveFeedback(DWORD /*dwEffect*/) override
+ {
+ return DRAGDROP_S_USEDEFAULTCURSORS;
+ }
+private:
+ int mRefCount = 1;
+};
+
+static void deepFormatCopy(FORMATETC& dest, const FORMATETC& source)
+{
+ dest = source;
+ if (source.ptd != nullptr)
+ {
+ dest.ptd = (DVTARGETDEVICE*) CoTaskMemAlloc (sizeof (DVTARGETDEVICE));
+ if (dest.ptd != nullptr)
+ {
+ *(dest.ptd) = *(source.ptd);
+ }
+ }
+}
+
+// Enum class, seems necessary for some reason
+struct EnumFORMATETC final : public IEnumFORMATETC
+{
+ EnumFORMATETC (const FORMATETC* f) : mFormatPtr (f) {}
+ ~EnumFORMATETC() {}
+
+ ULONG STDMETHODCALLTYPE AddRef(void) { return ++mRefCount; }
+ ULONG STDMETHODCALLTYPE Release(void)
+ {
+ int refCount = --mRefCount;
+ assert(refCount >= 0);
+ if(refCount <= 0) {
+ delete this;
+ }
+ return refCount;
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID refiid, void **resultHandle)
+ {
+ if (refiid == IID_IEnumFORMATETC)
+ {
+ AddRef();
+ *resultHandle = this;
+ return S_OK;
+ }
+ *resultHandle = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ HRESULT Clone (IEnumFORMATETC** resultHandle) override
+ {
+ if (resultHandle == nullptr)
+ {
+ return E_POINTER;
+ }
+ EnumFORMATETC* copyObj = new EnumFORMATETC(mFormatPtr);
+ copyObj->mCounter = mCounter;
+ *resultHandle = copyObj;
+ return S_OK;
+ }
+
+ HRESULT Next (ULONG celt, FORMATETC *pFormat, ULONG* pceltFetched) override
+ {
+ if (pceltFetched != nullptr)
+ {
+ *pceltFetched = 0;
+ }
+ else if (celt != 1)
+ {
+ return S_FALSE;
+ }
+
+ if (mCounter == 0 && celt > 0 && pFormat != nullptr)
+ {
+ deepFormatCopy(pFormat[0], *mFormatPtr);
+ mCounter++;
+ if (pceltFetched != nullptr)
+ {
+ *pceltFetched = 1;
+ }
+ return S_OK;
+ }
+ return S_FALSE;
+ }
+
+ HRESULT Skip (ULONG celt) override
+ {
+ if (mCounter + (int) celt >= 1)
+ {
+ return S_FALSE;
+ }
+ mCounter += (int) celt;
+ return S_OK;
+ }
+
+ HRESULT Reset() override
+ {
+ mCounter = 0;
+ return S_OK;
+ }
+
+private:
+ int mRefCount = 1;
+ const FORMATETC* const mFormatPtr;
+ int mCounter = 0;
+};
+
+// Object that carries the DnD payload. TODO: also support text/string?
+class DataObject : public IDataObject
+{
+public:
+ DataObject(const FORMATETC* f, const char *filePath) : mFormatPtr (f)
+ {
+ mFilePath = filePath;
+ }
+
+ virtual ~DataObject()
+ {
+ assert(mRefCount == 0);
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID refiid, void **resultHandle)
+ {
+ if (refiid == IID_IDataObject || refiid == IID_IUnknown)
+ { // note: it seems that IUnknown must be supported here, don't know why
+ AddRef();
+ *resultHandle=this;
+ return S_OK;
+ }
+ *resultHandle = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef() { return ++mRefCount; }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ int refCount = --mRefCount;
+ assert(mRefCount>=0);
+ if (mRefCount<=0)
+ {
+ delete this;
+ }
+ return refCount;
+ }
+
+ bool acceptFormat(FORMATETC *f)
+ {
+ return (f->dwAspect & DVASPECT_CONTENT) && (f->tymed & TYMED_HGLOBAL) && (f->cfFormat == CF_HDROP);
+ }
+
+ HRESULT STDMETHODCALLTYPE GetData(FORMATETC *pFormat, STGMEDIUM *pMedium)
+ {
+ if (pFormat == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (acceptFormat(pFormat) == false)
+ {
+ return DV_E_FORMATETC;
+ }
+
+ UTF8AsUTF16 pathWide(mFilePath.c_str());
+ // GHND ensures that the memory is zeroed
+ HDROP hGlobal = (HDROP)GlobalAlloc(GHND, sizeof(DROPFILES) + (sizeof(wchar_t) * (pathWide.GetLength() + 2)));
+
+ if (!hGlobal)
+ {
+ DBGMSG("DataObject::GetData ERROR: GlobalAlloc returned null, aborting.\n");
+ return false;
+ }
+
+ DROPFILES* pDropFiles = (DROPFILES*) GlobalLock(hGlobal);
+ if (!pDropFiles)
+ {
+ DBGMSG("DataObject::GetData ERROR: GlobalLock returned null, aborting.\n");
+ return false;
+ }
+ // Populate the dropfile structure and copy the file path
+ pDropFiles->pFiles = sizeof(DROPFILES);
+ pDropFiles->fWide = true;
+ std::copy_n(pathWide.Get(), pathWide.GetLength(), reinterpret_cast(&pDropFiles[1]));
+
+ // not sure if this is necessary to set the medium but why not
+ pMedium->tymed = TYMED_HGLOBAL;
+ pMedium->hGlobal = hGlobal;
+ pMedium->pUnkForRelease = nullptr;
+
+ GlobalUnlock(hGlobal);
+
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryGetData( FORMATETC *pFormat )
+ {
+ if (pFormat == nullptr)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (acceptFormat(pFormat) == false)
+ {
+ return DV_E_FORMATETC;
+ }
+
+ return S_OK;
+ }
+
+ HRESULT GetCanonicalFormatEtc(FORMATETC*, FORMATETC* pFormatOut) override
+ {
+ pFormatOut->ptd = nullptr;
+ return E_NOTIMPL;
+ }
+
+ HRESULT EnumFormatEtc(DWORD direction, IEnumFORMATETC** resultHandle) override
+ {
+ if (!resultHandle)
+ {
+ return E_POINTER;
+ }
+ if (direction == DATADIR_GET)
+ {
+ *resultHandle = new EnumFORMATETC(mFormatPtr);
+ return S_OK;
+ }
+ *resultHandle = nullptr;
+ return E_NOTIMPL;
+ }
+
+ // unimplemented (unnecessary?) functions
+ HRESULT STDMETHODCALLTYPE GetDataHere(FORMATETC*, STGMEDIUM*) { return E_NOTIMPL; }
+ HRESULT STDMETHODCALLTYPE SetData(FORMATETC*, STGMEDIUM*, BOOL) { return E_NOTIMPL; }
+ HRESULT STDMETHODCALLTYPE DAdvise(FORMATETC*, DWORD, IAdviseSink*, DWORD*) { return E_NOTIMPL; }
+ HRESULT STDMETHODCALLTYPE DUnadvise(DWORD) { return E_NOTIMPL; }
+ HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA**) { return E_NOTIMPL; }
+private:
+ int mRefCount = 1;
+ std::string mFilePath;
+ const FORMATETC* const mFormatPtr;
+};
+} // end of DragAndDropHelpers namespace
+
+END_IGRAPHICS_NAMESPACE
+END_IPLUG_NAMESPACE
\ No newline at end of file