ultimatepp/uppsrc/CtrlCore/CocoClip.mm

507 lines
12 KiB
Text

#include "CocoMM.h"
#ifdef GUI_COCOA
#define LLOG(x) // LOG(x)
extern NSEvent *sCurrentMouseEvent__;
namespace Upp {
NSString *PasteboardType(const String& fmt)
{
return decode(fmt, "text", NSPasteboardTypeString, "png", NSPasteboardTypePNG,
"files", NSFilenamesPboardType, "url", NSURLPboardType,
"rtf", NSPasteboardTypeRTF,
[NSString stringWithUTF8String:~fmt]);
}
NSPasteboard *Pasteboard(bool dnd = false)
{
return dnd ? [NSPasteboard pasteboardWithName:NSPasteboardNameDrag] : [NSPasteboard generalPasteboard];
}
};
@interface CocoClipboardOwner : NSObject {
@public
Upp::VectorMap<Upp::String, Upp::ClipData> data;
Upp::Ptr<Upp::Ctrl> source;
bool dnd;
}
@end
@implementation CocoClipboardOwner
-(void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
{
Upp::GuiLock __;
auto render = [&](const Upp::String& fmt) -> Upp::String {
int q = data.Find(fmt);
if(q < 0)
return Upp::Null;
return data[q].Render();
};
NSPasteboard *pasteboard = Upp::Pasteboard(dnd);
if([type isEqualTo:NSPasteboardTypeString]) {
Upp::String raw = render("text");
if(raw.GetCount() == 0 && source)
raw = source->GetDropData("text");
[pasteboard setString:[NSString stringWithUTF8String:raw]
forType:type];
return;
}
Upp::String fmt = [type isEqualTo:NSPasteboardTypePNG] ? "png" :
[type isEqualTo:NSPasteboardTypeRTF] ? "rtf" :
Upp::ToString(type);
Upp::String raw = render(fmt);
if(raw.GetCount() == 0 && source)
raw = source->GetDropData(fmt);
[pasteboard setData:[NSData dataWithBytes:~raw length:raw.GetCount()] forType:type];
}
- (void)pasteboardChangedOwner:(NSPasteboard *)sender
{
Upp::GuiLock __;
LLOG("pasteboardCahandedOwner");
// data.Clear(); // TODO: This seems to trigger on declareTypes despite owner being the same...
}
@end
namespace Upp {
CocoClipboardOwner *ClipboardOwner(bool dnd = false)
{
GuiLock __;
static CocoClipboardOwner *general = [[CocoClipboardOwner alloc] init];
static CocoClipboardOwner *drag = [[CocoClipboardOwner alloc] init];
general->dnd = false;
drag->dnd = true;
return dnd ? drag : general;
}
void ClearClipboard(bool dnd)
{
GuiLock __;
[Pasteboard(dnd) clearContents];
ClipboardOwner()->data.Clear();
}
void ClearClipboard()
{
ClearClipboard(false);
}
NSMutableSet *PasteboardTypes(const Vector<String>& fmt)
{
NSMutableSet *types = [[[NSMutableSet alloc] init] autorelease];
for(auto id : fmt)
[types addObject:PasteboardType(id)];
return types;
}
void AppendClipboard(bool dnd, const char *format, const Value& value, String (*render)(const Value& data))
{
GuiLock __;
auto& data = ClipboardOwner(dnd)->data;
for(String fmt : Split(format, ';'))
data.GetAdd(fmt) = ClipData(value, render);
AutoreleasePool ___;
[Pasteboard(dnd) declareTypes:[PasteboardTypes(data.GetKeys()) allObjects]
owner:ClipboardOwner(dnd)];
}
Index<String> drop_formats;
void Ctrl::RegisterCocoaDropFormats()
{
AutoreleasePool ___;
NSView *nsview = (NSView *)GetNSView();
[nsview registerForDraggedTypes:[PasteboardTypes(drop_formats.GetKeys()) allObjects]];
}
void Ctrl::RegisterDropFormats(const char *formats)
{
int n = drop_formats.GetCount();
for(String fmt : Split(formats, ';'))
drop_formats.FindAdd(fmt);
if(drop_formats.GetCount() != n)
for(Ctrl *q : Ctrl::GetTopCtrls())
q->RegisterCocoaDropFormats();
}
INITBLOCK {
Ctrl::RegisterDropFormats("text;png;image;rtf;files;url");
}
void AppendClipboard(const char *format, const Value& value, String (*render)(const Value& data))
{
AppendClipboard(false, format, value, render);
}
void AppendClipboard(const char *format, const String& data)
{
GuiLock __;
AppendClipboard(format, Value(data), NULL);
}
void AppendClipboard(const char *format, const byte *data, int length)
{
GuiLock __;
AppendClipboard(format, String(data, length));
}
bool IsFormatAvailable(NSPasteboard *pasteboard, const char *fmt)
{
return [pasteboard availableTypeFromArray:[NSArray arrayWithObjects:PasteboardType(fmt), nil]];
}
String ReadFormat(NSPasteboard *pasteboard, const char *fmt)
{
NSData *data = [pasteboard dataForType:PasteboardType(fmt)];
return String((const char *)[data bytes], [data length]);
}
bool PasteClip::IsAvailable(const char *fmt) const
{
return nspasteboard ? IsFormatAvailable((NSPasteboard *)nspasteboard, fmt)
: IsClipboardAvailable(fmt);
}
String PasteClip::Get(const char *fmt) const
{
return nspasteboard ? ReadFormat((NSPasteboard *)nspasteboard, fmt)
: ReadClipboard(fmt);
}
void PasteClip::GuiPlatformConstruct()
{
nspasteboard = NULL;
}
bool IsClipboardAvailable(const char *fmt)
{
return IsFormatAvailable([NSPasteboard generalPasteboard], fmt);
}
String ReadClipboard(const char *fmt)
{
return ReadFormat([NSPasteboard generalPasteboard], fmt);
}
bool IsClipboardAvailableText()
{
return IsClipboardAvailable("text");
}
String ReadClipboardText()
{
if(IsClipboardAvailableText())
return ReadClipboard("text");
return Null;
}
WString ReadClipboardUnicodeText()
{
return ReadClipboardText().ToWString();
}
void AppendClipboardText(const String& s)
{
AppendClipboard("text", s);
// TODO Remove:
// CFRef<CFStringRef> cs = CFStringCreateWithCString(NULL, (const char *)~s.ToString(), kCFStringEncodingUTF8);
// [[NSPasteboard generalPasteboard] setString:(NSString *)~cs forType:NSPasteboardTypeString];
}
void AppendClipboardUnicodeText(const WString& s)
{
AppendClipboardText(s.ToString());
}
const char *ClipFmtsText()
{
return "text";
}
String GetString(PasteClip& clip)
{
GuiLock __;
return clip.Get("text");
}
WString GetWString(PasteClip& clip)
{
GuiLock __;
return GetString(clip).ToWString();
}
bool AcceptText(PasteClip& clip)
{
return clip.Accept(ClipFmtsText());
}
void Append(VectorMap<String, ClipData>& data, const String& text)
{
data.GetAdd("text", ClipData(text, NULL));
}
void Append(VectorMap<String, ClipData>& data, const WString& text)
{
data.GetAdd("text", ClipData(text.ToString(), NULL));
}
String GetTextClip(const WString& text, const String& fmt)
{
return text.ToString();
}
String GetTextClip(const String& text, const String& fmt)
{
return text;
}
const char *ClipFmtsImage()
{
return "image;png";
}
bool AcceptImage(PasteClip& clip)
{
GuiLock __;
return clip.Accept(ClipFmtsImage());
}
Image GetImage(PasteClip& clip)
{
GuiLock __;
if(clip.Accept("image")) {
Image m;
LoadFromString(m, ~clip);
if(!m.IsEmpty())
return m;
}
if(clip.Accept("png"))
return PNGRaster().LoadString(~clip);
return Null;
}
Image ReadClipboardImage()
{
GuiLock __;
PasteClip d = Ctrl::Clipboard();
return GetImage(d);
}
String sPng(const Value& image)
{
if(IsNull(image))
return Null;
return PNGEncoder().SaveString(image);
}
String sImage(const Value& image)
{
Image img = image;
return StoreAsString(img);
}
String GetImageClip(const Image& img, const String& fmt)
{
GuiLock __;
if(img.IsEmpty()) return Null;
if(fmt == "image")
return sImage(img);
if(fmt == "png")
return sPng(img);
return Null;
}
void Append(VectorMap<String, ClipData>& data, const Image& img)
{
data.GetAdd("image") = ClipData(img, sImage);
}
void AppendClipboardImage(const Image& img)
{
GuiLock __;
if(img.IsEmpty())
return;
AppendClipboard("image", img, sImage);
AppendClipboard("png", img, sPng);
}
bool AcceptFiles(PasteClip& clip)
{
if(clip.Accept("files;url")) {
clip.SetAction(DND_COPY);
return true;
}
return false;
}
bool IsAvailableFiles(PasteClip& clip)
{
return clip.IsAvailable("files;url");
}
Vector<String> GetFiles(PasteClip& clip)
{
GuiLock __;
Vector<String> f;
String raw;
bool files = clip.IsAvailable("files");
if(files)
raw = clip.Get("files");
else
if(clip.IsAvailable("url"))
raw = clip.Get("url");
XmlNode n = ParseXML(raw);
for(const auto& e : n["plist"]["array"])
if(e.IsTag("string")) {
String fn = e.GatherText();
if(files ? fn.GetCount() : fn.TrimStart("file://"))
f.Add(fn);
}
return f;
}
void AppendFiles(VectorMap<String, ClipData>& clip, const Vector<String>& files)
{ // TODO (does not work in modern MacOS)
#if 0
if(files.GetCount() == 0)
return;
String xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\"><array>\n"
;
for(String f : files)
xml << XmlTag("string").Text(f);
xml << "</array></plist>";
DDUMP(xml);
clip.GetAdd("files") = xml;
#endif
}
Ctrl * Ctrl::GetDragAndDropSource()
{
return ClipboardOwner(true)->source;
}
};
@interface DNDSource : NSObject
{
@public
int actions;
int result;
}
@end
@implementation DNDSource
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return actions;
}
- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
{
result = operation;
}
@end
namespace Upp {
bool Ctrl::local_dnd_copy;
int Ctrl::DoDragAndDrop(const char *fmts, const Image& sample, dword actions,
const VectorMap<String, ClipData>& data)
{
ASSERT_(sCurrentMouseEvent__, "Drag can only start within LeftDrag!");
if(!sCurrentMouseEvent__)
return DND_NONE;
if(data.GetCount() == 0)
return DND_NONE; // Cocoa crashes if there is nothing to drop
NSWindow *nswindow = (NSWindow *)GetTopCtrl()->GetNSWindow();
ASSERT_(nswindow, "Ctrl is not in open window");
if(!nswindow)
return DND_NONE;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ClearClipboard(true);
ClipboardOwner(true)->source = this;
for(int i = 0; i < data.GetCount(); i++) {
RegisterDropFormats(data.GetKey(i));
AppendClipboard(true, data.GetKey(i), data[i].data, data[i].render);
}
for(String fmt : Split(fmts, ';')) // GetDropData formats
AppendClipboard(true, fmt, String(), NULL);
CGImageRef cgimg = createCGImage(Nvl(sample, DrawImg::DefaultDragImage()));
Size isz = sample.GetSize();
NSSize size;
double scale = 1.0 / DPI(1);
size.width = scale * isz.cx;
size.height = scale * isz.cy;
NSImage *nsimg = [[[NSImage alloc] initWithCGImage:cgimg size:size] autorelease];
static DNDSource *src = [[DNDSource alloc] init];
src->actions = 0;
if(actions & DND_COPY)
src->actions |= NSDragOperationCopy;
if(actions & DND_MOVE)
src->actions |= NSDragOperationMove;
NSPoint p = [sCurrentMouseEvent__ locationInWindow];
p.y -= size.height;
local_dnd_copy = false; // macos does not have ability to change action in performDragOperation
[nswindow dragImage:nsimg
at:p
offset:NSMakeSize(0, 0)
event:sCurrentMouseEvent__
pasteboard:Pasteboard(true)
source:src
slideBack:YES];
ClipboardOwner(true)->source = NULL;
[pool release];
CGImageRelease(cgimg);
if(local_dnd_copy) // action was local and changed to copy in DragAndDrop
return DND_COPY;
return decode(src->result, NSDragOperationCopy, DND_COPY,
NSDragOperationMove, DND_MOVE,
DND_NONE);
}
void Ctrl::SetSelectionSource(const char *fmts) {}
Image MakeDragImage(const Image& arrow, Image sample);
Image MakeDragImage(const Image& arrow, const Image& arrow98, Image sample)
{
return MakeDragImage(arrow, sample);
}
};
#endif