<commit_before>/**
 * Scintilla source code edit control
 * PlatCocoa.mm - implementation of platform facilities on MacOS X/Cocoa
 *
 * Written by <NAME>
 * Based on PlatMacOSX.cxx
 * Based on work by <NAME> (c) 2002 <<EMAIL>>
 * Based on PlatGTK.cxx Copyright 1998-2002 by <NAME> <<EMAIL>>
 * The License.txt file describes the conditions under which this software may be distributed.
 *
 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
 * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt).
 */

#import <ScintillaView.h>

#include "PlatCocoa.h"

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <assert.h>
#include <sys/time.h>
#include <stdexcept>
#include <map>

#include "XPM.h"

#import <Foundation/NSGeometry.h>

#import <Carbon/Carbon.h> // Temporary

using namespace Scintilla;

extern sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam);

//--------------------------------------------------------------------------------------------------

/**
 * Converts a PRectangle as used by Scintilla to standard Obj-C NSRect structure .
 */
NSRect PRectangleToNSRect(PRectangle& rc)
{
  return NSMakeRect(rc.left, rc.top, rc.Width(), rc.Height());
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts an NSRect as used by the system to a native Scintilla rectangle.
 */
PRectangle NSRectToPRectangle(NSRect& rc)
{
  return PRectangle(rc.origin.x, rc.origin.y, rc.size.width + rc.origin.x, rc.size.height + rc.origin.y);
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts a PRctangle as used by Scintilla to a Quartz-style rectangle.
 */
inline CGRect PRectangleToCGRect(PRectangle& rc)
{
  return CGRectMake(rc.left, rc.top, rc.Width(), rc.Height());
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts a Quartz-style rectangle to a PRectangle structure as used by Scintilla.
 */
inline PRectangle CGRectToPRectangle(const CGRect& rect)
{
  PRectangle rc;
  rc.left = (int)(rect.origin.x + 0.5);
  rc.top = (int)(rect.origin.y + 0.5);
  rc.right = (int)(rect.origin.x + rect.size.width + 0.5);
  rc.bottom = (int)(rect.origin.y + rect.size.height + 0.5);
  return rc;
}

//----------------- Point --------------------------------------------------------------------------

/**
 * Converts a point given as a long into a native Point structure.
 */
Scintilla::Point Scintilla::Point::FromLong(long lpoint)
{
  return Scintilla::Point(
                          Platform::LowShortFromLong(lpoint),
                          Platform::HighShortFromLong(lpoint)
                          );
}

//----------------- Palette ------------------------------------------------------------------------

// The Palette implementation is only here because we would get linker errors if not.
// We don't use indexed colors in ScintillaCocoa.

Scintilla::Palette::Palette()
{
}

//--------------------------------------------------------------------------------------------------

Scintilla::Palette::~Palette()
{
}

//--------------------------------------------------------------------------------------------------

void Scintilla::Palette::Release()
{
}

//--------------------------------------------------------------------------------------------------

/**
 * Used to transform a given color, if needed. If the caller tries to find a color that matches the
 * desired color then we simply pass it on, as we support the full color space.
 */
void Scintilla::Palette::WantFind(ColourPair &cp, bool want)
{
  if (!want)
    cp.allocated.Set(cp.desired.AsLong());
  
  // Don't do anything if the caller wants the color it has already set.
}

//--------------------------------------------------------------------------------------------------

void Scintilla::Palette::Allocate(Window&)
{
  // Nothing to allocate as we don't use palettes.
}

//----------------- Font ---------------------------------------------------------------------------

Font::Font(): fid(0)
{
}

//--------------------------------------------------------------------------------------------------

Font::~Font()
{
  Release();
}

//--------------------------------------------------------------------------------------------------

static int FontCharacterSet(Font &f) {
	return reinterpret_cast<QuartzTextStyle *>(f.GetID())->getCharacterSet();
}

/**
 * Creates a CTFontRef with the given properties.
 */
void Font::Create(const char *faceName, int characterSet, int size, bool bold, bool italic, 
                  int /* extraFontFlag */)
{
	Release();

	QuartzTextStyle* style = new QuartzTextStyle();
	fid = style;

	// Create the font with attributes
	QuartzFont font(faceName, strlen(faceName), size, bold, italic);
	CTFontRef fontRef = font.getFontID();
	style->setFontRef(fontRef, characterSet);
}

//--------------------------------------------------------------------------------------------------

void Font::Release()
{
  if (fid)
    delete reinterpret_cast<QuartzTextStyle*>( fid );
  fid = 0;
}

//----------------- SurfaceImpl --------------------------------------------------------------------

SurfaceImpl::SurfaceImpl()
{
  unicodeMode = true;
  x = 0;
  y = 0;
  gc = NULL;

  textLayout = new QuartzTextLayout(NULL);
  codePage = 0;
  verticalDeviceResolution = 0;

  bitmapData = NULL; // Release will try and delete bitmapData if != NULL
  bitmapWidth = 0;
  bitmapHeight = 0;

  Release();
}

//--------------------------------------------------------------------------------------------------

SurfaceImpl::~SurfaceImpl()
{
  Release();
  delete textLayout;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Release()
{
  textLayout->setContext (NULL);
  if ( bitmapData != NULL )
  {
    delete[] bitmapData;
    // We only "own" the graphics context if we are a bitmap context
    if (gc != NULL)
      CGContextRelease(gc);
  }
  bitmapData = NULL;
  gc = NULL;
  
  bitmapWidth = 0;
  bitmapHeight = 0;
  x = 0;
  y = 0;
}

//--------------------------------------------------------------------------------------------------

bool SurfaceImpl::Initialised()
{
  // We are initalised if the graphics context is not null
  return gc != NULL;// || port != NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Init(WindowID)
{
  // To be able to draw, the surface must get a CGContext handle.  We save the graphics port,
  // then aquire/release the context on an as-need basis (see above).
  // XXX Docs on QDBeginCGContext are light, a better way to do this would be good.
  // AFAIK we should not hold onto a context retrieved this way, thus the need for
  // aquire/release of the context.
  
  Release();
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Init(SurfaceID sid, WindowID)
{
  Release();
  gc = reinterpret_cast<CGContextRef>(sid);
  CGContextSetLineWidth(gc, 1.0);
  textLayout->setContext(gc);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::InitPixMap(int width, int height, Surface* /* surface_ */, WindowID /* wid */)
{
  Release();
  
  // Create a new bitmap context, along with the RAM for the bitmap itself
  bitmapWidth = width;
  bitmapHeight = height;
  
  const int bitmapBytesPerRow = (width * BYTES_PER_PIXEL);
  const int bitmapByteCount = (bitmapBytesPerRow * height);
  
  // Create an RGB color space.
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  if (colorSpace == NULL)
    return;
  
  // Create the bitmap.
  bitmapData = new uint8_t[bitmapByteCount];
  if (bitmapData != NULL)
  {
    // create the context
    gc = CGBitmapContextCreate(bitmapData,
                               width,
                               height,
                               BITS_PER_COMPONENT,
                               bitmapBytesPerRow,
                               colorSpace,
                               kCGImageAlphaPremultipliedLast);
    
    if (gc == NULL)
    {
      // the context couldn't be created for some reason,
      // and we have no use for the bitmap without the context
      delete[] bitmapData;
      bitmapData = NULL;
    }
    textLayout->setContext (gc);
  }
  
  // the context retains the color space, so we can release it
  CGColorSpaceRelease(colorSpace);
  
  if (gc != NULL && bitmapData != NULL)
  {
    // "Erase" to white.
    CGContextClearRect( gc, CGRectMake( 0, 0, width, height ) );
    CGContextSetRGBFillColor( gc, 1.0, 1.0, 1.0, 1.0 );
    CGContextFillRect( gc, CGRectMake( 0, 0, width, height ) );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::PenColour(ColourAllocated fore)
{
  if (gc)
  {
    ColourDesired colour(fore.AsLong());
    
    // Set the Stroke color to match
    CGContextSetRGBStrokeColor(gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, 
                               colour.GetBlue() / 255.0, 1.0 );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillColour(const ColourAllocated& back)
{
  if (gc)
  {
    ColourDesired colour(back.AsLong());
    
    // Set the Fill color to match
    CGContextSetRGBFillColor(gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, 
                             colour.GetBlue() / 255.0, 1.0 );
  }
}

//--------------------------------------------------------------------------------------------------

CGImageRef SurfaceImpl::GetImage()
{
  // For now, assume that GetImage can only be called on PixMap surfaces.
  if (bitmapData == NULL)
    return NULL;
  
  CGContextFlush(gc);
  
  // Create an RGB color space.
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  if( colorSpace == NULL )
    return NULL;
  
  const int bitmapBytesPerRow = ((int) bitmapWidth * BYTES_PER_PIXEL);
  const int bitmapByteCount = (bitmapBytesPerRow * (int) bitmapHeight);
  
  // Make a copy of the bitmap data for the image creation and divorce it
  // From the SurfaceImpl lifetime
  CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, bitmapData, bitmapByteCount);

  // Create a data provider.
  CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef);

  CGImageRef image = NULL;
  if (dataProvider != NULL)
  {
    // Create the CGImage.
    image = CGImageCreate(bitmapWidth,
                          bitmapHeight,
                          BITS_PER_COMPONENT,
                          BITS_PER_PIXEL,
                          bitmapBytesPerRow,
                          colorSpace,
                          kCGImageAlphaPremultipliedLast,
                          dataProvider,
                          NULL,
                          0,
                          kCGRenderingIntentDefault);
  }
  
  // The image retains the color space, so we can release it.
  CGColorSpaceRelease(colorSpace);
  colorSpace = NULL;
  
  // Done with the data provider.
  CGDataProviderRelease(dataProvider);
  dataProvider = NULL;
  
  // Done with the data provider.
  CFRelease(dataRef);
    
  return image;
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the vertical logical device resolution of the main monitor.
 */
int SurfaceImpl::LogPixelsY()
{
  if (verticalDeviceResolution == 0)
  {
    NSSize deviceResolution = [[[[NSScreen mainScreen] deviceDescription]
    objectForKey: NSDeviceResolution] sizeValue];
    verticalDeviceResolution = (int) deviceResolution.height;
  }
  return verticalDeviceResolution;
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts the logical font height (in dpi) into a pixel height for the current main screen.
 */
int SurfaceImpl::DeviceHeightFont(int points)
{
  int logPix = LogPixelsY();
  return (points * logPix + logPix / 2) / 72;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::MoveTo(int x_, int y_)
{
  x = x_;
  y = y_;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::LineTo(int x_, int y_)
{
  CGContextBeginPath( gc );
  
  // Because Quartz is based on floating point, lines are drawn with half their colour
  // on each side of the line. Integer coordinates specify the INTERSECTION of the pixel
  // divison lines. If you specify exact pixel values, you get a line that
  // is twice as thick but half as intense. To get pixel aligned rendering,
  // we render the "middle" of the pixels by adding 0.5 to the coordinates.
  CGContextMoveToPoint( gc, x + 0.5, y + 0.5 );
  CGContextAddLineToPoint( gc, x_ + 0.5, y_ + 0.5 );
  CGContextStrokePath( gc );
  x = x_;
  y = y_;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Polygon(Scintilla::Point *pts, int npts, ColourAllocated fore,
                          ColourAllocated back)
{
  // Allocate memory for the array of points.
  CGPoint *points = new CGPoint[npts];
  
  for (int i = 0;i < npts;i++)
  {
    // Quartz floating point issues: plot the MIDDLE of the pixels
    points[i].x = pts[i].x + 0.5;
    points[i].y = pts[i].y + 0.5;
  }
  
  CGContextBeginPath(gc);
  
  // Set colours
  FillColour(back);
  PenColour(fore);
  
  // Draw the polygon
  CGContextAddLines(gc, points, npts);
  
  // Explicitly close the path, so it is closed for stroking AND filling (implicit close = filling only)
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
  
  // Deallocate memory.
  delete points;
  points = NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::RectangleDraw(PRectangle rc, ColourAllocated fore, ColourAllocated back)
{
  if (gc)
  {
    CGContextBeginPath( gc );
    FillColour(back);
    PenColour(fore);
    
    // Quartz integer -> float point conversion fun (see comment in SurfaceImpl::LineTo)
    // We subtract 1 from the Width() and Height() so that all our drawing is within the area defined
    // by the PRectangle. Otherwise, we draw one pixel too far to the right and bottom.
    CGContextAddRect( gc, CGRectMake( rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1 ) );
    CGContextDrawPath( gc, kCGPathFillStroke );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillRectangle(PRectangle rc, ColourAllocated back)
{
  if (gc)
  {
    FillColour(back);
    CGRect rect = PRectangleToCGRect(rc);
    CGContextFillRect(gc, rect);
  }
}

//--------------------------------------------------------------------------------------------------

void drawImageRefCallback(CGImageRef pattern, CGContextRef gc)
{
  CGContextDrawImage(gc, CGRectMake(0, 0, CGImageGetWidth(pattern), CGImageGetHeight(pattern)), pattern);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern)
{
  SurfaceImpl& patternSurface = static_cast<SurfaceImpl &>(surfacePattern);
  
  // For now, assume that copy can only be called on PixMap surfaces. Shows up black.
  CGImageRef image = patternSurface.GetImage();
  if (image == NULL)
  {
    FillRectangle(rc, ColourAllocated(0));
    return;
  }
  
  const CGPatternCallbacks drawImageCallbacks = { 0, 
    reinterpret_cast<CGPatternDrawPatternCallback>(drawImageRefCallback), NULL };
  
  CGPatternRef pattern = CGPatternCreate(image,
                                         CGRectMake(0, 0, patternSurface.bitmapWidth, patternSurface.bitmapHeight),
                                         CGAffineTransformIdentity,
                                         patternSurface.bitmapWidth,
                                         patternSurface.bitmapHeight,
                                         kCGPatternTilingNoDistortion,
                                         true,
                                         &drawImageCallbacks
                                        );
  if (pattern != NULL)
  {
    // Create a pattern color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern( NULL );
    if( colorSpace != NULL ) {
      
      CGContextSaveGState( gc );
      CGContextSetFillColorSpace( gc, colorSpace );
      
      // Unlike the documentation, you MUST pass in a "components" parameter:
      // For coloured patterns it is the alpha value.
      const CGFloat alpha = 1.0;
      CGContextSetFillPattern( gc, pattern, &alpha );
      CGContextFillRect( gc, PRectangleToCGRect( rc ) );
      CGContextRestoreGState( gc );
      // Free the color space, the pattern and image
      CGColorSpaceRelease( colorSpace );
    } /* colorSpace != NULL */
    colorSpace = NULL;
    CGPatternRelease( pattern );
    pattern = NULL;
    CGImageRelease( image );
    image = NULL;
  } /* pattern != NULL */
}

void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
  // This is only called from the margin marker drawing code for SC_MARK_ROUNDRECT
  // The Win32 version does
  //  ::RoundRect(hdc, rc.left + 1, rc.top, rc.right - 1, rc.bottom, 8, 8 );
  // which is a rectangle with rounded corners each having a radius of 4 pixels.
  // It would be almost as good just cutting off the corners with lines at
  // 45 degrees as is done on GTK+.
  
  // Create a rectangle with semicircles at the corners
  const int MAX_RADIUS = 4;
  int radius = Platform::Minimum( MAX_RADIUS, rc.Height()/2 );
  radius = Platform::Minimum( radius, rc.Width()/2 );
  
  // Points go clockwise, starting from just below the top left
  // Corners are kept together, so we can easily create arcs to connect them
  CGPoint corners[4][3] =
  {
    {
      { rc.left, rc.top + radius },
      { rc.left, rc.top },
      { rc.left + radius, rc.top },
    },
    {
      { rc.right - radius - 1, rc.top },
      { rc.right - 1, rc.top },
      { rc.right - 1, rc.top + radius },
    },
    {
      { rc.right - 1, rc.bottom - radius - 1 },
      { rc.right - 1, rc.bottom - 1 },
      { rc.right - radius - 1, rc.bottom - 1 },
    },
    {
      { rc.left + radius, rc.bottom - 1 },
      { rc.left, rc.bottom - 1 },
      { rc.left, rc.bottom - radius - 1 },
    },
  };
  
  // Align the points in the middle of the pixels
  for( int i = 0; i < 4*3; ++ i )
  {
    CGPoint* c = (CGPoint*) corners;
    c[i].x += 0.5;
    c[i].y += 0.5;
  }
  
  PenColour( fore );
  FillColour( back );
  
  // Move to the last point to begin the path
  CGContextBeginPath( gc );
  CGContextMoveToPoint( gc, corners[3][2].x, corners[3][2].y );
  
  for ( int i = 0; i < 4; ++ i )
  {
    CGContextAddLineToPoint( gc, corners[i][0].x, corners[i][0].y );
    CGContextAddArcToPoint( gc, corners[i][1].x, corners[i][1].y, corners[i][2].x, corners[i][2].y, radius );
  }
  
  // Close the path to enclose it for stroking and for filling, then draw it
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
}

void Scintilla::SurfaceImpl::AlphaRectangle(PRectangle rc, int /*cornerSize*/, ColourAllocated fill, int alphaFill,
                                            ColourAllocated /*outline*/, int /*alphaOutline*/, int /*flags*/)
{
  if ( gc ) {
    ColourDesired colour( fill.AsLong() );
    
    // Set the Fill color to match
    CGContextSetRGBFillColor( gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, colour.GetBlue() / 255.0, alphaFill / 255.0 );
    CGRect rect = PRectangleToCGRect( rc );
    CGContextFillRect( gc, rect );
  }
}

static CGImageRef ImageFromRGBA(int width, int height, const unsigned char *pixelsImage, bool invert) {
	CGImageRef image = 0;

	// Create an RGB color space.
	CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
	if (colorSpace) {
		const int bitmapBytesPerRow = ((int) width * 4);
		const int bitmapByteCount = (bitmapBytesPerRow * (int) height);
		
		// Create a data provider.
		CGDataProviderRef dataProvider = 0;
		unsigned char *pixelsUpsideDown = 0;
		if (invert) {
			pixelsUpsideDown = new unsigned char[bitmapByteCount];
		
			for (int y=0; y<height; y++) {
				int yInverse = height - y - 1;
				memcpy(pixelsUpsideDown + y * bitmapBytesPerRow,
				       pixelsImage + yInverse * bitmapBytesPerRow,
				       bitmapBytesPerRow);
			}
			
			dataProvider = CGDataProviderCreateWithData(
								NULL, pixelsUpsideDown, bitmapByteCount, NULL);
		} else {
			dataProvider = CGDataProviderCreateWithData(
								NULL, pixelsImage, bitmapByteCount, NULL);
			
		}
		if (dataProvider) {
			// Create the CGImage.
			image = CGImageCreate(width,
							 height,
							 8,
							 8 * 4,
							 bitmapBytesPerRow,
							 colorSpace,
							 kCGImageAlphaLast,
							 dataProvider,
							 NULL,
							 0,
							 kCGRenderingIntentDefault);

			CGDataProviderRelease(dataProvider);
		}
		delete []pixelsUpsideDown;
		
		// The image retains the color space, so we can release it.
		CGColorSpaceRelease(colorSpace);
	}
	return image;
}

void SurfaceImpl::DrawRGBAImage(PRectangle /* rc */, int width, int height, const unsigned char *pixelsImage) {
	CGImageRef image = ImageFromRGBA(width, height, pixelsImage, true);
	if (image) {
		//CGContextSaveGState(gc);
		//CGRect dst = PRectangleToCGRect(rc);
		//CGContextClipToRect(gc, dst);
		CGRect drawRect = CGRectMake (0, 0, width, height);
		CGContextDrawImage(gc, drawRect, image);
		//CGContextRestoreGState (gc);
		
		CGImageRelease(image);
	}
}

void SurfaceImpl::Ellipse(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
  // Drawing an ellipse with bezier curves. Code modified from:
  // http://www.codeguru.com/gdi/ellipse.shtml
  // MAGICAL CONSTANT to map ellipse to beziers 2/3*(sqrt(2)-1)
  const double EToBConst = 0.2761423749154;
  
  CGSize offset = CGSizeMake((int)(rc.Width() * EToBConst), (int)(rc.Height() * EToBConst));
  CGPoint centre = CGPointMake((rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2);
  
  // The control point array
  CGPoint cCtlPt[13];
  
  // Assign values to all the control points
  cCtlPt[0].x  =
  cCtlPt[1].x  =
  cCtlPt[11].x =
  cCtlPt[12].x = rc.left + 0.5;
  cCtlPt[5].x  =
  cCtlPt[6].x  =
  cCtlPt[7].x  = rc.right - 0.5;
  cCtlPt[2].x  =
  cCtlPt[10].x = centre.x - offset.width + 0.5;
  cCtlPt[4].x  =
  cCtlPt[8].x  = centre.x + offset.width + 0.5;
  cCtlPt[3].x  =
  cCtlPt[9].x  = centre.x + 0.5;
  
  cCtlPt[2].y  =
  cCtlPt[3].y  =
  cCtlPt[4].y  = rc.top + 0.5;
  cCtlPt[8].y  =
  cCtlPt[9].y  =
  cCtlPt[10].y = rc.bottom - 0.5;
  cCtlPt[7].y  =
  cCtlPt[11].y = centre.y + offset.height + 0.5;
  cCtlPt[1].y =
  cCtlPt[5].y  = centre.y - offset.height + 0.5;
  cCtlPt[0].y =
  cCtlPt[12].y =
  cCtlPt[6].y  = centre.y + 0.5;
  
  FillColour(back);
  PenColour(fore);
  
  CGContextBeginPath( gc );
  CGContextMoveToPoint( gc, cCtlPt[0].x, cCtlPt[0].y );
  
  for ( int i = 1; i < 13; i += 3 )
  {
    CGContextAddCurveToPoint( gc, cCtlPt[i].x, cCtlPt[i].y, cCtlPt[i+1].x, cCtlPt[i+1].y, cCtlPt[i+2].x, cCtlPt[i+2].y );
  }
  
  // Close the path to enclose it for stroking and for filling, then draw it
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
}

void SurfaceImpl::CopyImageRectangle(Surface &surfaceSource, PRectangle srcRect, PRectangle dstRect)
{
  SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
  CGImageRef image = source.GetImage();
  
  CGRect src = PRectangleToCGRect(srcRect);
  CGRect dst = PRectangleToCGRect(dstRect);
  
  /* source from QuickDrawToQuartz2D.pdf on developer.apple.com */
  float w = (float) CGImageGetWidth(image);
  float h = (float) CGImageGetHeight(image);
  CGRect drawRect = CGRectMake (0, 0, w, h);
  if (!CGRectEqualToRect (src, dst))
  {
    float sx = CGRectGetWidth(dst) / CGRectGetWidth(src);
    float sy = CGRectGetHeight(dst) / CGRectGetHeight(src);
    float dx = CGRectGetMinX(dst) - (CGRectGetMinX(src) * sx);
    float dy = CGRectGetMinY(dst) - (CGRectGetMinY(src) * sy);
    drawRect = CGRectMake (dx, dy, w*sx, h*sy);
  }
  CGContextSaveGState (gc);
  CGContextClipToRect (gc, dst);
  CGContextDrawImage (gc, drawRect, image);
  CGContextRestoreGState (gc);
  CGImageRelease(image);
}

void SurfaceImpl::Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSource) {
  // Maybe we have to make the Surface two contexts:
  // a bitmap context which we do all the drawing on, and then a "real" context
  // which we copy the output to when we call "Synchronize". Ugh! Gross and slow!
  
  // For now, assume that copy can only be called on PixMap surfaces
  SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
  
  // Get the CGImageRef
  CGImageRef image = source.GetImage();
  // If we could not get an image reference, fill the rectangle black
  if ( image == NULL )
  {
    FillRectangle( rc, ColourAllocated( 0 ) );
    return;
  }
  
  // Now draw the image on the surface
  
  // Some fancy clipping work is required here: draw only inside of rc
  CGContextSaveGState( gc );
  CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
  
  //Platform::DebugPrintf(stderr, "Copy: CGContextDrawImage: (%d, %d) - (%d X %d)\n", rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight );
  CGContextDrawImage( gc, CGRectMake( rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight ), image );
  
  // Undo the clipping fun
  CGContextRestoreGState( gc );
  
  // Done with the image
  CGImageRelease( image );
  image = NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len,
                                 ColourAllocated fore, ColourAllocated back)
{
  FillRectangle(rc, back);
  DrawTextTransparent(rc, font_, ybase, s, len, fore);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len,
                                  ColourAllocated fore, ColourAllocated back)
{
  CGContextSaveGState(gc);
  CGContextClipToRect(gc, PRectangleToCGRect(rc));
  DrawTextNoClip(rc, font_, ybase, s, len, fore, back);
  CGContextRestoreGState(gc);
}

//--------------------------------------------------------------------------------------------------

CFStringEncoding EncodingFromCharacterSet(bool unicode, int characterSet)
{
  if (unicode)
    return kCFStringEncodingUTF8;

  // Unsupported -> Latin1 as reasonably safe
  enum { notSupported = kCFStringEncodingISOLatin1};

  switch (characterSet)
  {
  case SC_CHARSET_ANSI:
    return kCFStringEncodingISOLatin1;
  case SC_CHARSET_DEFAULT:
    return kCFStringEncodingISOLatin1;
  case SC_CHARSET_BALTIC:
    return kCFStringEncodingWindowsBalticRim;
  case SC_CHARSET_CHINESEBIG5:
    return kCFStringEncodingBig5;
  case SC_CHARSET_EASTEUROPE:
    return kCFStringEncodingWindowsLatin2;
  case SC_CHARSET_GB2312:
    return kCFStringEncodingGB_18030_2000;
  case SC_CHARSET_GREEK:
    return kCFStringEncodingWindowsGreek;
  case SC_CHARSET_HANGUL:
    return kCFStringEncodingEUC_KR;
  case SC_CHARSET_MAC:
    return kCFStringEncodingMacRoman;
  case SC_CHARSET_OEM:
    return kCFStringEncodingISOLatin1;
  case SC_CHARSET_RUSSIAN:
    return kCFStringEncodingKOI8_R;
  case SC_CHARSET_CYRILLIC:
    return kCFStringEncodingWindowsCyrillic;
  case SC_CHARSET_SHIFTJIS:
    return kCFStringEncodingShiftJIS;
  case SC_CHARSET_SYMBOL:
    return kCFStringEncodingMacSymbol;
  case SC_CHARSET_TURKISH:
    return kCFStringEncodingWindowsLatin5;
  case SC_CHARSET_JOHAB:
    return kCFStringEncodingWindowsKoreanJohab;
  case SC_CHARSET_HEBREW:
    return kCFStringEncodingWindowsHebrew;
  case SC_CHARSET_ARABIC:
    return kCFStringEncodingWindowsArabic;
  case SC_CHARSET_VIETNAMESE:
    return kCFStringEncodingWindowsVietnamese;
  case SC_CHARSET_THAI:
    return kCFStringEncodingISOLatinThai;
  case SC_CHARSET_8859_15:
    return kCFStringEncodingISOLatin1;
  default:
    return notSupported;
  }
}

void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len, 
                                      ColourAllocated fore)
{
	CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_));
	ColourDesired colour(fore.AsLong());
	CGColorRef color = CGColorCreateGenericRGB(colour.GetRed()/255.0,colour.GetGreen()/255.0,colour.GetBlue()/255.0,1.0);

	QuartzTextStyle* style = reinterpret_cast<QuartzTextStyle*>(font_.GetID());
	style->setCTStyleColor(color);
	
	CGColorRelease(color);

	textLayout->setText (reinterpret_cast<const UInt8*>(s), len, encoding, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
	textLayout->draw(rc.left, ybase);
}

static size_t utf8LengthFromLead(unsigned char uch) {
	if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
		return 4;
	} else if (uch >= (0x80 + 0x40 + 0x20)) {
		return 3;
	} else if (uch >= (0x80)) {
		return 2;
	} else {
		return 1;
	}
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, int *positions)
{
	CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_));
	textLayout->setText (reinterpret_cast<const UInt8*>(s), len, encoding, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
	
	CTLineRef mLine = textLayout->getCTLine();
	assert(mLine != NULL);
	
	if (unicodeMode) {
		// Map the widths given for UTF-16 characters back onto the UTF-8 input string
		CFIndex fit = textLayout->getStringLength();
		int ui=0;
		const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
		int i=0;
		while (ui<fit) {
			size_t lenChar = utf8LengthFromLead(us[i]);
			size_t codeUnits = (lenChar < 4) ? 1 : 2;
			CGFloat xPosition = CTLineGetOffsetForStringIndex(mLine, ui+1, NULL);
			for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
				positions[i++] = static_cast<int>(lround(xPosition));
			}
			ui += codeUnits;
		}
		int lastPos = 0;
		if (i > 0)
			lastPos = positions[i-1];
		while (i<len) {
			positions[i++] = lastPos;
		}
	} else if (codePage) {
		int ui = 0;
		for (int i=0;i<len;) {
			size_t lenChar = Platform::IsDBCSLeadByte(codePage, s[i]) ? 2 : 1;
			CGFloat xPosition = CTLineGetOffsetForStringIndex(mLine, ui+1, NULL);
			for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
				positions[i++] = static_cast<int>(lround(xPosition));
			}
			ui++;
		}
	} else {	// Single byte encoding
		for (int i=0;i<len;i++) {
			CGFloat xPosition = CTLineGetOffsetForStringIndex(mLine, i+1, NULL);
			positions[i] = static_cast<int>(lround(xPosition));
		}
	}

}

int SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
  if (font_.GetID())
  {
    CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_));
    textLayout->setText (reinterpret_cast<const UInt8*>(s), len, encoding, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
    
	return textLayout->MeasureStringWidth();
  }
  return 1;
}

int SurfaceImpl::WidthChar(Font &font_, char ch) {
  char str[2] = { ch, '\0' };
  if (font_.GetID())
  {
    CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_));
    textLayout->setText (reinterpret_cast<const UInt8*>(str), 1, encoding, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
    
    return textLayout->MeasureStringWidth();
  }
  else
    return 1;
}

// This string contains a good range of characters to test for size.
const char sizeString[] = "`~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890"
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

int SurfaceImpl::Ascent(Font &font_) {
  if (!font_.GetID())
    return 1;
  
	float ascent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAscent();
	return ascent + 0.5;

}

int SurfaceImpl::Descent(Font &font_) {
  if (!font_.GetID())
    return 1;
  
	float descent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getDescent();
	return descent + 0.5;

}

int SurfaceImpl::InternalLeading(Font &) {
  return 0;
}

int SurfaceImpl::ExternalLeading(Font &font_) {
  if (!font_.GetID())
    return 1;
  
	float leading = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getLeading();
	return leading + 0.5;

}

int SurfaceImpl::Height(Font &font_) {

	int ht = Ascent(font_) + Descent(font_);
	return ht;
}

int SurfaceImpl::AverageCharWidth(Font &font_) {
  
  if (!font_.GetID())
    return 1;
  
  const int sizeStringLength = (sizeof( sizeString ) / sizeof( sizeString[0] ) - 1);
  int width = WidthText( font_, sizeString, sizeStringLength  );
  
  return (int) ((width / (float) sizeStringLength) + 0.5);
}

int SurfaceImpl::SetPalette(Scintilla::Palette *, bool) {
  // Mac OS X is always true colour (I think) so this doesn't matter
  return 0;
}

void SurfaceImpl::SetClip(PRectangle rc) {
  CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
}

void SurfaceImpl::FlushCachedState() {
  CGContextSynchronize( gc );
}

void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
  unicodeMode = unicodeMode_;
}

void SurfaceImpl::SetDBCSMode(int codePage_) {
  if (codePage_ && (codePage_ != SC_CP_UTF8))
    codePage = codePage_;
}

Surface *Surface::Allocate()
{
  return new SurfaceImpl();
}

//----------------- Window -------------------------------------------------------------------------

// Cocoa uses different types for windows and views, so a Window may
// be either an NSWindow or NSView and the code will check the type
// before performing an action.

Window::~Window()
{
}

//--------------------------------------------------------------------------------------------------

void Window::Destroy()
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSWindow class]])
    {
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      [win release];
    }
  }
  wid = 0;
}

//--------------------------------------------------------------------------------------------------

bool Window::HasFocus()
{
  NSView* container = reinterpret_cast<NSView*>(wid);
  return [[container window] firstResponder] == container;
}

//--------------------------------------------------------------------------------------------------

static int ScreenMax(NSWindow* win)
{
  NSScreen* screen = [win screen];
  NSRect frame = [screen frame];
  return frame.origin.y + frame.size.height;
}

PRectangle Window::GetPosition()
{
  if (wid)
  {
    NSRect rect;
    id idWin = reinterpret_cast<id>(wid);
    NSWindow* win;
    if ([idWin isKindOfClass: [NSView class]])
    {
      // NSView
      NSView* view = reinterpret_cast<NSView*>(idWin);
      win = [view window];
      rect = [view bounds];
      rect = [view convertRectToBase: rect];
      rect.origin = [win convertBaseToScreen:rect.origin];
    }
    else
    {
      // NSWindow
      win = reinterpret_cast<NSWindow*>(idWin);
      rect = [win frame];
    }
    int screenHeight = ScreenMax(win);
    // Invert screen positions to match Scintilla
    return PRectangle(
        NSMinX(rect), screenHeight - NSMaxY(rect),
        NSMaxX(rect), screenHeight - NSMinY(rect));
  }
  else
  {
    return PRectangle(0, 0, 1, 1);
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetPosition(PRectangle rc)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSView class]])
    {
      // NSView
      // Moves this view inside the parent view
      NSRect nsrc = NSMakeRect(rc.left, rc.bottom, rc.Width(), rc.Height());
      NSView* view = reinterpret_cast<NSView*>(idWin);
      nsrc.origin = [[view window] convertScreenToBase:nsrc.origin];
      [view setFrame: nsrc];
    }
    else
    {
      // NSWindow
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      int screenHeight = ScreenMax(win);
      NSRect nsrc = NSMakeRect(rc.left, screenHeight - rc.bottom,
          rc.Width(), rc.Height());
      [win setFrame: nsrc display:YES];
    }
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetPositionRelative(PRectangle rc, Window window)
{
  PRectangle rcOther = window.GetPosition();
  rc.left += rcOther.left;
  rc.right += rcOther.left;
  rc.top += rcOther.top;
  rc.bottom += rcOther.top;
  SetPosition(rc);
}

