Files
wxWidgets/src/osx/carbon/utilscocoa.mm
Stefan Csomor e7d21f6638 Initial Commit of full native bitmap macOS implementation
Right now, to better test regressions, with dual implementation
2018-09-24 00:11:31 +02:00

596 lines
17 KiB
Plaintext

/////////////////////////////////////////////////////////////////////////////
// Name: src/osx/carbon/utilscocoa.mm
// Purpose: various cocoa mixin utility functions
// Author: Stefan Csomor
// Modified by:
// Created: 1998-01-01
// Copyright: (c) Stefan Csomor
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/object.h"
#include "wx/math.h"
#endif
#if wxOSX_USE_COCOA_OR_CARBON
#include <Cocoa/Cocoa.h>
#else
#import <UIKit/UIKit.h>
#endif
#ifdef __WXMAC__
#include "wx/osx/private.h"
#endif
#include "wx/fontutil.h"
#ifdef __WXMAC__
wxMacAutoreleasePool::wxMacAutoreleasePool()
{
m_pool = [[NSAutoreleasePool alloc] init];
}
wxMacAutoreleasePool::~wxMacAutoreleasePool()
{
[(NSAutoreleasePool*)m_pool release];
}
#endif
#if wxOSX_USE_COCOA
CGContextRef wxOSXGetContextFromCurrentContext()
{
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext]
graphicsPort];
return context;
}
bool wxOSXLockFocus( WXWidget view)
{
return [view lockFocusIfCanDraw];
}
void wxOSXUnlockFocus( WXWidget view)
{
[view unlockFocus];
}
#endif
#if wxOSX_USE_IPHONE
CGContextRef wxOSXGetContextFromCurrentContext()
{
CGContextRef context = UIGraphicsGetCurrentContext();
return context;
}
#endif
// ----------------------------------------------------------------------------
// NSObject Utils
// ----------------------------------------------------------------------------
void wxMacCocoaRelease( void* obj )
{
[(NSObject*)obj release];
}
void wxMacCocoaAutorelease( void* obj )
{
[(NSObject*)obj autorelease];
}
void* wxMacCocoaRetain( void* obj )
{
[(NSObject*)obj retain];
return obj;
}
// ----------------------------------------------------------------------------
// NSFont Utils
// ----------------------------------------------------------------------------
#if wxOSX_USE_COCOA
NSFont* wxFont::OSXGetNSFont() const
{
wxCHECK_MSG( m_refData != NULL , 0, wxT("invalid font") );
// cast away constness otherwise lazy font resolution is not possible
const_cast<wxFont *>(this)->RealizeResource();
NSFont *font = const_cast<NSFont*>(reinterpret_cast<const NSFont*>(OSXGetCTFont()));
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12
// There's a bug in OS X 10.11 (but not present in 10.10 or 10.12) where a
// toll-free bridged font may have an attributed of private class __NSCFCharacterSet
// that unlike NSCharacterSet doesn't conform to NSSecureCoding. This poses
// a problem when such font is used in user-editable content, because some
// Asian input methods then crash in 10.11 when editing the string.
// As a workaround for this bug, don't use toll-free bridging, but
// re-create NSFont from the descriptor instead on buggy OS X versions.
int osMajor, osMinor;
wxGetOsVersion(&osMajor, &osMinor, NULL);
if (osMajor == 10 && osMinor == 11)
{
return [NSFont fontWithDescriptor:[font fontDescriptor] size:[font pointSize]];
}
#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12
return font;
}
#endif
#if wxOSX_USE_IPHONE
UIFont* wxFont::OSXGetUIFont() const
{
wxCHECK_MSG( m_refData != NULL , 0, wxT("invalid font") );
// cast away constness otherwise lazy font resolution is not possible
const_cast<wxFont *>(this)->RealizeResource();
return const_cast<UIFont*>(reinterpret_cast<const UIFont*>(OSXGetCTFont()));
}
#endif
// ----------------------------------------------------------------------------
// NSWindow Utils
// ----------------------------------------------------------------------------
#if wxOSX_USE_COCOA
WXWindow wxOSXGetMainWindow()
{
return [NSApp mainWindow];
}
WXWindow wxOSXGetKeyWindow()
{
return [NSApp keyWindow];
}
#endif
// ----------------------------------------------------------------------------
// NSImage Utils
// ----------------------------------------------------------------------------
#if wxOSX_USE_IPHONE
WX_UIImage wxOSXGetUIImageFromCGImage( CGImageRef image )
{
return [UIImage imageWithCGImage:image];
}
wxBitmap wxOSXCreateSystemBitmap(const wxString& name, const wxString &client, const wxSize& size)
{
#if 1
// unfortunately this only accesses images in the app bundle, not the system wide globals
wxCFStringRef cfname(name);
return wxBitmap( [[UIImage imageNamed:cfname.AsNSString()] CGImage] );
#else
return wxBitmap();
#endif
}
double wxOSXGetMainScreenContentScaleFactor()
{
double scale;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
{
scale=[[UIScreen mainScreen] scale];
}
else
#endif
{
scale=1.0;
}
return scale;
}
#endif
#if wxOSX_USE_COCOA
WX_NSImage wxOSXGetSystemImage(const wxString& name)
{
wxCFStringRef cfname(name);
NSImage* nsimage = [NSImage imageNamed:cfname.AsNSString()];
return nsimage;
}
wxBitmap wxOSXCreateSystemBitmap(const wxString& name, const wxString &client, const wxSize& sizeHint)
{
NSImage* nsimage = wxOSXGetSystemImage(name);
if ( nsimage )
{
// if ( sizeHint != wxDefaultSize )
// [nsimage setSize:NSMakeSize(sizeHint.GetHeight(), sizeHint.GetWidth())];
return wxBitmap( nsimage );
}
return wxNullBitmap;
}
WX_NSImage wxOSXGetNSImageFromCGImage( CGImageRef image, double scaleFactor, bool isTemplate )
{
NSRect imageRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
// Get the image dimensions.
imageRect.size.height = CGImageGetHeight(image)/scaleFactor;
imageRect.size.width = CGImageGetWidth(image)/scaleFactor;
NSImage* newImage = [[NSImage alloc] initWithCGImage:image size:imageRect.size];
[newImage setTemplate:isTemplate];
[newImage autorelease];
return( newImage );
}
WX_NSImage WXDLLIMPEXP_CORE wxOSXGetNSImageFromIconRef( WXHICON iconref )
{
NSImage *newImage = [[NSImage alloc] initWithIconRef:iconref];
[newImage autorelease];
return( newImage );
}
CGImageRef WXDLLIMPEXP_CORE wxOSXGetCGImageFromNSImage( WX_NSImage nsimage, CGRect* r, CGContextRef cg)
{
NSRect nsRect = NSRectFromCGRect(*r);
return [nsimage CGImageForProposedRect:&nsRect
context:[NSGraphicsContext graphicsContextWithGraphicsPort:cg flipped:YES]
hints:nil];
}
CGContextRef WXDLLIMPEXP_CORE wxOSXCreateBitmapContextFromNSImage( WX_NSImage nsimage, bool *isTemplate)
{
// based on http://www.mail-archive.com/cocoa-dev@lists.apple.com/msg18065.html
CGContextRef hbitmap = NULL;
if (nsimage != nil)
{
double scale = wxOSXGetMainScreenContentScaleFactor();
NSSize imageSize = [nsimage size];
hbitmap = CGBitmapContextCreate(NULL, imageSize.width*scale, imageSize.height*scale, 8, 0, wxMacGetGenericRGBColorSpace(), kCGImageAlphaPremultipliedFirst);
CGContextScaleCTM( hbitmap, scale, scale );
NSGraphicsContext *previousContext = [NSGraphicsContext currentContext];
NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:hbitmap flipped:NO];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:nsGraphicsContext];
[[NSColor whiteColor] setFill];
NSRectFill(NSMakeRect(0.0, 0.0, imageSize.width, imageSize.height));
[nsimage drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[NSGraphicsContext setCurrentContext:previousContext];
if (isTemplate)
*isTemplate = [nsimage isTemplate];
}
return hbitmap;
}
double wxOSXGetMainScreenContentScaleFactor()
{
return [[NSScreen mainScreen] backingScaleFactor];
}
WX_NSImage wxOSXGetIconForType(OSType type )
{
return [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode(type)];
}
void wxOSXSetImageSize(WX_NSImage image, CGFloat width, CGFloat height)
{
[image setSize:NSMakeSize(width, height)];
}
CGImageRef wxOSXCreateCGImageFromNSImage( WX_NSImage nsimage, double *scaleptr )
{
// based on http://www.mail-archive.com/cocoa-dev@lists.apple.com/msg18065.html
CGImageRef image = NULL;
if (nsimage != nil)
{
CGContextRef context = wxOSXCreateBitmapContextFromNSImage(nsimage);
if ( scaleptr )
{
// determine content scale
CGRect userrect = CGRectMake(0, 0, 10, 10);
CGRect devicerect;
devicerect = CGContextConvertRectToDeviceSpace(context, userrect);
*scaleptr = devicerect.size.height / userrect.size.height;
}
image = CGBitmapContextCreateImage(context);
CFRelease(context);
}
return image;
}
// ----------------------------------------------------------------------------
// NSCursor Utils
// ----------------------------------------------------------------------------
// copied from cursor.mm
static NSCursor* wxGetStockCursor( short sIndex )
{
ClassicCursor* pCursor = &gMacCursors[sIndex];
//Classic mac cursors are 1bps 16x16 black and white with a
//identical mask that is 1 for on and 0 for off
NSImage *theImage = [[NSImage alloc] initWithSize:NSMakeSize(16.0,16.0)];
//NSCursor takes an NSImage takes a number of Representations - here
//we need only one for the raw data
NSBitmapImageRep *theRep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes: NULL // Tell Cocoa to allocate the planes for us.
pixelsWide: 16 // All classic cursors are 16x16
pixelsHigh: 16
bitsPerSample: 1 // All classic cursors are bitmaps with bitmasks
samplesPerPixel: 2 // Sample 0:image 1:mask
hasAlpha: YES // Identify last sample as a mask
isPlanar: YES // Use a separate array for each sample
colorSpaceName: NSCalibratedWhiteColorSpace // 0.0=black 1.0=white
bytesPerRow: 2 // Rows in each plane are on 2-byte boundaries (no pad)
bitsPerPixel: 1]; // same as bitsPerSample since data is planar
// Ensure that Cocoa allocated 2 and only 2 of the 5 possible planes
unsigned char *planes[5];
[theRep getBitmapDataPlanes:planes];
wxASSERT(planes[0] != NULL);
wxASSERT(planes[1] != NULL);
wxASSERT(planes[2] == NULL);
wxASSERT(planes[3] == NULL);
wxASSERT(planes[4] == NULL);
// NOTE1: The Cursor's bits field is white=0 black=1.. thus the bitwise-not
// Why not use NSCalibratedBlackColorSpace? Because that reverses the
// sense of the alpha (mask) plane.
// NOTE2: The mask data is 0=off 1=on
// NOTE3: Cocoa asks for "premultiplied" color planes. Since we have a
// 1-bit color plane and a 1-bit alpha plane we can just do a bitwise-and
// on the two. The original cursor bitmaps have 0 (white actually) for
// any masked-off pixels. Therefore every masked-off pixel would be wrong
// since we bit-flip all of the picture bits. In practice, Cocoa doesn't
// seem to care, but we are following the documentation.
// Fill in the color (black/white) plane
for(int i=0; i<16; ++i)
{
planes[0][2*i ] = (~pCursor->bits[i] & pCursor->mask[i]) >> 8 & 0xff;
planes[0][2*i+1] = (~pCursor->bits[i] & pCursor->mask[i]) & 0xff;
}
// Fill in the alpha (i.e. mask) plane
for(int i=0; i<16; ++i)
{
planes[1][2*i ] = pCursor->mask[i] >> 8 & 0xff;
planes[1][2*i+1] = pCursor->mask[i] & 0xff;
}
//add the representation (data) to the image
[theImage addRepresentation:theRep];
//create the new cursor
NSCursor* theCursor = [[NSCursor alloc] initWithImage:theImage
hotSpot:NSMakePoint(pCursor->hotspot[1], pCursor->hotspot[0])
];
//do the usual cleanups
[theRep release];
[theImage release];
//return the new cursor
return theCursor;
}
WX_NSCursor wxMacCocoaCreateStockCursor( int cursor_type )
{
WX_NSCursor cursor = nil;
switch (cursor_type)
{
case wxCURSOR_COPY_ARROW:
cursor = [[NSCursor arrowCursor] retain];
break;
case wxCURSOR_WATCH:
case wxCURSOR_WAIT:
// an arrow should be displayed by the system when things are running
// according to the HIG
// cursor = [[NSCursor arrowCursor] retain];
// but for crossplatform compatibility we display a watch cursor
cursor = wxGetStockCursor(kwxCursorWatch);
break;
case wxCURSOR_IBEAM:
cursor = [[NSCursor IBeamCursor] retain];
break;
case wxCURSOR_CROSS:
cursor = [[NSCursor crosshairCursor] retain];
break;
case wxCURSOR_SIZENWSE:
cursor = wxGetStockCursor(kwxCursorSizeNWSE);
break;
case wxCURSOR_SIZENESW:
cursor = wxGetStockCursor(kwxCursorSizeNESW);
break;
case wxCURSOR_SIZEWE:
cursor = [[NSCursor resizeLeftRightCursor] retain];
break;
case wxCURSOR_SIZENS:
cursor = [[NSCursor resizeUpDownCursor] retain];
break;
case wxCURSOR_SIZING:
cursor = wxGetStockCursor(kwxCursorSize);
break;
case wxCURSOR_HAND:
cursor = [[NSCursor pointingHandCursor] retain];
break;
case wxCURSOR_BULLSEYE:
cursor = wxGetStockCursor(kwxCursorBullseye);
break;
case wxCURSOR_PENCIL:
cursor = wxGetStockCursor(kwxCursorPencil);
break;
case wxCURSOR_MAGNIFIER:
cursor = wxGetStockCursor(kwxCursorMagnifier);
break;
case wxCURSOR_NO_ENTRY:
cursor = wxGetStockCursor(kwxCursorNoEntry);
break;
case wxCURSOR_PAINT_BRUSH:
cursor = wxGetStockCursor(kwxCursorPaintBrush);
break;
case wxCURSOR_POINT_LEFT:
cursor = wxGetStockCursor(kwxCursorPointLeft);
break;
case wxCURSOR_POINT_RIGHT:
cursor = wxGetStockCursor(kwxCursorPointRight);
break;
case wxCURSOR_QUESTION_ARROW:
cursor = wxGetStockCursor(kwxCursorQuestionArrow);
break;
case wxCURSOR_BLANK:
cursor = wxGetStockCursor(kwxCursorBlank);
break;
case wxCURSOR_RIGHT_ARROW:
cursor = wxGetStockCursor(kwxCursorRightArrow);
break;
case wxCURSOR_SPRAYCAN:
cursor = wxGetStockCursor(kwxCursorRoller);
break;
case wxCURSOR_OPEN_HAND:
cursor = [[NSCursor openHandCursor] retain];
break;
case wxCURSOR_CLOSED_HAND:
cursor = [[NSCursor closedHandCursor] retain];
break;
case wxCURSOR_CHAR:
case wxCURSOR_ARROW:
case wxCURSOR_LEFT_BUTTON:
case wxCURSOR_RIGHT_BUTTON:
case wxCURSOR_MIDDLE_BUTTON:
default:
cursor = [[NSCursor arrowCursor] retain];
break;
}
return cursor;
}
// C-based style wrapper routines around NSCursor
WX_NSCursor wxMacCocoaCreateCursorFromCGImage( CGImageRef cgImageRef, float hotSpotX, float hotSpotY )
{
static BOOL firstTime = YES;
if ( firstTime )
{
// Must first call [[[NSWindow alloc] init] release] to get the NSWindow machinery set up so that NSCursor can use a window to cache the cursor image
[[[NSWindow alloc] init] release];
firstTime = NO;
}
NSImage *nsImage = wxOSXGetNSImageFromCGImage( cgImageRef );
NSCursor *cursor = [[NSCursor alloc] initWithImage:nsImage hotSpot:NSMakePoint( hotSpotX, hotSpotY )];
return cursor;
}
void wxMacCocoaSetCursor( WX_NSCursor cursor )
{
[cursor set];
}
void wxMacCocoaHideCursor()
{
[NSCursor hide];
}
void wxMacCocoaShowCursor()
{
[NSCursor unhide];
}
//---------------------------------------------------------
// helper functions for NSString<->wxString conversion
//---------------------------------------------------------
wxString wxStringWithNSString(NSString *nsstring)
{
#if wxUSE_UNICODE
return wxString([nsstring UTF8String], wxConvUTF8);
#else
return wxString([nsstring lossyCString]);
#endif // wxUSE_UNICODE
}
NSString* wxNSStringWithWxString(const wxString &wxstring)
{
#if wxUSE_UNICODE
return [NSString stringWithUTF8String: wxstring.mb_str(wxConvUTF8)];
#else
return [NSString stringWithCString: wxstring.c_str() length:wxstring.Len()];
#endif // wxUSE_UNICODE
}
// ----------------------------------------------------------------------------
// helper class for getting the correct system colors according to the
// appearance in effect
// ----------------------------------------------------------------------------
wxOSXEffectiveAppearanceSetter::wxOSXEffectiveAppearanceSetter()
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14
if ( wxPlatformInfo::Get().CheckOSVersion(10, 14 ) )
{
formerAppearance = NSAppearance.currentAppearance;
NSAppearance.currentAppearance = NSApp.effectiveAppearance;
}
#else
wxUnusedVar(formerAppearance);
#endif
}
wxOSXEffectiveAppearanceSetter::~wxOSXEffectiveAppearanceSetter()
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14
if ( wxPlatformInfo::Get().CheckOSVersion(10, 14 ) )
NSAppearance.currentAppearance = (NSAppearance*) formerAppearance;
#endif
}
#endif