1#include "CocoMM.h"
2
3#ifdef PLATFORM_COCOA
4
5#define LLOG(x)
6
7namespace Upp {
8
9CGImageRef createCGImage(const Image& img)
10{
11	if(IsNull(img))
12		return NULL;
13	CGDataProvider *dataProvider = CGDataProviderCreateWithData(NULL, ~img, img.GetLength() * sizeof(RGBA), NULL);
14	static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // TODO: This is probably wrong...
15	Upp::Size isz = img.GetSize();
16    CGImageRef cg_img = CGImageCreate(isz.cx, isz.cy, 8, 32, isz.cx * sizeof(RGBA),
17                                      colorSpace, kCGImageAlphaPremultipliedFirst,
18                                      dataProvider, 0, false, kCGRenderingIntentDefault);
19	CGDataProviderRelease(dataProvider);
20	return cg_img;
21}
22
23struct ImageSysData {
24	Image            img;
25	CGImageRef       cgimg = NULL;
26
27	void Init(const Image& img);
28	~ImageSysData();
29};
30
31// Note: Cocoa U++ has to ignore paintonly flag as the data of image are directly referenced by
32// data provider
33
34void ImageSysData::Init(const Image& m)
35{
36	img = m;
37	cgimg = createCGImage(img);
38	// Do not call SysImageRealized(img) here
39}
40
41ImageSysData::~ImageSysData()
42{
43	SysImageReleased(img);
44    // Do not call CGImageRelease(cgimg); here
45}
46
47struct ImageSysDataMaker : LRUCache<ImageSysData, int64>::Maker {
48	Image img;
49
50	virtual int64  Key() const                      { return img.GetSerialId(); }
51	virtual int    Make(ImageSysData& object) const { object.Init(img); return img.GetLength(); }
52};
53
54static LRUCache<ImageSysData, int64> cg_image_cache;
55
56static void sCleanImageCache(const Image& img)
57{
58	static int Rsz;
59	Rsz += img.GetLength();
60	if(Rsz > 200 * 200) { // we do not want to do this for each small image painted...
61		Rsz = 0;
62		cg_image_cache.Remove([](const ImageSysData& object) {
63			return object.img.GetRefCount() == 1;
64		});
65	}
66}
67
68void SystemDraw::SysDrawImageOp(int x, int y, const Image& img, Color color)
69{
70	GuiLock __;
71
72	if(img.GetLength() == 0)
73		return;
74
75	sCleanImageCache(img);
76
77	ImageSysDataMaker m;
78	LLOG("SysImage cache pixels " << cache.GetSize() << ", count " << cache.GetCount());
79	m.img = IsNull(color) ? img : CachedSetColorKeepAlpha(img, color); // TODO: Can setcolor be optimized out? By masks e.g.?
80	ImageSysData& sd = cg_image_cache.Get(m);
81	if(sd.cgimg) {
82		Size isz = img.GetSize();
83		CGContextSaveGState(cgHandle);
84		Point off = GetOffset();
85		CGContextTranslateCTM(cgHandle, x + off.x, y + off.y);
86		CGContextScaleCTM(cgHandle, 1.0, -1.0);
87		CGContextDrawImage(cgHandle, CGRectMake(0, -isz.cy, isz.cx, isz.cy), sd.cgimg);
88	    CGContextRestoreGState(cgHandle);
89	}
90	Size sz = Ctrl::GetPrimaryScreenArea().GetSize();
91	cg_image_cache.Shrink(4 * sz.cx * sz.cy, 1000); // Cache must be after Paint because of PaintOnly!
92}
93
94// TODO: https://stackoverflow.com/questions/10733228/native-osx-lion-resize-cursor-for-custom-nswindow-or-nsview
95
96enum {
97	MM_Arrow = 1,
98	MM_Wait,
99	MM_IBeam,
100	MM_No,
101	MM_SizeAll,
102	MM_SizeHorz,
103	MM_SizeVert,
104	MM_SizeTopLeft,
105	MM_SizeTop,
106	MM_SizeTopRight,
107	MM_SizeLeft,
108	MM_SizeRight,
109	MM_SizeBottomLeft,
110	MM_SizeBottom,
111	MM_SizeBottomRight,
112	MM_Cross,
113	MM_Hand,
114};
115
116
117#define FCURSOR_(x) { static Image h; ONCELOCK { h = CreateImage(Size(1, 1), Black); h.SetAuxData(x); } return h; }
118
119Image Image::Arrow() FCURSOR_(MM_Arrow)
120Image Image::Wait() FCURSOR_(MM_Wait)
121Image Image::IBeam() FCURSOR_(MM_IBeam)
122Image Image::No() FCURSOR_(MM_No)
123Image Image::SizeAll() FCURSOR_(MM_SizeAll)
124Image Image::SizeHorz() FCURSOR_(MM_SizeHorz)
125Image Image::SizeVert() FCURSOR_(MM_SizeVert)
126Image Image::SizeTopLeft() FCURSOR_(MM_SizeTopLeft)
127Image Image::SizeTop() FCURSOR_(MM_SizeTop)
128Image Image::SizeTopRight() FCURSOR_(MM_SizeTopRight)
129Image Image::SizeLeft() FCURSOR_(MM_SizeLeft)
130Image Image::SizeRight() FCURSOR_(MM_SizeRight)
131Image Image::SizeBottomLeft() FCURSOR_(MM_SizeBottomLeft)
132Image Image::SizeBottom() FCURSOR_(MM_SizeBottom)
133Image Image::SizeBottomRight() FCURSOR_(MM_SizeBottomRight)
134Image Image::Cross() FCURSOR_(MM_Cross)
135Image Image::Hand() FCURSOR_(MM_Hand)
136
137// TODO: Missing kinds (sizers mostly)
138// https://stackoverflow.com/questions/10733228/native-osx-lion-resize-cursor-for-custom-nswindow-or-nsview/21786835#21786835
139
140#pragma clang diagnostic push
141#pragma clang diagnostic ignored "-Wundeclared-selector"
142
143#define XCURSOR(id) \
144	if([NSCursor respondsToSelector:@selector(id)]) return [NSCursor performSelector:@selector(id)];
145
146NSCursor *GetNSCursor(int kind)
147{
148	switch(kind) {
149	case MM_SizeAll:
150		XCURSOR(_moveCursor);
151		break;
152	case MM_SizeHorz:
153		XCURSOR(_windowResizeEastWestCursor);
154		break;
155	case MM_SizeVert:
156		XCURSOR(_windowResizeNorthSouthCursor);
157		break;
158	case MM_SizeTopLeft:
159		XCURSOR(_windowResizeNorthWestSouthEastCursor);
160		break;
161	case MM_SizeTop:
162		XCURSOR(_windowResizeNorthCursor);
163		break;
164	case MM_SizeTopRight:
165		XCURSOR(_windowResizeNorthEastSouthWestCursor);
166		break;
167	case MM_SizeLeft:
168		XCURSOR(_windowResizeWestCursor);
169		break;
170	case MM_SizeRight:
171		XCURSOR(_windowResizeEastCursor);
172		break;
173	case MM_SizeBottomLeft:
174		XCURSOR(_windowResizeNorthEastSouthWestCursor);
175		break;
176	case MM_SizeBottom:
177		XCURSOR(_windowResizeSouthCursor);
178		break;
179	case MM_SizeBottomRight:
180		XCURSOR(_windowResizeNorthWestSouthEastCursor);
181		break;
182	case MM_IBeam: return [NSCursor IBeamCursor];
183	case MM_Cross: return [NSCursor crosshairCursor];
184	case MM_Hand:  return [NSCursor pointingHandCursor];
185	case MM_No:    return [NSCursor operationNotAllowedCursor];
186	}
187	return [NSCursor arrowCursor];
188}
189
190#pragma clang diagnostic pop
191
192NSImage *CreateNSImage(const Image& img, CGImageRef cgimg)
193{
194	sCleanImageCache(img);
195	Size isz = img.GetSize();
196	double scale = 1.0 / DPI(1);
197	NSSize size;
198	size.width = scale * isz.cx;
199	size.height = scale * isz.cy;
200	NSImage *nsimage = [[NSImage alloc] initWithCGImage:cgimg size:size];
201	cg_image_cache.Shrink(4 * 1024 * 768, 1000); // Cache must be after Paint because of PaintOnly!
202	return nsimage;
203}
204
205struct NSImageSysData {
206	Image            img;
207	NSImage         *nsimage = NULL;
208
209	void Init(const Image& img);
210	~NSImageSysData();
211};
212
213void NSImageSysData::Init(const Image& img)
214{
215	ImageSysDataMaker m;
216	m.img = img;
217	nsimage = CreateNSImage(img, cg_image_cache.Get(m).cgimg);
218}
219
220NSImageSysData::~NSImageSysData()
221{
222	if(nsimage)
223		[nsimage release];
224}
225
226struct NSImageSysDataMaker : LRUCache<NSImageSysData, int64>::Maker {
227	Image img;
228
229	virtual int64  Key() const                        { return img.GetSerialId(); }
230	virtual int    Make(NSImageSysData& object) const { object.Init(img); return img.GetLength(); }
231};
232
233static LRUCache<NSImageSysData, int64> nsimage_cache;
234
235NSImage *GetNSImage(const Image& img)
236{
237	NSImageSysDataMaker m;
238	m.img = img;
239	NSImage *nsimage = nsimage_cache.Get(m).nsimage;
240	nsimage_cache.Shrink(4 * 1024*768, 1000);
241	return nsimage;
242}
243
244void Ctrl::SetNSAppImage(const Image& img)
245{
246	ImageSysDataMaker m;
247	LLOG("SysImage cache pixels " << cache.GetSize() << ", count " << cache.GetCount());
248	m.img = img;
249	ImageSysData& sd = cg_image_cache.Get(m);
250	static CGImageRef cgimg;
251	static NSImage   *nsimg;
252	if(sd.cgimg != cgimg) {
253		cgimg = sd.cgimg;
254		if(nsimg)
255			[nsimg release];
256		nsimg = CreateNSImage(img, cgimg);
257	}
258	[NSApp setApplicationIconImage:nsimg];
259}
260
261void  Ctrl::SetMouseCursor(const Image& img)
262{
263	if(GetDragAndDropSource())
264			return;
265	int64 h = img.GetAuxData();
266	if(h) {
267		[GetNSCursor(h) set];
268		return;
269	}
270	ImageSysDataMaker m;
271	LLOG("SysImage cache pixels " << cache.GetSize() << ", count " << cache.GetCount());
272	m.img = img;
273	ImageSysData& sd = cg_image_cache.Get(m);
274	static CGImageRef cgimg;
275	static NSCursor  *cursor;
276	if(sd.cgimg != cgimg) {
277		cgimg = sd.cgimg;
278		if(cursor)
279			[cursor release];
280		NSImage *nsimg = CreateNSImage(img, cgimg);
281		double scale = 1.0 / DPI(1);
282		Point p = img.GetHotSpot();
283		NSPoint hot;
284		hot.x = scale * p.x;
285		hot.y = scale * p.y;
286		cursor = [[NSCursor alloc] initWithImage:nsimg hotSpot:hot];
287		[nsimg release];
288	}
289	[cursor set];
290}
291
292void ImageDraw::Init(int cx, int cy)
293{
294	ib.Create(cx, cy);
295	Fill(~ib, RGBAZero(), ib.GetLength());
296
297	static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
298
299	SystemDraw::Init(CGBitmapContextCreateWithData(~ib, cx, cy, 8, cx * sizeof(RGBA),
300	                                               colorSpace, kCGImageAlphaPremultipliedFirst,
301	                                               NULL, NULL), NULL);
302	CGContextTranslateCTM(cgHandle, 0, cy);
303	if(IsUHDMode()) {
304		CGContextScaleCTM(cgHandle, 2, -2);
305		CGContextTranslateCTM(cgHandle, 0, -cy / 2.0);
306	}
307	else
308		CGContextScaleCTM(cgHandle, 1, -1);
309}
310
311ImageDraw::ImageDraw(Size sz)
312{
313	Init(sz.cx, sz.cy);
314}
315
316ImageDraw::ImageDraw(int cx, int cy)
317{
318	Init(cx, cy);
319}
320
321Draw& ImageDraw::Alpha()
322{
323	if(!alpha)
324		alpha.Create(ib.GetSize());
325	return *alpha;
326}
327
328ImageDraw::~ImageDraw()
329{
330	CGContextRelease(cgHandle);
331	handle = NULL; // avoid releasing invalid handle in ~SystemDraw
332}
333
334Image ImageDraw::GetStraight() const
335{
336	ImageBuffer ib2(ib.GetSize());
337	memcpy_t(~ib2, ~ib, ib.GetLength());
338	if(alpha) {
339		RGBA *e = ib2.End();
340		RGBA *t = ib2;
341		const RGBA *s = alpha->ib;
342		while(t < e) {
343			t->a = s->r;
344			s++;
345			t++;
346		}
347	}
348	return Image(ib2);
349}
350
351ImageDraw::operator Image() const
352{
353	if(alpha)
354		return Premultiply(GetStraight());
355	ImageBuffer ib2(ib.GetSize());
356	memcpy_t(~ib2, ~ib, ib.GetLength());
357	return Image(ib2);
358}
359
360Image GetIconForFile(const char *path)
361{
362	ImageDraw iw(DPI(16, 16));
363
364	CGContextRef cg = (CGContextRef) iw.GetCGHandle();
365
366
367	NSImage *image;
368	CFRef<CFStringRef> fexe = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
369	image = [[NSWorkspace sharedWorkspace]iconForFile:(__bridge NSString *)~fexe];
370/*
371	}
372	else { // not used any more
373		CFRef<CFStringRef> fext = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
374		image = *ext == '*' ? [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)]
375	                        : [[NSWorkspace sharedWorkspace]iconForFileType:(__bridge NSString *)~fext];
376	}
377*/
378    NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:cg flipped:YES];
379    NSGraphicsContext* cgc = [NSGraphicsContext currentContext];
380    [NSGraphicsContext setCurrentContext:gc];
381    [image drawInRect:NSMakeRect(0, 0, DPI(16), DPI(16))];
382	[NSGraphicsContext setCurrentContext:cgc];
383
384	return iw;
385}
386
387};
388
389#endif
390