//--------------------------------------------------------------------------------------------------

PRectangle Window::GetClientPosition()
{
  // This means, in MacOS X terms, get the "frame bounds". Call GetPosition, just like on Win32.
  return GetPosition();
}

//--------------------------------------------------------------------------------------------------

void Window::Show(bool show)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSWindow class]])
    {
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      if (show)
      {
        [win orderFront:nil];
      }
      else
      {
        [win orderOut:nil];
      }
    }
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Invalidates the entire window or view so it is completely redrawn.
 */
void Window::InvalidateAll()
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    NSView* container;
    if ([idWin isKindOfClass: [NSView class]])
    {
      container = reinterpret_cast<NSView*>(idWin);
    }
    else
    {
      // NSWindow
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      container = reinterpret_cast<NSView*>([win contentView]);
      container.needsDisplay = YES;
    }
    container.needsDisplay = YES;
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Invalidates part of the window or view so only this part redrawn.
 */
void Window::InvalidateRectangle(PRectangle rc)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    NSView* container;
    if ([idWin isKindOfClass: [NSView class]])
    {
      container = reinterpret_cast<NSView*>(idWin);
    }
    else
    {
      // NSWindow
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      container = reinterpret_cast<NSView*>([win contentView]);
    }
    [container setNeedsDisplayInRect: PRectangleToNSRect(rc)];
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetFont(Font&)
{
  // Implemented on list subclass on Cocoa.
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts the Scintilla cursor enum into an NSCursor and stores it in the associated NSView,
 * which then will take care to set up a new mouse tracking rectangle.
 */
void Window::SetCursor(Cursor curs)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isMemberOfClass: [InnerView class]])
    {
      InnerView* container = reinterpret_cast<InnerView*>(idWin);
      [container setCursor: curs];
    }
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetTitle(const char* s)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSWindow class]])
    {
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      NSString* sTitle = [NSString stringWithUTF8String:s];
      [win setTitle:sTitle];
    }
  }
}

