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