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