//--------------------------------------------------------------------------------------------------

PRectangle Window::GetMonitorRect(Point)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSWindow class]])
    {
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      NSScreen* screen = [win screen];
      NSRect rect = [screen frame];
      int screenHeight = rect.origin.y + rect.size.height;
      // Invert screen positions to match Scintilla
      return PRectangle(
          NSMinX(rect), screenHeight - NSMaxY(rect),
          NSMaxX(rect), screenHeight - NSMinY(rect));
    }
  }
  return PRectangle();
}

//----------------- ImageFromXPM -------------------------------------------------------------------

// Convert an XPM image into an NSImage for use with Cocoa

static NSImage* ImageFromXPM(XPM* pxpm)
{
  NSImage* img = nil;
  if (pxpm)
  {
    const int width = pxpm->GetWidth();
    const int height = pxpm->GetHeight();
    PRectangle rcxpm(0, 0, width, height);
    Surface* surfaceXPM = Surface::Allocate();
    if (surfaceXPM)
    {
      surfaceXPM->InitPixMap(width, height, NULL, NULL);
      SurfaceImpl* surfaceIXPM = static_cast<SurfaceImpl*>(surfaceXPM);
      CGContextClearRect(surfaceIXPM->GetContext(), CGRectMake(0, 0, width, height));
      pxpm->Draw(surfaceXPM, rcxpm);
      img = [NSImage alloc];
      [img autorelease];
      CGImageRef imageRef = surfaceIXPM->GetImage();
      [img initWithSize:NSZeroSize];
      NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef];
      [img addRepresentation: bitmapRep];
      [bitmapRep release];
      CGImageRelease(imageRef);
      delete surfaceXPM;
    }
  }
  return img;
}

