1#include "CocoMM.h" 2 3#ifdef PLATFORM_COCOA 4 5#define LLOG(x) // LOG(x) 6 7extern NSEvent *sCurrentMouseEvent__; 8 9namespace Upp { 10 11NSString *PasteboardType(const String& fmt) 12{ 13 return decode(fmt, "text", NSPasteboardTypeString, "png", NSPasteboardTypePNG, 14 "files", NSFilenamesPboardType, "url", NSURLPboardType, 15 "rtf", NSPasteboardTypeRTF, 16 [NSString stringWithUTF8String:~fmt]); 17} 18 19 20NSPasteboard *Pasteboard(bool dnd = false) 21{ 22 return dnd ? [NSPasteboard pasteboardWithName:NSPasteboardNameDrag] : [NSPasteboard generalPasteboard]; 23} 24 25}; 26 27@interface CocoClipboardOwner : NSObject { 28 @public 29 Upp::VectorMap<Upp::String, Upp::ClipData> data; 30 Upp::Ptr<Upp::Ctrl> source; 31 bool dnd; 32} 33@end 34 35@implementation CocoClipboardOwner 36-(void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type 37{ 38 auto render = [&](const Upp::String& fmt) -> Upp::String { 39 int q = data.Find(fmt); 40 if(q < 0) 41 return Upp::Null; 42 return data[q].Render(); 43 }; 44 45 NSPasteboard *pasteboard = Upp::Pasteboard(dnd); 46 47 if([type isEqualTo:NSPasteboardTypeString]) { 48 Upp::String raw = render("text"); 49 if(raw.GetCount() == 0 && source) 50 raw = source->GetDropData("text"); 51 [pasteboard setString:[NSString stringWithUTF8String:raw] 52 forType:type]; 53 return; 54 } 55 56 Upp::String fmt = [type isEqualTo:NSPasteboardTypePNG] ? "png" : 57 [type isEqualTo:NSPasteboardTypeRTF] ? "rtf" : 58 Upp::ToString(type); 59 Upp::String raw = render(fmt); 60 if(raw.GetCount() == 0 && source) 61 raw = source->GetDropData(fmt); 62 63 [pasteboard setData:[NSData dataWithBytes:~raw length:raw.GetCount()] forType:type]; 64} 65 66- (void)pasteboardChangedOwner:(NSPasteboard *)sender 67{ 68 LLOG("pasteboardCahandedOwner"); 69// data.Clear(); // TODO: This seems to trigger on declareTypes despite owner being the same... 70} 71 72@end 73 74 75namespace Upp { 76 77CocoClipboardOwner *ClipboardOwner(bool dnd = false) 78{ 79 GuiLock __; 80 static CocoClipboardOwner *general = [[CocoClipboardOwner alloc] init]; 81 static CocoClipboardOwner *drag = [[CocoClipboardOwner alloc] init]; 82 general->dnd = false; 83 drag->dnd = true; 84 return dnd ? drag : general; 85} 86 87void ClearClipboard(bool dnd) 88{ 89 GuiLock __; 90 [Pasteboard(dnd) clearContents]; 91 ClipboardOwner()->data.Clear(); 92} 93 94void ClearClipboard() 95{ 96 ClearClipboard(false); 97} 98 99NSMutableSet *PasteboardTypes(const Vector<String>& fmt) 100{ 101 NSMutableSet *types = [[[NSMutableSet alloc] init] autorelease]; 102 for(auto id : fmt) 103 [types addObject:PasteboardType(id)]; 104 return types; 105} 106 107void AppendClipboard(bool dnd, const char *format, const Value& value, String (*render)(const Value& data)) 108{ 109 GuiLock __; 110 111 auto& data = ClipboardOwner(dnd)->data; 112 113 for(String fmt : Split(format, ';')) 114 data.GetAdd(fmt) = ClipData(value, render); 115 116 AutoreleasePool ___; 117 118 [Pasteboard(dnd) declareTypes:[PasteboardTypes(data.GetKeys()) allObjects] 119 owner:ClipboardOwner(dnd)]; 120} 121 122Index<String> drop_formats; 123 124void Ctrl::RegisterCocoaDropFormats() 125{ 126 AutoreleasePool ___; 127 NSView *nsview = (NSView *)GetNSView(); 128 [nsview registerForDraggedTypes:[PasteboardTypes(drop_formats.GetKeys()) allObjects]]; 129} 130 131void Ctrl::RegisterDropFormats(const char *formats) 132{ 133 int n = drop_formats.GetCount(); 134 for(String fmt : Split(formats, ';')) 135 drop_formats.FindAdd(fmt); 136 if(drop_formats.GetCount() != n) 137 for(Ctrl *q : Ctrl::GetTopCtrls()) 138 q->RegisterCocoaDropFormats(); 139} 140 141INITBLOCK { 142 Ctrl::RegisterDropFormats("text;png;image;rtf;files;url"); 143} 144 145void AppendClipboard(const char *format, const Value& value, String (*render)(const Value& data)) 146{ 147 AppendClipboard(false, format, value, render); 148} 149 150void AppendClipboard(const char *format, const String& data) 151{ 152 GuiLock __; 153 AppendClipboard(format, Value(data), NULL); 154} 155 156void AppendClipboard(const char *format, const byte *data, int length) 157{ 158 GuiLock __; 159 AppendClipboard(format, String(data, length)); 160} 161 162bool IsFormatAvailable(NSPasteboard *pasteboard, const char *fmt) 163{ 164 return [pasteboard availableTypeFromArray:[NSArray arrayWithObjects:PasteboardType(fmt), nil]]; 165} 166 167String ReadFormat(NSPasteboard *pasteboard, const char *fmt) 168{ 169 NSData *data = [pasteboard dataForType:PasteboardType(fmt)]; 170 return String((const char *)[data bytes], [data length]); 171} 172 173bool PasteClip::IsAvailable(const char *fmt) const 174{ 175 return nspasteboard ? IsFormatAvailable((NSPasteboard *)nspasteboard, fmt) 176 : IsClipboardAvailable(fmt); 177} 178 179String PasteClip::Get(const char *fmt) const 180{ 181 return nspasteboard ? ReadFormat((NSPasteboard *)nspasteboard, fmt) 182 : ReadClipboard(fmt); 183} 184 185void PasteClip::GuiPlatformConstruct() 186{ 187 nspasteboard = NULL; 188} 189 190bool IsClipboardAvailable(const char *fmt) 191{ 192 return IsFormatAvailable([NSPasteboard generalPasteboard], fmt); 193} 194 195String ReadClipboard(const char *fmt) 196{ 197 return ReadFormat([NSPasteboard generalPasteboard], fmt); 198} 199 200 201bool IsClipboardAvailableText() 202{ 203 return IsClipboardAvailable("text"); 204} 205 206String ReadClipboardText() 207{ 208 if(IsClipboardAvailableText()) 209 return ReadClipboard("text"); 210 return Null; 211} 212 213WString ReadClipboardUnicodeText() 214{ 215 return ReadClipboardText().ToWString(); 216} 217 218void AppendClipboardText(const String& s) 219{ 220 AppendClipboard("text", s); 221 // TODO Remove: 222// CFRef<CFStringRef> cs = CFStringCreateWithCString(NULL, (const char *)~s.ToString(), kCFStringEncodingUTF8); 223 // [[NSPasteboard generalPasteboard] setString:(NSString *)~cs forType:NSPasteboardTypeString]; 224} 225 226void AppendClipboardUnicodeText(const WString& s) 227{ 228 AppendClipboardText(s.ToString()); 229} 230 231const char *ClipFmtsText() 232{ 233 return "text"; 234} 235 236String GetString(PasteClip& clip) 237{ 238 GuiLock __; 239 return clip.Get("text"); 240} 241 242WString GetWString(PasteClip& clip) 243{ 244 GuiLock __; 245 return GetString(clip).ToWString(); 246} 247 248bool AcceptText(PasteClip& clip) 249{ 250 return clip.Accept(ClipFmtsText()); 251} 252 253void Append(VectorMap<String, ClipData>& data, const String& text) 254{ 255 data.GetAdd("text", ClipData(text, NULL)); 256} 257 258void Append(VectorMap<String, ClipData>& data, const WString& text) 259{ 260 data.GetAdd("text", ClipData(text.ToString(), NULL)); 261} 262 263String GetTextClip(const WString& text, const String& fmt) 264{ 265 return text.ToString(); 266} 267 268String GetTextClip(const String& text, const String& fmt) 269{ 270 return text; 271} 272 273const char *ClipFmtsImage() 274{ 275 return "image;png"; 276} 277 278bool AcceptImage(PasteClip& clip) 279{ 280 GuiLock __; 281 return clip.Accept(ClipFmtsImage()); 282} 283 284Image GetImage(PasteClip& clip) 285{ 286 GuiLock __; 287 if(clip.Accept("image")) { 288 Image m; 289 LoadFromString(m, ~clip); 290 if(!m.IsEmpty()) 291 return m; 292 } 293 if(clip.Accept("png")) 294 return PNGRaster().LoadString(~clip); 295 return Null; 296} 297 298Image ReadClipboardImage() 299{ 300 GuiLock __; 301 PasteClip d = Ctrl::Clipboard(); 302 return GetImage(d); 303} 304 305String sPng(const Value& image) 306{ 307 if(IsNull(image)) 308 return Null; 309 return PNGEncoder().SaveString(image); 310} 311 312String sImage(const Value& image) 313{ 314 Image img = image; 315 return StoreAsString(img); 316} 317 318String GetImageClip(const Image& img, const String& fmt) 319{ 320 GuiLock __; 321 if(img.IsEmpty()) return Null; 322 if(fmt == "image") 323 return sImage(img); 324 if(fmt == "png") 325 return sPng(img); 326 return Null; 327} 328 329void Append(VectorMap<String, ClipData>& data, const Image& img) 330{ 331 data.GetAdd("image", ClipData(img, sImage)); 332} 333 334void AppendClipboardImage(const Image& img) 335{ 336 GuiLock __; 337 if(img.IsEmpty()) 338 return; 339 AppendClipboard("image", img, sImage); 340 AppendClipboard("png", img, sPng); 341} 342 343bool AcceptFiles(PasteClip& clip) 344{ 345 if(clip.Accept("files;url")) { 346 clip.SetAction(DND_COPY); 347 return true; 348 } 349 return false; 350} 351 352bool IsAvailableFiles(PasteClip& clip) 353{ 354 return clip.IsAvailable("files;url"); 355} 356 357Vector<String> GetFiles(PasteClip& clip) 358{ 359 GuiLock __; 360 Vector<String> f; 361 String raw; 362 bool files = clip.IsAvailable("files"); 363 if(files) 364 raw = clip.Get("files"); 365 else 366 if(clip.IsAvailable("url")) 367 raw = clip.Get("url"); 368 XmlNode n = ParseXML(raw); 369 for(const auto& e : n["plist"]["array"]) 370 if(e.IsTag("string")) { 371 String fn = e.GatherText(); 372 if(files ? fn.GetCount() : fn.TrimStart("file://")) 373 f.Add(fn); 374 } 375 return f; 376} 377 378Ctrl * Ctrl::GetDragAndDropSource() 379{ 380 return ClipboardOwner(true)->source; 381} 382 383}; 384 385@interface DNDSource : NSObject 386{ 387 @public 388 int actions; 389 int result; 390} 391@end 392 393@implementation DNDSource 394- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal 395{ 396 return actions; 397} 398 399- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation 400{ 401 result = operation; 402} 403 404@end 405 406namespace Upp { 407 408bool Ctrl::local_dnd_copy; 409 410int Ctrl::DoDragAndDrop(const char *fmts, const Image& sample, dword actions, 411 const VectorMap<String, ClipData>& data) 412{ 413 ASSERT_(sCurrentMouseEvent__, "Drag can only start within LeftDrag!"); 414 if(!sCurrentMouseEvent__) 415 return DND_NONE; 416 NSWindow *nswindow = (NSWindow *)GetTopCtrl()->GetNSWindow(); 417 ASSERT_(nswindow, "Ctrl is not in open window"); 418 if(!nswindow) 419 return DND_NONE; 420 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 421 422 ClearClipboard(true); 423 ClipboardOwner(true)->source = this; 424 for(int i = 0; i < data.GetCount(); i++) { 425 RegisterDropFormats(data.GetKey(i)); 426 AppendClipboard(true, data.GetKey(i), data[i].data, data[i].render); 427 } 428 for(String fmt : Split(fmts, ';')) // GetDropData formats 429 AppendClipboard(true, fmt, String(), NULL); 430 431 CGImageRef cgimg = createCGImage(sample); 432 433 Size isz = sample.GetSize(); 434 NSSize size; 435 double scale = 1.0 / DPI(1); 436 size.width = scale * isz.cx; 437 size.height = scale * isz.cy; 438 439 NSImage *nsimg = [[[NSImage alloc] initWithCGImage:cgimg size:size] autorelease]; 440 441 static DNDSource *src = [[DNDSource alloc] init]; 442 443 src->actions = 0; 444 if(actions & DND_COPY) 445 src->actions |= NSDragOperationCopy; 446 if(actions & DND_MOVE) 447 src->actions |= NSDragOperationMove; 448 449 NSPoint p = [sCurrentMouseEvent__ locationInWindow]; 450 p.y -= size.height; 451 452 local_dnd_copy = false; // macos does not have ability to change action in performDragOperation 453 454 [nswindow dragImage:nsimg 455 at:p 456 offset:NSMakeSize(0, 0) 457 event:sCurrentMouseEvent__ 458 pasteboard:Pasteboard(true) 459 source:src 460 slideBack:YES]; 461 462 ClipboardOwner(true)->source = NULL; 463 464 [pool release]; 465 466 CGImageRelease(cgimg); 467 468 if(local_dnd_copy) // action was local and changed to copy in DragAndDrop 469 return DND_COPY; 470 471 return decode(src->result, NSDragOperationCopy, DND_COPY, 472 NSDragOperationMove, DND_MOVE, 473 DND_NONE); 474} 475 476void Ctrl::SetSelectionSource(const char *fmts) {} 477 478Image MakeDragImage(const Image& arrow, Image sample); 479 480Image MakeDragImage(const Image& arrow, const Image& arrow98, Image sample) 481{ 482#ifdef PLATFORM_WIN32 483 if(IsWin2K()) 484 return MakeDragImage(arrow, sample); 485 else 486#endif 487 return arrow98; 488} 489 490}; 491 492#endif 493