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