//----------------- ListBox ------------------------------------------------------------------------

ListBox::ListBox()
{
}

//--------------------------------------------------------------------------------------------------

ListBox::~ListBox()
{
}

//--------------------------------------------------------------------------------------------------

struct RowData
{
  int type;
  std::string text;
  RowData(int type_, const char* text_) :
    type(type_), text(text_)
  {
  }
};

class LinesData
{
  std::vector<RowData> lines;
public:
  LinesData()
  {
  }
  ~LinesData()
  {
  }
  int Length() const
  {
    return static_cast<int>(lines.size());
  }
  void Clear()
  {
    lines.clear();
  }
  void Add(int /* index */, int type, char* str)
  {
    lines.push_back(RowData(type, str));
  }
  int GetType(size_t index) const
  {
    if (index < lines.size())
    {
      return lines[index].type;
    }
    else
    {
      return 0;
    }
  }
  const char* GetString(size_t index) const
  {
    if (index < lines.size())
    {
      return lines[index].text.c_str();
    }
    else
    {
      return 0;
    }
  }
};

class ListBoxImpl;

@interface AutoCompletionDataSource :
NSObject <NSTableViewDataSource>
{
  ListBoxImpl* box;<commit_msg>Specify that AutoCompletionDataSource implements NSTableViewDataSource protocol only when building for later than 10.5 so SDK has to know about that protocol.<commit_after>/**
 * Scintilla source code edit control
 * PlatCocoa.mm - implementation of platform facilities on MacOS X/Cocoa
 *
 * Written by <NAME>
 * Based on PlatMacOSX.cxx
 * Based on work by <NAME> (c) 2002 <<EMAIL>>
 * Based on PlatGTK.cxx Copyright 1998-2002 by <NAME> <<EMAIL>>
 * The License.txt file describes the conditions under which this software may be distributed.
 *
 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
 * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt).
 */

#import <ScintillaView.h>

#include "PlatCocoa.h"

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <assert.h>
#include <sys/time.h>
#include <stdexcept>
#include <map>

#include "XPM.h"

#import <Foundation/NSGeometry.h>

#import <Carbon/Carbon.h> // Temporary

using namespace Scintilla;

extern sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam);

