Browse Source

Made the DragAndDropContainer::performExternalDragDropOfFiles() and ::performExternalDragDropOfText() methods asynchronous on Windows so that behaviour is consistent across all platforms and updated the documentation to reflect this

tags/2021-05-28
ed 6 years ago
parent
commit
4280b51d09
8 changed files with 207 additions and 81 deletions
  1. +23
    -2
      BREAKING-CHANGES.txt
  2. +17
    -13
      modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h
  3. +1
    -3
      modules/juce_gui_basics/mouse/juce_DragAndDropTarget.h
  4. +5
    -2
      modules/juce_gui_basics/native/juce_android_Windowing.cpp
  5. +2
    -2
      modules/juce_gui_basics/native/juce_ios_Windowing.mm
  6. +15
    -9
      modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp
  7. +65
    -34
      modules/juce_gui_basics/native/juce_mac_Windowing.mm
  8. +79
    -16
      modules/juce_gui_basics/native/juce_win32_DragAndDrop.cpp

+ 23
- 2
BREAKING-CHANGES.txt View File

@@ -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.


+ 17
- 13
modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h View File

@@ -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<void()> 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<void()> callback = nullptr);
protected:
/** Override this if you want to be able to perform an external drag of a set of files


+ 1
- 3
modules/juce_gui_basics/mouse/juce_DragAndDropTarget.h View File

@@ -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:


+ 5
- 2
modules/juce_gui_basics/native/juce_android_Windowing.cpp View File

@@ -1077,13 +1077,16 @@ void MouseCursor::showInAllWindows() const {}
//==============================================================================
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& /*files*/, const bool /*canMove*/,
Component* /*srcComp*/)
Component* /*srcComp*/, std::function<void()> /*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<void()> /*callback*/)
{
jassertfalse; // no such thing on Android!
return false;
}


+ 2
- 2
modules/juce_gui_basics/native/juce_ios_Windowing.mm View File

@@ -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<void()>)
{
jassertfalse; // no such thing on iOS!
return false;
}
bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*)
bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*, std::function<void()>)
{
jassertfalse; // no such thing on iOS!
return false;


+ 15
- 9
modules/juce_gui_basics/native/juce_linux_X11_Windowing.cpp View File

@@ -2471,15 +2471,15 @@ public:
}
}
bool externalDragTextInit (const String& text)
bool externalDragTextInit (const String& text, std::function<void()> 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<void()> 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<int> silentRect;
String textOrFiles;
Array<Atom> allowedTypes;
std::function<void()> 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<void()> 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<void()> 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<void()> 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;


+ 65
- 34
modules/juce_gui_basics/native/juce_mac_Windowing.mm View File

@@ -177,14 +177,22 @@ static NSView* getNSViewForDragEvent (Component* sourceComp)
return nil;
}
struct TextDragDataProviderClass : public ObjCClass<NSObject>
struct NSDraggingSourceHelper : public ObjCClass<NSObject<NSDraggingSource>>
{
TextDragDataProviderClass() : ObjCClass<NSObject> ("JUCE_NSTextDragDataProvider_")
NSDraggingSourceHelper() : ObjCClass<NSObject<NSDraggingSource>> ("JUCENSDraggingSourceHelper_")
{
addIvar<std::function<void()>*> ("callback");
addIvar<String*> ("text");
addIvar<NSDragOperation*> ("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<NSObject>
object_setInstanceVariable (self, "text", new String (text));
}
static void setCompletionCallback (id self, std::function<void()> cb)
{
object_setInstanceVariable (self, "callback", new std::function<void()> (cb));
}
static void setDragOperation (id self, NSDragOperation op)
{
object_setInstanceVariable (self, "operation", new NSDragOperation (op));
}
private:
static void dealloc (id self, SEL)
{
delete getIvar<String*> (self, "text");
delete getIvar<std::function<void()>*> (self, "callback");
delete getIvar<NSDragOperation*> (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<NSDragOperation*> (self, "operation");
}
static void draggingSessionEnded (id self, SEL, NSDraggingSession*, NSPoint, NSDragOperation)
{
if (auto* cb = getIvar<std::function<void()>*> (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<void()> 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<NSObject<NSDraggingSource>>
{
NSDraggingSourceHelper() : ObjCClass<NSObject<NSDraggingSource>> ("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<void()> 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;
}
}
}


+ 79
- 16
modules/juce_gui_basics/native/juce_win32_DragAndDrop.cpp View File

@@ -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<void()> 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<void()> 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<void()> 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<void()> 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<WCHAR*> (GlobalLock (medium.hGlobal));
auto* data = static_cast<WCHAR*> (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

Loading…
Cancel
Save