1///////////////////////////////////////////////////////////////////////////////
2// Name:        src/osx/cocoa/dnd.mm
3// Purpose:     wxDropTarget, wxDropSource implementations
4// Author:      Stefan Csomor
5// Modified by:
6// Created:     1998-01-01
7// Copyright:   (c) 1998 Stefan Csomor
8// Licence:     wxWindows licence
9///////////////////////////////////////////////////////////////////////////////
10
11#include "wx/wxprec.h"
12
13#if wxUSE_DRAG_AND_DROP || wxUSE_CLIPBOARD
14
15#ifndef WX_PRECOMP
16#include "wx/object.h"
17#endif
18
19#include "wx/dnd.h"
20#include "wx/clipbrd.h"
21#include "wx/filename.h"
22
23#ifndef WX_PRECOMP
24    #include "wx/app.h"
25    #include "wx/toplevel.h"
26    #include "wx/gdicmn.h"
27    #include "wx/wx.h"
28#endif // WX_PRECOMP
29
30#include "wx/evtloop.h"
31
32#include "wx/osx/private.h"
33#include "wx/osx/private/datatransfer.h"
34
35wxOSXDataSinkItem::~wxOSXDataSinkItem()
36{
37}
38
39void wxOSXDataSinkItem::SetFilename(const wxString& filename)
40{
41    wxCFRef<CFURLRef> url(wxOSXCreateURLFromFileSystemPath(filename));
42    wxCFRef<CFDataRef> data(CFURLCreateData(NULL,url,kCFStringEncodingUTF8,true));
43    DoSetData( kUTTypeFileURL, data);
44}
45
46wxOSXDataSourceItem::~wxOSXDataSourceItem()
47{
48}
49
50bool wxOSXDataSource::IsSupported(const wxDataFormat &dataFormat)
51{
52    wxCFMutableArrayRef<CFStringRef> typesarray;
53    dataFormat.AddSupportedTypesForSetting(typesarray);
54    return HasData(typesarray);
55}
56
57bool wxOSXDataSource::IsSupported(const wxDataObject &dataobj)
58{
59    wxCFMutableArrayRef<CFStringRef> typesarray;
60    dataobj.AddSupportedTypes(typesarray, wxDataObjectBase::Direction::Get);
61    return HasData(typesarray);
62}
63
64class WXDLLIMPEXP_CORE wxOSXPasteboardSinkItem : public wxOSXDataSinkItem
65{
66public:
67    wxOSXPasteboardSinkItem(NSPasteboardItem* item): m_item(item)
68    {
69    }
70
71    ~wxOSXPasteboardSinkItem()
72    {
73
74    }
75
76    virtual void SetData(const wxDataFormat& format, const void *buf, size_t datasize)
77    {
78        SetData( format.GetFormatId(), buf, datasize);
79    }
80
81    virtual void SetData(wxDataFormat::NativeFormat format, const void *buf, size_t datasize)
82    {
83        wxCFRef<CFDataRef> data(CFDataCreate( kCFAllocatorDefault, (UInt8*)buf, datasize ));
84        DoSetData(format, data);
85    }
86
87    virtual void DoSetData(wxDataFormat::NativeFormat format, CFDataRef data)
88    {
89        [m_item setData:(NSData*) data forType:(NSString*) format];
90    }
91
92    NSPasteboardItem* GetNative() { return m_item; }
93private:
94    NSPasteboardItem* m_item;
95};
96
97class WXDLLIMPEXP_CORE wxOSXPasteboardSourceItem : public wxOSXDataSourceItem
98{
99public:
100    wxOSXPasteboardSourceItem(NSPasteboardItem* item, NSPasteboard* board): m_item(item), m_pasteboard(board)
101    {
102    }
103
104    virtual wxDataFormat::NativeFormat AvailableType(CFArrayRef types) const
105    {
106        return (wxDataFormat::NativeFormat)[m_item availableTypeFromArray:(NSArray*)types];
107    }
108
109    virtual bool GetData( const wxDataFormat& dataFormat, wxMemoryBuffer& target)
110    {
111        return GetData(dataFormat.GetFormatId(), target);
112    }
113
114
115    virtual bool GetData( wxDataFormat::NativeFormat type, wxMemoryBuffer& target)
116    {
117        bool success = false;
118
119        target.Clear();
120
121        CFDataRef flavorData = DoGetData( type );
122
123        if ( flavorData )
124        {
125            CFIndex flavorDataSize = CFDataGetLength(flavorData);
126            size_t allocatedSize = flavorDataSize+4;
127            void * buf = target.GetWriteBuf(allocatedSize);
128            if ( buf )
129            {
130                memset(buf, 0, allocatedSize);
131                memcpy(buf, CFDataGetBytePtr(flavorData), flavorDataSize);
132                target.UngetWriteBuf(flavorDataSize);
133                success = true;
134            }
135        }
136        return success;
137    }
138
139    virtual CFDataRef DoGetData(wxDataFormat::NativeFormat type) const
140    {
141        // before a file promise can be resolved, we must pass a paste location
142        if ( UTTypeConformsTo((CFStringRef)type, kPasteboardTypeFileURLPromise ) )
143        {
144            wxString tempdir = wxFileName::GetTempDir() + wxFILE_SEP_PATH + "wxtemp.XXXXXX";
145            char* result = mkdtemp((char*)tempdir.fn_str().data());
146
147            wxCFRef<CFURLRef> dest(CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)result, strlen(result), true));
148            PasteboardRef pboardRef = NULL;
149            PasteboardCreate((CFStringRef)[m_pasteboard name], &pboardRef);
150            if (pboardRef != NULL) {
151                PasteboardSynchronize(pboardRef);
152                PasteboardSetPasteLocation(pboardRef, (CFURLRef)dest);
153                CFRelease(pboardRef);
154            }
155        }
156
157        return (CFDataRef) [m_item dataForType:(NSString*) type];
158    }
159
160    NSPasteboardItem* GetNative() { return m_item; }
161private:
162    NSPasteboardItem* m_item;
163    NSPasteboard* m_pasteboard;
164};
165
166wxOSXPasteboard::wxOSXPasteboard(OSXPasteboard native)
167{
168    m_pasteboard = native;
169}
170
171wxOSXPasteboard::~wxOSXPasteboard()
172{
173    DeleteSinkItems();
174}
175
176void wxOSXPasteboard::DeleteSinkItems()
177{
178    for ( wxVector<wxOSXDataSinkItem*>::iterator it = m_sinkItems.begin();
179     it != m_sinkItems.end();
180     ++it)
181    {
182        delete (*it);
183    }
184    m_sinkItems.clear();
185}
186
187// data sink methods
188
189void wxOSXPasteboard::Clear()
190{
191    [m_pasteboard clearContents];
192    DeleteSinkItems();
193}
194
195void wxOSXPasteboard::Flush()
196{
197    NSMutableArray* nsarray = [[NSMutableArray alloc] init];
198    for ( wxVector<wxOSXDataSinkItem*>::iterator it = m_sinkItems.begin();
199         it != m_sinkItems.end();
200         ++it)
201    {
202        wxOSXPasteboardSinkItem* item = dynamic_cast<wxOSXPasteboardSinkItem*>(*it);
203        [nsarray addObject:item->GetNative()];
204        delete item;
205    }
206    m_sinkItems.clear();
207    [m_pasteboard writeObjects:nsarray];
208    [nsarray release];
209}
210
211wxOSXDataSinkItem* wxOSXPasteboard::CreateItem()
212{
213    NSPasteboardItem* nsitem = [[NSPasteboardItem alloc] init];
214    wxOSXPasteboardSinkItem* item = new wxOSXPasteboardSinkItem(nsitem);
215    m_sinkItems.push_back(item);
216
217    return item;
218}
219
220// data source methods
221
222bool wxOSXPasteboard::HasData(CFArrayRef types) const
223{
224    return [m_pasteboard canReadItemWithDataConformingToTypes:(NSArray*) types];
225}
226
227const wxOSXDataSourceItem* wxOSXPasteboard::GetItem(size_t pos) const
228{
229    return new wxOSXPasteboardSourceItem([[m_pasteboard pasteboardItems] objectAtIndex: pos], m_pasteboard);
230}
231
232// data source methods
233
234wxOSXPasteboard* wxOSXPasteboard::GetGeneralClipboard()
235{
236    static wxOSXPasteboard clipboard( [NSPasteboard pasteboardWithName:NSGeneralPboard]);
237    return &clipboard;
238}
239
240size_t wxOSXPasteboard::GetItemCount() const
241{
242    return [[m_pasteboard pasteboardItems] count];
243}
244
245#if wxUSE_DRAG_AND_DROP
246
247wxDropSource* gCurrentSource = NULL;
248
249wxDragResult NSDragOperationToWxDragResult(NSDragOperation code)
250{
251    switch (code)
252    {
253        case NSDragOperationGeneric:
254            return wxDragCopy;
255        case NSDragOperationCopy:
256            return wxDragCopy;
257        case NSDragOperationMove:
258            return wxDragMove;
259        case NSDragOperationLink:
260            return wxDragLink;
261        case NSDragOperationNone:
262            return wxDragNone;
263        case NSDragOperationDelete:
264            return wxDragNone;
265        default:
266            wxFAIL_MSG("Unexpected result code");
267    }
268    return wxDragNone;
269}
270
271@interface DropSourceDelegate : NSObject<NSDraggingSource>
272{
273    BOOL dragFinished;
274    int resultCode;
275    wxDropSource* impl;
276
277    // Flags for drag and drop operations (wxDrag_* ).
278    int m_dragFlags;
279}
280
281- (void)setImplementation:(wxDropSource *)dropSource flags:(int)flags;
282- (BOOL)finished;
283- (NSDragOperation)code;
284- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context;
285- (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint;
286- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation;
287@end
288
289@implementation DropSourceDelegate
290
291- (id)init
292{
293    if ( self = [super init] )
294    {
295        dragFinished = NO;
296        resultCode = NSDragOperationNone;
297        impl = 0;
298        m_dragFlags = wxDrag_CopyOnly;
299    }
300    return self;
301}
302
303- (void)setImplementation:(wxDropSource *)dropSource flags:(int)flags
304{
305    impl = dropSource;
306    m_dragFlags = flags;
307}
308
309- (BOOL)finished
310{
311    return dragFinished;
312}
313
314- (NSDragOperation)code
315{
316    return resultCode;
317}
318
319- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
320{
321    wxUnusedVar(session);
322    wxUnusedVar(context);
323
324    NSDragOperation allowedDragOperations = NSDragOperationEvery;
325
326    // NSDragOperationGeneric also makes a drag to the trash possible
327    // resulting in something we don't support (NSDragOperationDelete)
328
329    allowedDragOperations &= ~(NSDragOperationDelete | NSDragOperationGeneric);
330
331    if (m_dragFlags == wxDrag_CopyOnly)
332    {
333        allowedDragOperations &= ~NSDragOperationMove;
334    }
335
336    // we might adapt flags here in the future
337    // context can be NSDraggingContextOutsideApplication or NSDraggingContextWithinApplication
338
339    return allowedDragOperations;
340}
341
342- (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint
343{
344    wxUnusedVar( anImage );
345    wxUnusedVar( aPoint );
346
347    bool optionDown = GetCurrentKeyModifiers() & optionKey;
348    wxDragResult result = optionDown ? wxDragCopy : wxDragMove;
349
350    if (wxDropSource* source = impl)
351    {
352        if (!source->GiveFeedback(result))
353        {
354            wxStockCursor cursorID = wxCURSOR_NONE;
355
356            switch (result)
357            {
358                case wxDragCopy:
359                    cursorID = wxCURSOR_COPY_ARROW;
360                    break;
361
362                case wxDragMove:
363                    cursorID = wxCURSOR_ARROW;
364                    break;
365
366                case wxDragNone:
367                    cursorID = wxCURSOR_NO_ENTRY;
368                    break;
369
370                case wxDragError:
371                case wxDragLink:
372                case wxDragCancel:
373                default:
374                    // put these here to make gcc happy
375                    ;
376            }
377
378            if (cursorID != wxCURSOR_NONE)
379            {
380                // TODO under 10.6 the os itself deals with the cursor, remove if things
381                // work properly everywhere
382#if 0
383                wxCursor cursor( cursorID );
384                cursor.MacInstall();
385#endif
386            }
387        }
388    }
389}
390
391- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
392{
393    wxUnusedVar( anImage );
394    wxUnusedVar( aPoint );
395
396    resultCode = operation;
397    dragFinished = YES;
398}
399
400@end
401
402wxDropTarget::wxDropTarget( wxDataObject *data )
403            : wxDropTargetBase( data )
404{
405
406}
407
408//-------------------------------------------------------------------------
409// wxDropSource
410//-------------------------------------------------------------------------
411
412wxDropSource::wxDropSource(wxWindow *win,
413                           const wxCursor &cursorCopy,
414                           const wxCursor &cursorMove,
415                           const wxCursor &cursorStop)
416            : wxDropSourceBase(cursorCopy, cursorMove, cursorStop)
417{
418    m_window = win;
419}
420
421wxDropSource::wxDropSource(wxDataObject& data,
422                           wxWindow *win,
423                           const wxCursor &cursorCopy,
424                           const wxCursor &cursorMove,
425                           const wxCursor &cursorStop)
426            : wxDropSourceBase(cursorCopy, cursorMove, cursorStop)
427{
428    SetData( data );
429    m_window = win;
430}
431
432wxDropSource* wxDropSource::GetCurrentDropSource()
433{
434    return gCurrentSource;
435}
436
437#if __MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
438typedef NSString* NSPasteboardType;
439#endif
440
441@interface wxPasteBoardWriter : NSObject<NSPasteboardWriting>
442{
443    wxDataObject* m_data;
444}
445
446- (id) initWithDataObject:(wxDataObject*) obj;
447@end
448
449@implementation wxPasteBoardWriter
450
451- (id) initWithDataObject:(wxDataObject*) obj
452{
453    m_data = obj;
454    return self;
455}
456
457- (void) clearDataObject
458{
459    m_data = NULL;
460}
461- (nullable id)pasteboardPropertyListForType:(nonnull NSPasteboardType)type
462{
463    if ( m_data )
464    {
465        wxDataFormat format((wxDataFormat::NativeFormat) type);
466        size_t size = m_data->GetDataSize(format);
467        CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault,size );
468        m_data->GetDataHere(format, CFDataGetMutableBytePtr(data));
469        CFDataSetLength(data, size);
470        return (id) data;
471    }
472    return nil;
473}
474
475- (nonnull NSArray<NSPasteboardType> *)writableTypesForPasteboard:(nonnull NSPasteboard *)pasteboard
476{
477    wxUnusedVar(pasteboard);
478    wxCFMutableArrayRef<CFStringRef> typesarray;
479    if ( m_data )
480        m_data->AddSupportedTypes(typesarray, wxDataObjectBase::Direction::Get);
481    return (NSArray<NSPasteboardType>*) typesarray.autorelease();
482}
483
484@end
485
486wxDragResult wxDropSource::DoDragDrop(int flags)
487{
488    wxASSERT_MSG( m_data, wxT("Drop source: no data") );
489
490    wxDragResult result = wxDragNone;
491    if ((m_data == NULL) || (m_data->GetFormatCount() == 0))
492        return result;
493
494    NSView* view = m_window->GetPeer()->GetWXWidget();
495    if (view)
496    {
497        NSEvent* theEvent = (NSEvent*)wxTheApp->MacGetCurrentEvent();
498        wxASSERT_MSG(theEvent, "DoDragDrop must be called in response to a mouse down or drag event.");
499
500        gCurrentSource = this;
501
502        DropSourceDelegate* delegate = [[DropSourceDelegate alloc] init];
503        [delegate setImplementation:this flags:flags];
504
505        // add a dummy square as dragged image for the moment,
506        // TODO: proper drag image for data
507        NSSize sz = NSMakeSize(16,16);
508        NSRect fillRect = NSMakeRect(0, 0, 16, 16);
509        NSImage* image = [[NSImage alloc] initWithSize: sz];
510
511        [image lockFocus];
512
513        [[[NSColor whiteColor] colorWithAlphaComponent:0.8] set];
514        NSRectFill(fillRect);
515        [[NSColor blackColor] set];
516        NSFrameRectWithWidthUsingOperation(fillRect,1.0f,NSCompositeDestinationOver);
517
518        [image unlockFocus];
519
520        NSPoint down = [theEvent locationInWindow];
521        NSPoint p = [view convertPoint:down fromView:nil];
522
523        wxPasteBoardWriter* writer = [[wxPasteBoardWriter alloc] initWithDataObject:m_data];
524        wxCFMutableArrayRef<NSDraggingItem*> items;
525        NSDraggingItem* item = [[NSDraggingItem alloc] initWithPasteboardWriter:writer];
526        [item setDraggingFrame:NSMakeRect(p.x, p.y, 16, 16) contents:image];
527        items.push_back(item);
528        [view beginDraggingSessionWithItems:items event:theEvent source:delegate];
529
530        wxEventLoopBase * const loop = wxEventLoop::GetActive();
531        while ( ![delegate finished] )
532            loop->Dispatch();
533
534        result = NSDragOperationToWxDragResult([delegate code]);
535        [delegate release];
536        [image release];
537        [writer clearDataObject];
538        [writer release];
539
540        wxWindow* mouseUpTarget = wxWindow::GetCapture();
541
542        if ( mouseUpTarget == NULL )
543        {
544            mouseUpTarget = m_window;
545        }
546
547        if ( mouseUpTarget != NULL )
548        {
549            wxMouseEvent wxevent(wxEVT_LEFT_DOWN);
550            ((wxWidgetCocoaImpl*)mouseUpTarget->GetPeer())->SetupMouseEvent(wxevent , theEvent) ;
551            wxevent.SetEventType(wxEVT_LEFT_UP);
552
553            mouseUpTarget->HandleWindowEvent(wxevent);
554        }
555
556        gCurrentSource = NULL;
557    }
558
559
560    return result;
561}
562
563#endif // wxUSE_DRAG_AND_DROP
564#endif // wxUSE_DRAG_AND_DROP || wxUSE_CLIPBOARD
565