//--------------------------------------------------------------------------------------------------

/**
 * Converts a PRectangle as used by Scintilla to standard Obj-C NSRect structure .
 */
NSRect PRectangleToNSRect(PRectangle& rc)
{
  return NSMakeRect(rc.left, rc.top, rc.Width(), rc.Height());
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts an NSRect as used by the system to a native Scintilla rectangle.
 */
PRectangle NSRectToPRectangle(NSRect& rc)
{
  return PRectangle(rc.origin.x, rc.origin.y, rc.size.width + rc.origin.x, rc.size.height + rc.origin.y);
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts a PRctangle as used by Scintilla to a Quartz-style rectangle.
 */
inline CGRect PRectangleToCGRect(PRectangle& rc)
{
  return CGRectMake(rc.left, rc.top, rc.Width(), rc.Height());
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts a Quartz-style rectangle to a PRectangle structure as used by Scintilla.
 */
inline PRectangle CGRectToPRectangle(const CGRect& rect)
{
  PRectangle rc;
  rc.left = (int)(rect.origin.x + 0.5);
  rc.top = (int)(rect.origin.y + 0.5);
  rc.right = (int)(rect.origin.x + rect.size.width + 0.5);
  rc.bottom = (int)(rect.origin.y + rect.size.height + 0.5);
  return rc;
}

//----------------- Point --------------------------------------------------------------------------

/**
 * Converts a point given as a long into a native Point structure.
 */
Scintilla::Point Scintilla::Point::FromLong(long lpoint)
{
  return Scintilla::Point(
                          Platform::LowShortFromLong(lpoint),
                          Platform::HighShortFromLong(lpoint)
                          );
}

//----------------- Palette ------------------------------------------------------------------------

// The Palette implementation is only here because we would get linker errors if not.
// We don't use indexed colors in ScintillaCocoa.

Scintilla::Palette::Palette()
{
}

//--------------------------------------------------------------------------------------------------

Scintilla::Palette::~Palette()
{
}

//--------------------------------------------------------------------------------------------------

void Scintilla::Palette::Release()
{
}

//--------------------------------------------------------------------------------------------------

/**
 * Used to transform a given color, if needed. If the caller tries to find a color that matches the
 * desired color then we simply pass it on, as we support the full color space.
 */
void Scintilla::Palette::WantFind(ColourPair &cp, bool want)
{
  if (!want)
    cp.allocated.Set(cp.desired.AsLong());
  
  // Don't do anything if the caller wants the color it has already set.
}

//--------------------------------------------------------------------------------------------------

void Scintilla::Palette::Allocate(Window&)
{
  // Nothing to allocate as we don't use palettes.
}

//----------------- Font ---------------------------------------------------------------------------

Font::Font(): fid(0)
{
}

//--------------------------------------------------------------------------------------------------

Font::~Font()
{
  Release();
}

//--------------------------------------------------------------------------------------------------

static int FontCharacterSet(Font &f) {
	return reinterpret_cast<QuartzTextStyle *>(f.GetID())->getCharacterSet();
}

/**
 * Creates a CTFontRef with the given properties.
 */
void Font::Create(const char *faceName, int characterSet, int size, bool bold, bool italic, 
                  int /* extraFontFlag */)
{
	Release();

	QuartzTextStyle* style = new QuartzTextStyle();
	fid = style;

	// Create the font with attributes
	QuartzFont font(faceName, strlen(faceName), size, bold, italic);
	CTFontRef fontRef = font.getFontID();
	style->setFontRef(fontRef, characterSet);
}

//--------------------------------------------------------------------------------------------------

void Font::Release()
{
  if (fid)
    delete reinterpret_cast<QuartzTextStyle*>( fid );
  fid = 0;
}

//----------------- SurfaceImpl --------------------------------------------------------------------

SurfaceImpl::SurfaceImpl()
{
  unicodeMode = true;
  x = 0;
  y = 0;
  gc = NULL;

  textLayout = new QuartzTextLayout(NULL);
  codePage = 0;
  verticalDeviceResolution = 0;

  bitmapData = NULL; // Release will try and delete bitmapData if != NULL
  bitmapWidth = 0;
  bitmapHeight = 0;

  Release();
}

//--------------------------------------------------------------------------------------------------

SurfaceImpl::~SurfaceImpl()
{
  Release();
  delete textLayout;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Release()
{
  textLayout->setContext (NULL);
  if ( bitmapData != NULL )
  {
    delete[] bitmapData;
    // We only "own" the graphics context if we are a bitmap context
    if (gc != NULL)
      CGContextRelease(gc);
  }
  bitmapData = NULL;
  gc = NULL;
  
  bitmapWidth = 0;
  bitmapHeight = 0;
  x = 0;
  y = 0;
}

//--------------------------------------------------------------------------------------------------

bool SurfaceImpl::Initialised()
{
  // We are initalised if the graphics context is not null
  return gc != NULL;// || port != NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Init(WindowID)
{
  // To be able to draw, the surface must get a CGContext handle.  We save the graphics port,
  // then aquire/release the context on an as-need basis (see above).
  // XXX Docs on QDBeginCGContext are light, a better way to do this would be good.
  // AFAIK we should not hold onto a context retrieved this way, thus the need for
  // aquire/release of the context.
  
  Release();
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Init(SurfaceID sid, WindowID)
{
  Release();
  gc = reinterpret_cast<CGContextRef>(sid);
  CGContextSetLineWidth(gc, 1.0);
  textLayout->setContext(gc);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::InitPixMap(int width, int height, Surface* /* surface_ */, WindowID /* wid */)
{
  Release();
  
  // Create a new bitmap context, along with the RAM for the bitmap itself
  bitmapWidth = width;
  bitmapHeight = height;
  
  const int bitmapBytesPerRow = (width * BYTES_PER_PIXEL);
  const int bitmapByteCount = (bitmapBytesPerRow * height);
  
  // Create an RGB color space.
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  if (colorSpace == NULL)
    return;
  
  // Create the bitmap.
  bitmapData = new uint8_t[bitmapByteCount];
  if (bitmapData != NULL)
  {
    // create the context
    gc = CGBitmapContextCreate(bitmapData,
                               width,
                               height,
                               BITS_PER_COMPONENT,
                               bitmapBytesPerRow,
                               colorSpace,
                               kCGImageAlphaPremultipliedLast);
    
    if (gc == NULL)
    {
      // the context couldn't be created for some reason,
      // and we have no use for the bitmap without the context
      delete[] bitmapData;
      bitmapData = NULL;
    }
    textLayout->setContext (gc);
  }
  
  // the context retains the color space, so we can release it
  CGColorSpaceRelease(colorSpace);
  
  if (gc != NULL && bitmapData != NULL)
  {
    // "Erase" to white.
    CGContextClearRect( gc, CGRectMake( 0, 0, width, height ) );
    CGContextSetRGBFillColor( gc, 1.0, 1.0, 1.0, 1.0 );
    CGContextFillRect( gc, CGRectMake( 0, 0, width, height ) );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::PenColour(ColourAllocated fore)
{
  if (gc)
  {
    ColourDesired colour(fore.AsLong());
    
    // Set the Stroke color to match
    CGContextSetRGBStrokeColor(gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, 
                               colour.GetBlue() / 255.0, 1.0 );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillColour(const ColourAllocated& back)
{
  if (gc)
  {
    ColourDesired colour(back.AsLong());
    
    // Set the Fill color to match
    CGContextSetRGBFillColor(gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, 
                             colour.GetBlue() / 255.0, 1.0 );
  }
}

//--------------------------------------------------------------------------------------------------

CGImageRef SurfaceImpl::GetImage()
{
  // For now, assume that GetImage can only be called on PixMap surfaces.
  if (bitmapData == NULL)
    return NULL;
  
  CGContextFlush(gc);
  
  // Create an RGB color space.
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  if( colorSpace == NULL )
    return NULL;
  
  const int bitmapBytesPerRow = ((int) bitmapWidth * BYTES_PER_PIXEL);
  const int bitmapByteCount = (bitmapBytesPerRow * (int) bitmapHeight);
  
  // Make a copy of the bitmap data for the image creation and divorce it
  // From the SurfaceImpl lifetime
  CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, bitmapData, bitmapByteCount);

  // Create a data provider.
  CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef);

  CGImageRef image = NULL;
  if (dataProvider != NULL)
  {
    // Create the CGImage.
    image = CGImageCreate(bitmapWidth,
                          bitmapHeight,
                          BITS_PER_COMPONENT,
                          BITS_PER_PIXEL,
                          bitmapBytesPerRow,
                          colorSpace,
                          kCGImageAlphaPremultipliedLast,
                          dataProvider,
                          NULL,
                          0,
                          kCGRenderingIntentDefault);
  }
  
  // The image retains the color space, so we can release it.
  CGColorSpaceRelease(colorSpace);
  colorSpace = NULL;
  
  // Done with the data provider.
  CGDataProviderRelease(dataProvider);
  dataProvider = NULL;
  
  // Done with the data provider.
  CFRelease(dataRef);
    
  return image;
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the vertical logical device resolution of the main monitor.
 */
int SurfaceImpl::LogPixelsY()
{
  if (verticalDeviceResolution == 0)
  {
    NSSize deviceResolution = [[[[NSScreen mainScreen] deviceDescription]
    objectForKey: NSDeviceResolution] sizeValue];
    verticalDeviceResolution = (int) deviceResolution.height;
  }
  return verticalDeviceResolution;
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts the logical font height (in dpi) into a pixel height for the current main screen.
 */
int SurfaceImpl::DeviceHeightFont(int points)
{
  int logPix = LogPixelsY();
  return (points * logPix + logPix / 2) / 72;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::MoveTo(int x_, int y_)
{
  x = x_;
  y = y_;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::LineTo(int x_, int y_)
{
  CGContextBeginPath( gc );
  
  // Because Quartz is based on floating point, lines are drawn with half their colour
  // on each side of the line. Integer coordinates specify the INTERSECTION of the pixel
  // divison lines. If you specify exact pixel values, you get a line that
  // is twice as thick but half as intense. To get pixel aligned rendering,
  // we render the "middle" of the pixels by adding 0.5 to the coordinates.
  CGContextMoveToPoint( gc, x + 0.5, y + 0.5 );
  CGContextAddLineToPoint( gc, x_ + 0.5, y_ + 0.5 );
  CGContextStrokePath( gc );
  x = x_;
  y = y_;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::Polygon(Scintilla::Point *pts, int npts, ColourAllocated fore,
                          ColourAllocated back)
{
  // Allocate memory for the array of points.
  CGPoint *points = new CGPoint[npts];
  
  for (int i = 0;i < npts;i++)
  {
    // Quartz floating point issues: plot the MIDDLE of the pixels
    points[i].x = pts[i].x + 0.5;
    points[i].y = pts[i].y + 0.5;
  }
  
  CGContextBeginPath(gc);
  
  // Set colours
  FillColour(back);
  PenColour(fore);
  
  // Draw the polygon
  CGContextAddLines(gc, points, npts);
  
  // Explicitly close the path, so it is closed for stroking AND filling (implicit close = filling only)
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
  
  // Deallocate memory.
  delete points;
  points = NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::RectangleDraw(PRectangle rc, ColourAllocated fore, ColourAllocated back)
{
  if (gc)
  {
    CGContextBeginPath( gc );
    FillColour(back);
    PenColour(fore);
    
    // Quartz integer -> float point conversion fun (see comment in SurfaceImpl::LineTo)
    // We subtract 1 from the Width() and Height() so that all our drawing is within the area defined
    // by the PRectangle. Otherwise, we draw one pixel too far to the right and bottom.
    CGContextAddRect( gc, CGRectMake( rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1 ) );
    CGContextDrawPath( gc, kCGPathFillStroke );
  }
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillRectangle(PRectangle rc, ColourAllocated back)
{
  if (gc)
  {
    FillColour(back);
    CGRect rect = PRectangleToCGRect(rc);
    CGContextFillRect(gc, rect);
  }
}

//--------------------------------------------------------------------------------------------------

void drawImageRefCallback(CGImageRef pattern, CGContextRef gc)
{
  CGContextDrawImage(gc, CGRectMake(0, 0, CGImageGetWidth(pattern), CGImageGetHeight(pattern)), pattern);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern)
{
  SurfaceImpl& patternSurface = static_cast<SurfaceImpl &>(surfacePattern);
  
  // For now, assume that copy can only be called on PixMap surfaces. Shows up black.
  CGImageRef image = patternSurface.GetImage();
  if (image == NULL)
  {
    FillRectangle(rc, ColourAllocated(0));
    return;
  }
  
  const CGPatternCallbacks drawImageCallbacks = { 0, 
    reinterpret_cast<CGPatternDrawPatternCallback>(drawImageRefCallback), NULL };
  
  CGPatternRef pattern = CGPatternCreate(image,
                                         CGRectMake(0, 0, patternSurface.bitmapWidth, patternSurface.bitmapHeight),
                                         CGAffineTransformIdentity,
                                         patternSurface.bitmapWidth,
                                         patternSurface.bitmapHeight,
                                         kCGPatternTilingNoDistortion,
                                         true,
                                         &drawImageCallbacks
                                        );
  if (pattern != NULL)
  {
    // Create a pattern color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern( NULL );
    if( colorSpace != NULL ) {
      
      CGContextSaveGState( gc );
      CGContextSetFillColorSpace( gc, colorSpace );
      
      // Unlike the documentation, you MUST pass in a "components" parameter:
      // For coloured patterns it is the alpha value.
      const CGFloat alpha = 1.0;
      CGContextSetFillPattern( gc, pattern, &alpha );
      CGContextFillRect( gc, PRectangleToCGRect( rc ) );
      CGContextRestoreGState( gc );
      // Free the color space, the pattern and image
      CGColorSpaceRelease( colorSpace );
    } /* colorSpace != NULL */
    colorSpace = NULL;
    CGPatternRelease( pattern );
    pattern = NULL;
    CGImageRelease( image );
    image = NULL;
  } /* pattern != NULL */
}

void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
  // This is only called from the margin marker drawing code for SC_MARK_ROUNDRECT
  // The Win32 version does
  //  ::RoundRect(hdc, rc.left + 1, rc.top, rc.right - 1, rc.bottom, 8, 8 );
  // which is a rectangle with rounded corners each having a radius of 4 pixels.
  // It would be almost as good just cutting off the corners with lines at
  // 45 degrees as is done on GTK+.
  
  // Create a rectangle with semicircles at the corners
  const int MAX_RADIUS = 4;
  int radius = Platform::Minimum( MAX_RADIUS, rc.Height()/2 );
  radius = Platform::Minimum( radius, rc.Width()/2 );
  
  // Points go clockwise, starting from just below the top left
  // Corners are kept together, so we can easily create arcs to connect them
  CGPoint corners[4][3] =
  {
    {
      { rc.left, rc.top + radius },
      { rc.left, rc.top },
      { rc.left + radius, rc.top },
    },
    {
      { rc.right - radius - 1, rc.top },
      { rc.right - 1, rc.top },
      { rc.right - 1, rc.top + radius },
    },
    {
      { rc.right - 1, rc.bottom - radius - 1 },
      { rc.right - 1, rc.bottom - 1 },
      { rc.right - radius - 1, rc.bottom - 1 },
    },
    {
      { rc.left + radius, rc.bottom - 1 },
      { rc.left, rc.bottom - 1 },
      { rc.left, rc.bottom - radius - 1 },
    },
  };
  
  // Align the points in the middle of the pixels
  for( int i = 0; i < 4*3; ++ i )
  {
    CGPoint* c = (CGPoint*) corners;
    c[i].x += 0.5;
    c[i].y += 0.5;
  }
  
  PenColour( fore );
  FillColour( back );
  
  // Move to the last point to begin the path
  CGContextBeginPath( gc );
  CGContextMoveToPoint( gc, corners[3][2].x, corners[3][2].y );
  
  for ( int i = 0; i < 4; ++ i )
  {
    CGContextAddLineToPoint( gc, corners[i][0].x, corners[i][0].y );
    CGContextAddArcToPoint( gc, corners[i][1].x, corners[i][1].y, corners[i][2].x, corners[i][2].y, radius );
  }
  
  // Close the path to enclose it for stroking and for filling, then draw it
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
}

void Scintilla::SurfaceImpl::AlphaRectangle(PRectangle rc, int /*cornerSize*/, ColourAllocated fill, int alphaFill,
                                            ColourAllocated /*outline*/, int /*alphaOutline*/, int /*flags*/)
{
  if ( gc ) {
    ColourDesired colour( fill.AsLong() );
    
    // Set the Fill color to match
    CGContextSetRGBFillColor( gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, colour.GetBlue() / 255.0, alphaFill / 255.0 );
    CGRect rect = PRectangleToCGRect( rc );
    CGContextFillRect( gc, rect );
  }
}

static CGImageRef ImageFromRGBA(int width, int height, const unsigned char *pixelsImage, bool invert) {
	CGImageRef image = 0;

	// Create an RGB color space.
	CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
	if (colorSpace) {
		const int bitmapBytesPerRow = ((int) width * 4);
		const int bitmapByteCount = (bitmapBytesPerRow * (int) height);
		
		// Create a data provider.
		CGDataProviderRef dataProvider = 0;
		unsigned char *pixelsUpsideDown = 0;
		if (invert) {
			pixelsUpsideDown = new unsigned char[bitmapByteCount];
		
			for (int y=0; y<height; y++) {
				int yInverse = height - y - 1;
				memcpy(pixelsUpsideDown + y * bitmapBytesPerRow,
				       pixelsImage + yInverse * bitmapBytesPerRow,
				       bitmapBytesPerRow);
			}
			
			dataProvider = CGDataProviderCreateWithData(
								NULL, pixelsUpsideDown, bitmapByteCount, NULL);
		} else {
			dataProvider = CGDataProviderCreateWithData(
								NULL, pixelsImage, bitmapByteCount, NULL);
			
		}
		if (dataProvider) {
			// Create the CGImage.
			image = CGImageCreate(width,
							 height,
							 8,
							 8 * 4,
							 bitmapBytesPerRow,
							 colorSpace,
							 kCGImageAlphaLast,
							 dataProvider,
							 NULL,
							 0,
							 kCGRenderingIntentDefault);

			CGDataProviderRelease(dataProvider);
		}
		delete []pixelsUpsideDown;
		
		// The image retains the color space, so we can release it.
		CGColorSpaceRelease(colorSpace);
	}
	return image;
}

void SurfaceImpl::DrawRGBAImage(PRectangle /* rc */, int width, int height, const unsigned char *pixelsImage) {
	CGImageRef image = ImageFromRGBA(width, height, pixelsImage, true);
	if (image) {
		//CGContextSaveGState(gc);
		//CGRect dst = PRectangleToCGRect(rc);
		//CGContextClipToRect(gc, dst);
		CGRect drawRect = CGRectMake (0, 0, width, height);
		CGContextDrawImage(gc, drawRect, image);
		//CGContextRestoreGState (gc);
		
		CGImageRelease(image);
	}
}

void SurfaceImpl::Ellipse(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
  // Drawing an ellipse with bezier curves. Code modified from:
  // http://www.codeguru.com/gdi/ellipse.shtml
  // MAGICAL CONSTANT to map ellipse to beziers 2/3*(sqrt(2)-1)
  const double EToBConst = 0.2761423749154;
  
  CGSize offset = CGSizeMake((int)(rc.Width() * EToBConst), (int)(rc.Height() * EToBConst));
  CGPoint centre = CGPointMake((rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2);
  
  // The control point array
  CGPoint cCtlPt[13];
  
  // Assign values to all the control points
  cCtlPt[0].x  =
  cCtlPt[1].x  =
  cCtlPt[11].x =
  cCtlPt[12].x = rc.left + 0.5;
  cCtlPt[5].x  =
  cCtlPt[6].x  =
  cCtlPt[7].x  = rc.right - 0.5;
  cCtlPt[2].x  =
  cCtlPt[10].x = centre.x - offset.width + 0.5;
  cCtlPt[4].x  =
  cCtlPt[8].x  = centre.x + offset.width + 0.5;
  cCtlPt[3].x  =
  cCtlPt[9].x  = centre.x + 0.5;
  
  cCtlPt[2].y  =
  cCtlPt[3].y  =
  cCtlPt[4].y  = rc.top + 0.5;
  cCtlPt[8].y  =
  cCtlPt[9].y  =
  cCtlPt[10].y = rc.bottom - 0.5;
  cCtlPt[7].y  =
  cCtlPt[11].y = centre.y + offset.height + 0.5;
  cCtlPt[1].y =
  cCtlPt[5].y  = centre.y - offset.height + 0.5;
  cCtlPt[0].y =
  cCtlPt[12].y =
  cCtlPt[6].y  = centre.y + 0.5;
  
  FillColour(back);
  PenColour(fore);
  
  CGContextBeginPath( gc );
  CGContextMoveToPoint( gc, cCtlPt[0].x, cCtlPt[0].y );
  
  for ( int i = 1; i < 13; i += 3 )
  {
    CGContextAddCurveToPoint( gc, cCtlPt[i].x, cCtlPt[i].y, cCtlPt[i+1].x, cCtlPt[i+1].y, cCtlPt[i+2].x, cCtlPt[i+2].y );
  }
  
  // Close the path to enclose it for stroking and for filling, then draw it
  CGContextClosePath( gc );
  CGContextDrawPath( gc, kCGPathFillStroke );
}

void SurfaceImpl::CopyImageRectangle(Surface &surfaceSource, PRectangle srcRect, PRectangle dstRect)
{
  SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
  CGImageRef image = source.GetImage();
  
  CGRect src = PRectangleToCGRect(srcRect);
  CGRect dst = PRectangleToCGRect(dstRect);
  
  /* source from QuickDrawToQuartz2D.pdf on developer.apple.com */
  float w = (float) CGImageGetWidth(image);
  float h = (float) CGImageGetHeight(image);
  CGRect drawRect = CGRectMake (0, 0, w, h);
  if (!CGRectEqualToRect (src, dst))
  {
    float sx = CGRectGetWidth(dst) / CGRectGetWidth(src);
    float sy = CGRectGetHeight(dst) / CGRectGetHeight(src);
    float dx = CGRectGetMinX(dst) - (CGRectGetMinX(src) * sx);
    float dy = CGRectGetMinY(dst) - (CGRectGetMinY(src) * sy);
    drawRect = CGRectMake (dx, dy, w*sx, h*sy);
  }
  CGContextSaveGState (gc);
  CGContextClipToRect (gc, dst);
  CGContextDrawImage (gc, drawRect, image);
  CGContextRestoreGState (gc);
  CGImageRelease(image);
}

void SurfaceImpl::Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSource) {
  // Maybe we have to make the Surface two contexts:
  // a bitmap context which we do all the drawing on, and then a "real" context
  // which we copy the output to when we call "Synchronize". Ugh! Gross and slow!
  
  // For now, assume that copy can only be called on PixMap surfaces
  SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
  
  // Get the CGImageRef
  CGImageRef image = source.GetImage();
  // If we could not get an image reference, fill the rectangle black
  if ( image == NULL )
  {
    FillRectangle( rc, ColourAllocated( 0 ) );
    return;
  }
  
  // Now draw the image on the surface
  
  // Some fancy clipping work is required here: draw only inside of rc
  CGContextSaveGState( gc );
  CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
  
  //Platform::DebugPrintf(stderr, "Copy: CGContextDrawImage: (%d, %d) - (%d X %d)\n", rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight );
  CGContextDrawImage( gc, CGRectMake( rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight ), image );
  
  // Undo the clipping fun
  CGContextRestoreGState( gc );
  
  // Done with the image
  CGImageRelease( image );
  image = NULL;
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len,
                                 ColourAllocated fore, ColourAllocated back)
{
  FillRectangle(rc, back);
  DrawTextTransparent(rc, font_, ybase, s, len, fore);
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len,
                                  ColourAllocated fore, ColourAllocated back)
{
  CGContextSaveGState(gc);
  CGContextClipToRect(gc, PRectangleToCGRect(rc));
  DrawTextNoClip(rc, font_, ybase, s, len, fore, back);
  CGContextRestoreGState(gc);
}

//--------------------------------------------------------------------------------------------------

CFStringEncoding EncodingFromCharacterSet(bool unicode, int characterSet)
{
  if (unicode)
    return kCFStringEncodingUTF8;

  // Unsupported -> Latin1 as reasonably safe
  enum { notSupported = kCFStringEncodingISOLatin1};

  switch (characterSet)
  {
  case SC_CHARSET_ANSI:
    return kCFStringEncodingISOLatin1;
  case SC_CHARSET_DEFAULT:
    return kCFStringEncodingISOLatin1;
  case SC_CHARSET_BALTIC:
    return kCFStringEncodingWindowsBalticRim;
  case SC_CHARSET_CHINESEBIG5:
    return kCFStringEncodingBig5;
  case SC_CHARSET_EASTEUROPE:
    return kCFStringEncodingWindowsLatin2;
  case SC_CHARSET_GB2312:
    return kCFStringEncodingGB_18030_2000;
  case SC_CHARSET_GREEK:
    return kCFStringEncodingWindowsGreek;
  case SC_CHARSET_HANGUL:
    return kCFStringEncodingEUC_KR;
  case SC_CHARSET_MAC:
    return kCFStringEncodingMacRoman;
  case SC_CHARSET_OEM:
    return kCFStringEncodingISOLatin1;
  case SC_CHARSET_RUSSIAN:
    return kCFStringEncodingKOI8_R;
  case SC_CHARSET_CYRILLIC:
    return kCFStringEncodingWindowsCyrillic;
  case SC_CHARSET_SHIFTJIS:
    return kCFStringEncodingShiftJIS;
  case SC_CHARSET_SYMBOL:
    return kCFStringEncodingMacSymbol;
  case SC_CHARSET_TURKISH:
    return kCFStringEncodingWindowsLatin5;
  case SC_CHARSET_JOHAB:
    return kCFStringEncodingWindowsKoreanJohab;
  case SC_CHARSET_HEBREW:
    return kCFStringEncodingWindowsHebrew;
  case SC_CHARSET_ARABIC:
    return kCFStringEncodingWindowsArabic;
  case SC_CHARSET_VIETNAMESE:
    return kCFStringEncodingWindowsVietnamese;
  case SC_CHARSET_THAI:
    return kCFStringEncodingISOLatinThai;
  case SC_CHARSET_8859_15:
    return kCFStringEncodingISOLatin1;
  default:
    return notSupported;
  }
}

void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len, 
                                      ColourAllocated fore)
{
	CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_));
	ColourDesired colour(fore.AsLong());
	CGColorRef color = CGColorCreateGenericRGB(colour.GetRed()/255.0,colour.GetGreen()/255.0,colour.GetBlue()/255.0,1.0);

	QuartzTextStyle* style = reinterpret_cast<QuartzTextStyle*>(font_.GetID());
	style->setCTStyleColor(color);
	
	CGColorRelease(color);

	textLayout->setText (reinterpret_cast<const UInt8*>(s), len, encoding, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
	textLayout->draw(rc.left, ybase);
}

static size_t utf8LengthFromLead(unsigned char uch) {
	if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
		return 4;
	} else if (uch >= (0x80 + 0x40 + 0x20)) {
		return 3;
	} else if (uch >= (0x80)) {
		return 2;
	} else {
		return 1;
	}
}

//--------------------------------------------------------------------------------------------------

void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, int *positions)
{
	CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_));
	textLayout->setText (reinterpret_cast<const UInt8*>(s), len, encoding, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
	
	CTLineRef mLine = textLayout->getCTLine();
	assert(mLine != NULL);
	
	if (unicodeMode) {
		// Map the widths given for UTF-16 characters back onto the UTF-8 input string
		CFIndex fit = textLayout->getStringLength();
		int ui=0;
		const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
		int i=0;
		while (ui<fit) {
			size_t lenChar = utf8LengthFromLead(us[i]);
			size_t codeUnits = (lenChar < 4) ? 1 : 2;
			CGFloat xPosition = CTLineGetOffsetForStringIndex(mLine, ui+1, NULL);
			for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
				positions[i++] = static_cast<int>(lround(xPosition));
			}
			ui += codeUnits;
		}
		int lastPos = 0;
		if (i > 0)
			lastPos = positions[i-1];
		while (i<len) {
			positions[i++] = lastPos;
		}
	} else if (codePage) {
		int ui = 0;
		for (int i=0;i<len;) {
			size_t lenChar = Platform::IsDBCSLeadByte(codePage, s[i]) ? 2 : 1;
			CGFloat xPosition = CTLineGetOffsetForStringIndex(mLine, ui+1, NULL);
			for (unsigned int bytePos=0; (bytePos<lenChar) && (i<len); bytePos++) {
				positions[i++] = static_cast<int>(lround(xPosition));
			}
			ui++;
		}
	} else {	// Single byte encoding
		for (int i=0;i<len;i++) {
			CGFloat xPosition = CTLineGetOffsetForStringIndex(mLine, i+1, NULL);
			positions[i] = static_cast<int>(lround(xPosition));
		}
	}

}

int SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
  if (font_.GetID())
  {
    CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_));
    textLayout->setText (reinterpret_cast<const UInt8*>(s), len, encoding, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
    
	return textLayout->MeasureStringWidth();
  }
  return 1;
}

int SurfaceImpl::WidthChar(Font &font_, char ch) {
  char str[2] = { ch, '\0' };
  if (font_.GetID())
  {
    CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_));
    textLayout->setText (reinterpret_cast<const UInt8*>(str), 1, encoding, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
    
    return textLayout->MeasureStringWidth();
  }
  else
    return 1;
}

// This string contains a good range of characters to test for size.
const char sizeString[] = "`~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890"
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

int SurfaceImpl::Ascent(Font &font_) {
  if (!font_.GetID())
    return 1;
  
	float ascent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAscent();
	return ascent + 0.5;

}

int SurfaceImpl::Descent(Font &font_) {
  if (!font_.GetID())
    return 1;
  
	float descent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getDescent();
	return descent + 0.5;

}

int SurfaceImpl::InternalLeading(Font &) {
  return 0;
}

int SurfaceImpl::ExternalLeading(Font &font_) {
  if (!font_.GetID())
    return 1;
  
	float leading = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getLeading();
	return leading + 0.5;

}

int SurfaceImpl::Height(Font &font_) {

	int ht = Ascent(font_) + Descent(font_);
	return ht;
}

int SurfaceImpl::AverageCharWidth(Font &font_) {
  
  if (!font_.GetID())
    return 1;
  
  const int sizeStringLength = (sizeof( sizeString ) / sizeof( sizeString[0] ) - 1);
  int width = WidthText( font_, sizeString, sizeStringLength  );
  
  return (int) ((width / (float) sizeStringLength) + 0.5);
}

int SurfaceImpl::SetPalette(Scintilla::Palette *, bool) {
  // Mac OS X is always true colour (I think) so this doesn't matter
  return 0;
}

void SurfaceImpl::SetClip(PRectangle rc) {
  CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
}

void SurfaceImpl::FlushCachedState() {
  CGContextSynchronize( gc );
}

void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
  unicodeMode = unicodeMode_;
}

void SurfaceImpl::SetDBCSMode(int codePage_) {
  if (codePage_ && (codePage_ != SC_CP_UTF8))
    codePage = codePage_;
}

Surface *Surface::Allocate()
{
  return new SurfaceImpl();
}

//----------------- Window -------------------------------------------------------------------------

// Cocoa uses different types for windows and views, so a Window may
// be either an NSWindow or NSView and the code will check the type
// before performing an action.

Window::~Window()
{
}

//--------------------------------------------------------------------------------------------------

void Window::Destroy()
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSWindow class]])
    {
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      [win release];
    }
  }
  wid = 0;
}

