diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp index 0beb215fb5..527c8ea8a5 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp @@ -300,12 +300,17 @@ private: : files (f), canMoveFiles (canMove) {} + ExternalDragAndDropMessage (const String& t) : text (t), canMoveFiles() {} + void messageCallback() override { - DragAndDropContainer::performExternalDragDropOfFiles (files, canMoveFiles); + if (text.isEmpty()) + DragAndDropContainer::performExternalDragDropOfFiles (files, canMoveFiles); + else + DragAndDropContainer::performExternalDragDropOfText (text); } - private: + String text; StringArray files; bool canMoveFiles; }; @@ -318,14 +323,21 @@ private: { hasCheckedForExternalDrag = true; StringArray files; + String text; bool canMoveFiles = false; - if (owner.shouldDropFilesWhenDraggedExternally (details, files, canMoveFiles) - && files.size() > 0 - && ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) + if (ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) { - (new ExternalDragAndDropMessage (files, canMoveFiles))->post(); - deleteSelf(); + if (owner.shouldDropFilesWhenDraggedExternally (details, files, canMoveFiles) && ! files.isEmpty()) + { + (new ExternalDragAndDropMessage (files, canMoveFiles))->post(); + deleteSelf(); + } + else if (owner.shouldDropTextWhenDraggedExternally (details, text) && text.isNotEmpty()) + { + (new ExternalDragAndDropMessage (text))->post(); + deleteSelf(); + } } } } @@ -500,6 +512,11 @@ bool DragAndDropContainer::shouldDropFilesWhenDraggedExternally (const DragAndDr return false; } +bool DragAndDropContainer::shouldDropTextWhenDraggedExternally (const DragAndDropTarget::SourceDetails&, String&) +{ + return false; +} + void DragAndDropContainer::dragOperationStarted (const DragAndDropTarget::SourceDetails&) {} void DragAndDropContainer::dragOperationEnded (const DragAndDropTarget::SourceDetails&) {} diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h index a3980d5418..a807057816 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h @@ -152,10 +152,10 @@ public: static bool performExternalDragDropOfText (const String& text); protected: - /** Override this if you want to be able to perform an external drag a set of files + /** Override this if you want to be able to perform an external drag of a set of files when the user drags outside of this container component. - This method will be called when a drag operation moves outside the Juce-based window, + This method will be called when a drag operation moves outside the JUCE window, and if you want it to then perform a file drag-and-drop, add the filenames you want to the array passed in, and return true. @@ -163,16 +163,30 @@ protected: @param files on return, the filenames you want to drag @param canMoveFiles on return, true if it's ok for the receiver to move the files; false if it must make a copy of them (see the performExternalDragDropOfFiles() method) - @see performExternalDragDropOfFiles + @see performExternalDragDropOfFiles, shouldDropTextWhenDraggedExternally */ virtual bool shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails, StringArray& files, bool& canMoveFiles); + /** Override this if you want to be able to perform an external drag of text + when the user drags outside of this container component. + + This method will be called when a drag operation moves outside the JUCE window, + and if you want it to then perform a text drag-and-drop, copy the text you want to + be dragged into the argument provided and return true. + + @param sourceDetails information about the source of the drag operation + @param text on return, the text you want to drag + @see shouldDropFilesWhenDraggedExternally + */ + virtual bool shouldDropTextWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails, + String& text); + /** Subclasses can override this to be told when a drag starts. */ - virtual void dragOperationStarted (const DragAndDropTarget::SourceDetails& sourceDetails); + virtual void dragOperationStarted (const DragAndDropTarget::SourceDetails&); /** Subclasses can override this to be told when a drag finishes. */ - virtual void dragOperationEnded (const DragAndDropTarget::SourceDetails& sourceDetails); + virtual void dragOperationEnded (const DragAndDropTarget::SourceDetails&); private: //============================================================================== diff --git a/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/modules/juce_gui_basics/native/juce_mac_Windowing.mm index e2d9b32d85..ee22f3b191 100644 --- a/modules/juce_gui_basics/native/juce_mac_Windowing.mm +++ b/modules/juce_gui_basics/native/juce_mac_Windowing.mm @@ -142,45 +142,113 @@ int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconTy //============================================================================== -bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool /*canMoveFiles*/) +static NSRect getDragRect (NSView* view, NSEvent* event) { - if (files.size() == 0) - return false; + auto eventPos = [event locationInWindow]; + + return [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f) + fromView: nil]; +} - MouseInputSource* draggingSource = Desktop::getInstance().getDraggingMouseSource(0); +NSView* getNSViewForDragEvent() +{ + if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource(0)) + if (auto* sourceComp = draggingSource->getComponentUnderMouse()) + return (NSView*) sourceComp->getWindowHandle(); + + jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event! + return nil; +} - if (draggingSource == nullptr) +struct TextDragDataProviderClass : public ObjCClass +{ + TextDragDataProviderClass() : ObjCClass ("JUCE_NSTextDragDataProvider_") { - jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event! - return false; + addIvar ("text"); + addMethod (@selector (dealloc), dealloc, "v@:"); + addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@"); + addProtocol (@protocol (NSPasteboardItemDataProvider)); + registerClass(); } - Component* sourceComp = draggingSource->getComponentUnderMouse(); + static void setText (id self, const String& text) + { + object_setInstanceVariable (self, "text", new String (text)); + } - if (sourceComp == nullptr) +private: + static void dealloc (id self, SEL) { - jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event! + delete getIvar (self, "text"); + sendSuperclassMessage (self, @selector (dealloc)); + } + + static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type) + { + if ([type compare: NSPasteboardTypeString] == NSOrderedSame) + if (auto* text = getIvar (self, "text")) + [sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding] + forType: NSPasteboardTypeString]; + } +}; + +bool DragAndDropContainer::performExternalDragDropOfText (const String& text) +{ + if (text.isEmpty()) return false; + + if (auto* view = getNSViewForDragEvent()) + { + JUCE_AUTORELEASEPOOL + { + if (auto* event = [[view window] currentEvent]) + { + static TextDragDataProviderClass dataProviderClass; + id delegate = [dataProviderClass.createInstance() init]; + TextDragDataProviderClass::setText (delegate, text); + + auto* pasteboardItem = [[NSPasteboardItem new] autorelease]; + [pasteboardItem setDataProvider: delegate + forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]]; + + auto* dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease]; + + NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()]; + [dragItem setDraggingFrame: getDragRect (view, event) contents: image]; + + auto* draggingSession = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem] + event: event + source: delegate]; + + draggingSession.animatesToStartingPositionsOnCancelOrFail = YES; + draggingSession.draggingFormation = NSDraggingFormationNone; + return true; + } + } } - JUCE_AUTORELEASEPOOL + return false; +} + +bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool /*canMoveFiles*/) +{ + if (files.isEmpty()) + return false; + + if (auto* view = getNSViewForDragEvent()) { - if (NSView* view = (NSView*) sourceComp->getWindowHandle()) + JUCE_AUTORELEASEPOOL { - if (NSEvent* event = [[view window] currentEvent]) + if (auto* event = [[view window] currentEvent]) { - NSPoint eventPos = [event locationInWindow]; - NSRect dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f) - fromView: nil]; + auto dragRect = getDragRect (view, event); for (int i = 0; i < files.size(); ++i) - { if (! [view dragFile: juceStringToNS (files[i]) fromRect: dragRect slideBack: YES event: event]) return false; - } return true; } @@ -190,12 +258,6 @@ bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& fi return false; } -bool DragAndDropContainer::performExternalDragDropOfText (const String& /*text*/) -{ - jassertfalse; // not implemented! - return false; -} - //============================================================================== bool Desktop::canUseSemiTransparentWindows() noexcept { @@ -206,8 +268,8 @@ Point MouseInputSource::getCurrentRawMousePosition() { JUCE_AUTORELEASEPOOL { - const NSPoint p ([NSEvent mouseLocation]); - return Point ((float) p.x, (float) (getMainScreenHeight() - p.y)); + auto p = [NSEvent mouseLocation]; + return { (float) p.x, (float) (getMainScreenHeight() - p.y) }; } }