diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 1b5a8e56ad..64d89b4a52 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -6,7 +6,29 @@ Develop Change ------ -AudioProcessor::getTailLengthSeconds can now return infinity for VST/VST3/AU/AUv3 +DragAndDropContainer::performExternalDragDropOfFiles() and ::performExternalDragDropOfText() +are now asynchronous on Windows. + +Possible Issues +--------------- +Code that previously relied on these operations being synchronous and blocking until +completion will no longer work as the methods will return immediately and run +asynchronously. + +Workaround +---------- +Use the callback argument that has been added to these methods to register a lambda +that will be called when the operation has been completed. + +Rationale +--------- +The behaviour of these methods is now consistent across all platforms and the method +no longer blocks the message thread on Windows. + + +Change +------ +AudioProcessor::getTailLengthSeconds can now return infinity for VST/VST3/AU/AUv3. Possible Issues --------------- @@ -20,7 +42,6 @@ a buffer. Rationale --------- - Before this change there was no way for a JUCE plug-in to report an infinite tail time. diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h index 2749448142..94b7b29f6a 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h @@ -153,42 +153,46 @@ public: //============================================================================== - /** This performs a synchronous drag-and-drop of a set of files to some external + /** This performs an asynchronous drag-and-drop of a set of files to some external application. You can call this function in response to a mouseDrag callback, and it will - block, running its own internal message loop and tracking the mouse, while it - uses a native operating system drag-and-drop operation to move or copy some + use a native operating system drag-and-drop operation to move or copy some files to another application. @param files a list of filenames to drag @param canMoveFiles if true, the app that receives the files is allowed to move the files to a new location (if this is appropriate). If false, the receiver is expected to make a copy of them. - @param sourceComponent Normally, JUCE will assume that the component under the mouse is the source component + @param sourceComponent normally, JUCE will assume that the component under the mouse is the source component of the drag, but you can use this parameter to override this. - @returns true if the files were successfully dropped somewhere, or false if it - was interrupted + @param callback an optional completion callback that will be called when the operation has ended. + + @returns true if the drag operation was successfully started, or false if it failed for some reason + @see performExternalDragDropOfText */ static bool performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles, - Component* sourceComponent = nullptr); + Component* sourceComponent = nullptr, + std::function callback = nullptr); - /** This performs a synchronous drag-and-drop of a block of text to some external + /** This performs an asynchronous drag-and-drop of a block of text to some external application. You can call this function in response to a mouseDrag callback, and it will - block, running its own internal message loop and tracking the mouse, while it - uses a native operating system drag-and-drop operation to move or copy some + use a native operating system drag-and-drop operation to move or copy some text to another application. @param text the text to copy @param sourceComponent Normally, JUCE will assume that the component under the mouse is the source component of the drag, but you can use this parameter to override this. - @returns true if the text was successfully dropped somewhere, or false if it - was interrupted + @param callback an optional completion callback that will be called when the operation has ended. + + @returns true if the drag operation was successfully started, or false if it failed for some reason + @see performExternalDragDropOfFiles */ - static bool performExternalDragDropOfText (const String& text, Component* sourceComponent = nullptr); + static bool performExternalDragDropOfText (const String& text, Component* sourceComponent = nullptr, + std::function callback = nullptr); protected: /** Override this if you want to be able to perform an external drag of a set of files diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropTarget.h b/modules/juce_gui_basics/mouse/juce_DragAndDropTarget.h index 11d72dbc3a..2b7712a7f4 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropTarget.h +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropTarget.h @@ -50,9 +50,7 @@ public: virtual ~DragAndDropTarget() {} //============================================================================== - /** Contains details about the source of a drag-and-drop operation. - The contents of this - */ + /** Contains details about the source of a drag-and-drop operation. */ class JUCE_API SourceDetails { public: diff --git a/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/modules/juce_gui_basics/native/juce_android_Windowing.cpp index 34b71143bc..c085d6a81d 100644 --- a/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -1077,13 +1077,16 @@ void MouseCursor::showInAllWindows() const {} //============================================================================== bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& /*files*/, const bool /*canMove*/, - Component* /*srcComp*/) + Component* /*srcComp*/, std::function /*callback*/) { + jassertfalse; // no such thing on Android! return false; } -bool DragAndDropContainer::performExternalDragDropOfText (const String& /*text*/, Component* /*srcComp*/) +bool DragAndDropContainer::performExternalDragDropOfText (const String& /*text*/, Component* /*srcComp*/, + std::function /*callback*/) { + jassertfalse; // no such thing on Android! return false; } diff --git a/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/modules/juce_gui_basics/native/juce_ios_Windowing.mm index ce7135f257..5aa02073d5 100644 --- a/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ b/modules/juce_gui_basics/native/juce_ios_Windowing.mm @@ -640,13 +640,13 @@ int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType /*i } //============================================================================== -bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*) +bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*, std::function) { jassertfalse; // no such thing on iOS! return false; } -bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*) +bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*, std::function) { jassertfalse; // no such thing on iOS! return false; diff --git a/modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp b/modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp index 20f887abf3..6f269c1d97 100644 --- a/modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp @@ -2471,15 +2471,15 @@ public: } } - bool externalDragTextInit (const String& text) + bool externalDragTextInit (const String& text, std::function cb) { if (dragState->dragging) return false; - return externalDragInit (true, text); + return externalDragInit (true, text, cb); } - bool externalDragFileInit (const StringArray& files, bool /*canMoveFiles*/) + bool externalDragFileInit (const StringArray& files, bool /*canMoveFiles*/, std::function cb) { if (dragState->dragging) return false; @@ -2494,7 +2494,7 @@ public: uriList.add ("file://" + f); } - return externalDragInit (false, uriList.joinIntoString ("\r\n")); + return externalDragInit (false, uriList.joinIntoString ("\r\n"), cb); } //============================================================================== @@ -3183,6 +3183,7 @@ private: Rectangle silentRect; String textOrFiles; Array allowedTypes; + std::function completionCallback; }; //============================================================================== @@ -3624,7 +3625,7 @@ private: return externalFindDragTargetWindow (child); } - bool externalDragInit (bool isText, const String& textOrFiles) + bool externalDragInit (bool isText, const String& textOrFiles, std::function cb) { ScopedXLock xlock (display); @@ -3632,6 +3633,7 @@ private: dragState->isText = isText; dragState->textOrFiles = textOrFiles; dragState->targetWindow = windowH; + dragState->completionCallback = cb; const int pointerGrabMask = Button1MotionMask | ButtonReleaseMask; @@ -3664,6 +3666,9 @@ private: XUngrabPointer (display, CurrentTime); } + if (dragState->completionCallback != nullptr) + dragState->completionCallback(); + resetExternalDragState(); } @@ -4262,26 +4267,27 @@ static LinuxComponentPeer* getPeerForDragEvent (Component* sourceComp) } bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles, - Component* sourceComp) + Component* sourceComp, std::function callback) { if (files.isEmpty()) return false; if (auto* lp = getPeerForDragEvent (sourceComp)) - return lp->externalDragFileInit (files, canMoveFiles); + return lp->externalDragFileInit (files, canMoveFiles, callback); // This method must be called in response to a component's mouseDown or mouseDrag event! jassertfalse; return false; } -bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComp) +bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComp, + std::function callback) { if (text.isEmpty()) return false; if (auto* lp = getPeerForDragEvent (sourceComp)) - return lp->externalDragTextInit (text); + return lp->externalDragTextInit (text, callback); // This method must be called in response to a component's mouseDown or mouseDrag event! jassertfalse; diff --git a/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/modules/juce_gui_basics/native/juce_mac_Windowing.mm index dc77f04c12..05cddd8478 100644 --- a/modules/juce_gui_basics/native/juce_mac_Windowing.mm +++ b/modules/juce_gui_basics/native/juce_mac_Windowing.mm @@ -177,14 +177,22 @@ static NSView* getNSViewForDragEvent (Component* sourceComp) return nil; } -struct TextDragDataProviderClass : public ObjCClass +struct NSDraggingSourceHelper : public ObjCClass> { - TextDragDataProviderClass() : ObjCClass ("JUCE_NSTextDragDataProvider_") + NSDraggingSourceHelper() : ObjCClass> ("JUCENSDraggingSourceHelper_") { + addIvar*> ("callback"); addIvar ("text"); + addIvar ("operation"); + addMethod (@selector (dealloc), dealloc, "v@:"); addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@"); + + addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@"); + addMethod (@selector (draggingSession:endedAtPoint:operation:), draggingSessionEnded, "v@:@@@"); + addProtocol (@protocol (NSPasteboardItemDataProvider)); + registerClass(); } @@ -193,10 +201,23 @@ struct TextDragDataProviderClass : public ObjCClass object_setInstanceVariable (self, "text", new String (text)); } + static void setCompletionCallback (id self, std::function cb) + { + object_setInstanceVariable (self, "callback", new std::function (cb)); + } + + static void setDragOperation (id self, NSDragOperation op) + { + object_setInstanceVariable (self, "operation", new NSDragOperation (op)); + } + private: static void dealloc (id self, SEL) { delete getIvar (self, "text"); + delete getIvar*> (self, "callback"); + delete getIvar (self, "operation"); + sendSuperclassMessage (self, @selector (dealloc)); } @@ -207,9 +228,23 @@ private: [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding] forType: NSPasteboardTypeString]; } + + static NSDragOperation sourceOperationMaskForDraggingContext (id self, SEL, NSDraggingSession*, NSDraggingContext) + { + return *getIvar (self, "operation"); + } + + static void draggingSessionEnded (id self, SEL, NSDraggingSession*, NSPoint, NSDragOperation) + { + if (auto* cb = getIvar*> (self, "callback")) + cb->operator()(); + } }; -bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent) +static NSDraggingSourceHelper draggingSourceHelper; + +bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent, + std::function callback) { if (text.isEmpty()) return false; @@ -220,12 +255,15 @@ bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Co { if (auto* event = [[view window] currentEvent]) { - static TextDragDataProviderClass dataProviderClass; - id delegate = [dataProviderClass.createInstance() init]; - TextDragDataProviderClass::setText (delegate, text); + id helper = [draggingSourceHelper.createInstance() init]; + NSDraggingSourceHelper::setText (helper, text); + NSDraggingSourceHelper::setDragOperation (helper, NSDragOperationCopy); + + if (callback != nullptr) + NSDraggingSourceHelper::setCompletionCallback (helper, callback); auto* pasteboardItem = [[NSPasteboardItem new] autorelease]; - [pasteboardItem setDataProvider: delegate + [pasteboardItem setDataProvider: helper forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]]; auto* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease]; @@ -233,13 +271,15 @@ bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Co NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()]; [dragItem setDraggingFrame: getDragRect (view, event) contents: image]; - auto* draggingSession = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem] - event: event - source: delegate]; + if (auto* session = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem] + event: event + source: helper]) + { + session.animatesToStartingPositionsOnCancelOrFail = YES; + session.draggingFormation = NSDraggingFormationNone; - draggingSession.animatesToStartingPositionsOnCancelOrFail = YES; - draggingSession.draggingFormation = NSDraggingFormationNone; - return true; + return true; + } } } } @@ -247,24 +287,8 @@ bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Co return false; } -struct NSDraggingSourceHelper : public ObjCClass> -{ - NSDraggingSourceHelper() : ObjCClass> ("JUCENSDraggingSourceHelper_") - { - addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@"); - registerClass(); - } - - static NSDragOperation sourceOperationMaskForDraggingContext (id, SEL, NSDraggingSession*, NSDraggingContext) - { - return NSDragOperationCopy; - } -}; - -static NSDraggingSourceHelper draggingSourceHelper; - -bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool /*canMoveFiles*/, - Component* sourceComponent) +bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles, + Component* sourceComponent, std::function callback) { if (files.isEmpty()) return false; @@ -296,9 +320,16 @@ bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& fi auto* helper = [draggingSourceHelper.createInstance() autorelease]; - return [view beginDraggingSessionWithItems: dragItems - event: event - source: helper]; + if (callback != nullptr) + NSDraggingSourceHelper::setCompletionCallback (helper, callback); + + NSDraggingSourceHelper::setDragOperation (helper, canMoveFiles ? NSDragOperationMove + : NSDragOperationCopy); + + if (auto* session = [view beginDraggingSessionWithItems: dragItems + event: event + source: helper]) + return true; } } } diff --git a/modules/juce_gui_basics/native/juce_win32_DragAndDrop.cpp b/modules/juce_gui_basics/native/juce_win32_DragAndDrop.cpp index 5db2b1266a..7d7a6c9129 100644 --- a/modules/juce_gui_basics/native/juce_win32_DragAndDrop.cpp +++ b/modules/juce_gui_basics/native/juce_win32_DragAndDrop.cpp @@ -241,49 +241,112 @@ namespace DragAndDropHelpers return hDrop; } - bool performDragDrop (FORMATETC* const format, STGMEDIUM* const medium, const DWORD whatToDo) + struct DragAndDropJob : public ThreadPoolJob { - auto source = new JuceDropSource(); - auto data = new JuceDataObject (source, format, medium); + DragAndDropJob (FORMATETC f, STGMEDIUM m, DWORD d, std::function cb) + : ThreadPoolJob ("DragAndDrop"), + format (f), medium (m), whatToDo (d), + completionCallback (cb) + { + } - DWORD effect; - auto res = DoDragDrop (data, source, whatToDo, &effect); + JobStatus runJob() override + { + OleInitialize (0); - data->Release(); - source->Release(); + auto source = new JuceDropSource(); + auto data = new JuceDataObject (source, &format, &medium); - return res == DRAGDROP_S_DROP; - } + DWORD effect; + DoDragDrop (data, source, whatToDo, &effect); + + data->Release(); + source->Release(); + + if (completionCallback != nullptr) + MessageManager::callAsync (completionCallback); + + return jobHasFinished; + } + + FORMATETC format; + STGMEDIUM medium; + DWORD whatToDo; + + std::function completionCallback; + }; + + class ThreadPoolHolder : private DeletedAtShutdown + { + public: + ThreadPoolHolder() = default; + + ~ThreadPoolHolder() + { + // Wait forever if there's a job running. The user needs to cancel the transfer + // in the GUI. + pool.removeAllJobs (true, -1); + + clearSingletonInstance(); + } + + juce_DeclareSingleton_SingleThreaded (ThreadPoolHolder, true) + + // We need to make sure we don't do simultaneous text and file drag and drops, + // so use a pool that can only run a single job. + ThreadPool pool { 1 }; + }; + + juce_ImplementSingleton_SingleThreaded (ThreadPoolHolder) } //============================================================================== -bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove, Component*) +bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove, + Component*, std::function callback) { + if (files.isEmpty()) + return false; + FORMATETC format = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 }; medium.hGlobal = DragAndDropHelpers::createHDrop (files); - return DragAndDropHelpers::performDragDrop (&format, &medium, canMove ? (DWORD) (DROPEFFECT_COPY | DROPEFFECT_MOVE) - : (DWORD) DROPEFFECT_COPY); + auto& pool = DragAndDropHelpers::ThreadPoolHolder::getInstance()->pool; + pool.addJob (new DragAndDropHelpers::DragAndDropJob (format, medium, + canMove ? (DROPEFFECT_COPY | DROPEFFECT_MOVE) : DROPEFFECT_COPY, + callback), + true); + + return true; } -bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component*) +bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component*, std::function callback) { + if (text.isEmpty()) + return false; + FORMATETC format = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 }; auto numBytes = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer()); medium.hGlobal = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, numBytes + 2); - WCHAR* const data = static_cast (GlobalLock (medium.hGlobal)); + auto* data = static_cast (GlobalLock (medium.hGlobal)); - text.copyToUTF16 (data, numBytes); + text.copyToUTF16 (data, numBytes + 2); format.cfFormat = CF_UNICODETEXT; GlobalUnlock (medium.hGlobal); - return DragAndDropHelpers::performDragDrop (&format, &medium, DROPEFFECT_COPY | DROPEFFECT_MOVE); + auto& pool = DragAndDropHelpers::ThreadPoolHolder::getInstance()->pool; + pool.addJob (new DragAndDropHelpers::DragAndDropJob (format, + medium, + DROPEFFECT_COPY | DROPEFFECT_MOVE, + callback), + true); + + return true; } } // namespace juce