//--------------------------------------------------------------------------------------------------

bool Window::HasFocus()
{
  NSView* container = reinterpret_cast<NSView*>(wid);
  return [[container window] firstResponder] == container;
}

//--------------------------------------------------------------------------------------------------

static int ScreenMax(NSWindow* win)
{
  NSScreen* screen = [win screen];
  NSRect frame = [screen frame];
  return frame.origin.y + frame.size.height;
}

PRectangle Window::GetPosition()
{
  if (wid)
  {
    NSRect rect;
    id idWin = reinterpret_cast<id>(wid);
    NSWindow* win;
    if ([idWin isKindOfClass: [NSView class]])
    {
      // NSView
      NSView* view = reinterpret_cast<NSView*>(idWin);
      win = [view window];
      rect = [view bounds];
      rect = [view convertRectToBase: rect];
      rect.origin = [win convertBaseToScreen:rect.origin];
    }
    else
    {
      // NSWindow
      win = reinterpret_cast<NSWindow*>(idWin);
      rect = [win frame];
    }
    int screenHeight = ScreenMax(win);
    // Invert screen positions to match Scintilla
    return PRectangle(
        NSMinX(rect), screenHeight - NSMaxY(rect),
        NSMaxX(rect), screenHeight - NSMinY(rect));
  }
  else
  {
    return PRectangle(0, 0, 1, 1);
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetPosition(PRectangle rc)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSView class]])
    {
      // NSView
      // Moves this view inside the parent view
      NSRect nsrc = NSMakeRect(rc.left, rc.bottom, rc.Width(), rc.Height());
      NSView* view = reinterpret_cast<NSView*>(idWin);
      nsrc.origin = [[view window] convertScreenToBase:nsrc.origin];
      [view setFrame: nsrc];
    }
    else
    {
      // NSWindow
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      int screenHeight = ScreenMax(win);
      NSRect nsrc = NSMakeRect(rc.left, screenHeight - rc.bottom,
          rc.Width(), rc.Height());
      [win setFrame: nsrc display:YES];
    }
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetPositionRelative(PRectangle rc, Window window)
{
  PRectangle rcOther = window.GetPosition();
  rc.left += rcOther.left;
  rc.right += rcOther.left;
  rc.top += rcOther.top;
  rc.bottom += rcOther.top;
  SetPosition(rc);
}

//--------------------------------------------------------------------------------------------------

PRectangle Window::GetClientPosition()
{
  // This means, in MacOS X terms, get the "frame bounds". Call GetPosition, just like on Win32.
  return GetPosition();
}

//--------------------------------------------------------------------------------------------------

void Window::Show(bool show)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSWindow class]])
    {
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      if (show)
      {
        [win orderFront:nil];
      }
      else
      {
        [win orderOut:nil];
      }
    }
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Invalidates the entire window or view so it is completely redrawn.
 */
void Window::InvalidateAll()
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    NSView* container;
    if ([idWin isKindOfClass: [NSView class]])
    {
      container = reinterpret_cast<NSView*>(idWin);
    }
    else
    {
      // NSWindow
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      container = reinterpret_cast<NSView*>([win contentView]);
      container.needsDisplay = YES;
    }
    container.needsDisplay = YES;
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Invalidates part of the window or view so only this part redrawn.
 */
void Window::InvalidateRectangle(PRectangle rc)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    NSView* container;
    if ([idWin isKindOfClass: [NSView class]])
    {
      container = reinterpret_cast<NSView*>(idWin);
    }
    else
    {
      // NSWindow
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      container = reinterpret_cast<NSView*>([win contentView]);
    }
    [container setNeedsDisplayInRect: PRectangleToNSRect(rc)];
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetFont(Font&)
{
  // Implemented on list subclass on Cocoa.
}

//--------------------------------------------------------------------------------------------------

/**
 * Converts the Scintilla cursor enum into an NSCursor and stores it in the associated NSView,
 * which then will take care to set up a new mouse tracking rectangle.
 */
void Window::SetCursor(Cursor curs)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isMemberOfClass: [InnerView class]])
    {
      InnerView* container = reinterpret_cast<InnerView*>(idWin);
      [container setCursor: curs];
    }
  }
}

//--------------------------------------------------------------------------------------------------

void Window::SetTitle(const char* s)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSWindow class]])
    {
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      NSString* sTitle = [NSString stringWithUTF8String:s];
      [win setTitle:sTitle];
    }
  }
}

//--------------------------------------------------------------------------------------------------

PRectangle Window::GetMonitorRect(Point)
{
  if (wid)
  {
    id idWin = reinterpret_cast<id>(wid);
    if ([idWin isKindOfClass: [NSWindow class]])
    {
      NSWindow* win = reinterpret_cast<NSWindow*>(idWin);
      NSScreen* screen = [win screen];
      NSRect rect = [screen frame];
      int screenHeight = rect.origin.y + rect.size.height;
      // Invert screen positions to match Scintilla
      return PRectangle(
          NSMinX(rect), screenHeight - NSMaxY(rect),
          NSMaxX(rect), screenHeight - NSMinY(rect));
    }
  }
  return PRectangle();
}

//----------------- ImageFromXPM -------------------------------------------------------------------

// Convert an XPM image into an NSImage for use with Cocoa

static NSImage* ImageFromXPM(XPM* pxpm)
{
  NSImage* img = nil;
  if (pxpm)
  {
    const int width = pxpm->GetWidth();
    const int height = pxpm->GetHeight();
    PRectangle rcxpm(0, 0, width, height);
    Surface* surfaceXPM = Surface::Allocate();
    if (surfaceXPM)
    {
      surfaceXPM->InitPixMap(width, height, NULL, NULL);
      SurfaceImpl* surfaceIXPM = static_cast<SurfaceImpl*>(surfaceXPM);
      CGContextClearRect(surfaceIXPM->GetContext(), CGRectMake(0, 0, width, height));
      pxpm->Draw(surfaceXPM, rcxpm);
      img = [NSImage alloc];
      [img autorelease];
      CGImageRef imageRef = surfaceIXPM->GetImage();
      [img initWithSize:NSZeroSize];
      NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef];
      [img addRepresentation: bitmapRep];
      [bitmapRep release];
      CGImageRelease(imageRef);
      delete surfaceXPM;
    }
  }
  return img;
}

//----------------- ListBox ------------------------------------------------------------------------

ListBox::ListBox()
{
}

//--------------------------------------------------------------------------------------------------

ListBox::~ListBox()
{
}

//--------------------------------------------------------------------------------------------------

struct RowData
{
  int type;
  std::string text;
  RowData(int type_, const char* text_) :
    type(type_), text(text_)
  {
  }
};

class LinesData
{
  std::vector<RowData> lines;
public:
  LinesData()
  {
  }
  ~LinesData()
  {
  }
  int Length() const
  {
    return static_cast<int>(lines.size());
  }
  void Clear()
  {
    lines.clear();
  }
  void Add(int /* index */, int type, char* str)
  {
    lines.push_back(RowData(type, str));
  }
  int GetType(size_t index) const
  {
    if (index < lines.size())
    {
      return lines[index].type;
    }
    else
    {
      return 0;
    }
  }
  const char* GetString(size_t index) const
  {
    if (index < lines.size())
    {
      return lines[index].text.c_str();
    }
    else
    {
      return 0;
    }
  }
};

class ListBoxImpl;

@interface AutoCompletionDataSource :
NSObject
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
<NSTableViewDataSource>
#endif
{
  ListBoxImpl* box;
{'fraction_non_alphanumeric': 0.2286034345370766, 'fraction_numerical': 0.009839359748703676, 'mean_word_length': 1.9441044471644227, 'pattern_counts': {'":': 0, '<': 175, '<?xml version=': 0, '>': 204, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 40, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3664180', 'n_tokens_mistral': 27224, 'n_tokens_neox': 25713, 'n_words': 6611}