aboutsummaryrefslogtreecommitdiffhomepage
path: root/cocoa/PlatCocoa.mm
diff options
context:
space:
mode:
Diffstat (limited to 'cocoa/PlatCocoa.mm')
-rw-r--r--cocoa/PlatCocoa.mm2130
1 files changed, 2130 insertions, 0 deletions
diff --git a/cocoa/PlatCocoa.mm b/cocoa/PlatCocoa.mm
new file mode 100644
index 000000000..ca24c4b47
--- /dev/null
+++ b/cocoa/PlatCocoa.mm
@@ -0,0 +1,2130 @@
+/**
+ * Scintilla source code edit control
+ * PlatCocoa.mm - implementation of platform facilities on MacOS X/Cocoa
+ *
+ * Written by Mike Lischke
+ * Based on PlatMacOSX.cxx
+ * Based on work by Evan Jones (c) 2002 <ejones@uwaterloo.ca>
+ * Based on PlatGTK.cxx Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
+ * 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).
+ */
+
+#include "PlatCocoa.h"
+
+#include <cstring>
+#include <cstdio>
+#include <cstdlib>
+#include <assert.h>
+#include <sys/time.h>
+#include <stdexcept>
+
+#include "XPM.h"
+
+#import <Foundation/NSGeometry.h>
+
+#import <ScintillaView.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& w)
+{
+ // Nothing to allocate as we don't use palettes.
+}
+
+//----------------- Font ---------------------------------------------------------------------------
+
+Font::Font(): fid(0)
+{
+}
+
+//--------------------------------------------------------------------------------------------------
+
+Font::~Font()
+{
+ Release();
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Creates a Quartz 2D font with the given properties.
+ * TODO: rewrite to use NSFont.
+ */
+void Font::Create(const char *faceName, int characterSet, int size, bool bold, bool italic,
+ bool extraFontFlag)
+{
+ // TODO: How should I handle the characterSet request?
+ Release();
+
+ QuartzTextStyle* style = new QuartzTextStyle();
+ fid = style;
+
+ // Find the font
+ QuartzFont font(faceName, strlen(faceName));
+
+ // We set Font, Size, Bold, Italic
+ QuartzTextSize textSize(size);
+ QuartzTextBold isBold(bold);
+ QuartzTextItalic isItalic(italic);
+
+ // Actually set the attributes
+ QuartzTextStyleAttribute* attributes[] = { &font, &textSize, &isBold, &isItalic };
+ style->setAttributes(attributes, sizeof(attributes) / sizeof(*attributes));
+ style->setFontFeature(kLigaturesType, kCommonLigaturesOffSelector);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Font::Release()
+{
+ if (fid)
+ delete reinterpret_cast<QuartzTextStyle*>( fid );
+ fid = 0;
+}
+
+//----------------- SurfaceImpl --------------------------------------------------------------------
+
+SurfaceImpl::SurfaceImpl()
+{
+ bitmapData = NULL; // Release will try and delete bitmapData if != NULL
+ gc = NULL;
+ textLayout = new QuartzTextLayout(NULL);
+ 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;
+ //inited = false;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+bool SurfaceImpl::Initialised()
+{
+ // We are initalised if the graphics context is not null
+ return gc != NULL;// || port != NULL;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void SurfaceImpl::Init(WindowID wid)
+{
+ // 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 wid)
+{
+ 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);
+
+ // Create a data provider.
+ CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, bitmapData, bitmapByteCount,
+ NULL);
+ 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;
+
+ return image;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Returns the vertical logical device resolution of the main monitor.
+ */
+int SurfaceImpl::LogPixelsY()
+{
+ NSSize deviceResolution = [[[[NSScreen mainScreen] deviceDescription]
+ objectForKey: NSDeviceResolution] sizeValue];
+ return (int) deviceResolution.height;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * 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);
+
+ // TODO: Should the path be automatically closed, or is that the caller's responsability?
+ // 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.
+ // TODO: Create some version of PRectangleToCGRect to do this conversion for us?
+ 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 float 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) {
+ // TODO: Look at the Win32 API to determine what this is supposed to do:
+ // ::RoundRect(hdc, rc.left + 1, rc.top, rc.right - 1, rc.bottom, 8, 8 );
+
+ // 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
+ // TODO: Should I include these +0.5 in the array creation code above?
+ 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 / 100.0 );
+ CGRect rect = PRectangleToCGRect( rc );
+ CGContextFillRect( gc, rect );
+ }
+}
+
+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);
+}
+
+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);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len,
+ ColourAllocated fore)
+{
+ textLayout->setText (reinterpret_cast<const UInt8*>(s), len, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
+
+ // The Quartz RGB fill color influences the ATSUI color
+ FillColour(fore);
+
+ // Draw text vertically flipped as OS X uses a coordinate system where +Y is upwards.
+ textLayout->draw(rc.left, ybase, true);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, int *positions)
+{
+ // sample at http://developer.apple.com/samplecode/ATSUICurveAccessDemo/listing1.html
+ // sample includes use of ATSUGetGlyphInfo which would be better for older
+ // OSX systems. We should expand to using that on older systems as well.
+ for (int i = 0; i < len; i++)
+ positions [i] = 0;
+
+ // We need the right X coords, so we have to append a char to get the left coord of thast extra char
+ char* buf = (char*) malloc (len+1);
+ if (!buf)
+ return;
+
+ memcpy (buf, s, len);
+ buf [len] = '.';
+
+ textLayout->setText (reinterpret_cast<const UInt8*>(buf), len+1, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
+ ATSUGlyphInfoArray* theGlyphInfoArrayPtr;
+ ByteCount theArraySize;
+
+ // Get the GlyphInfoArray
+ ATSUTextLayout layout = textLayout->getLayout();
+ if ( noErr == ATSUGetGlyphInfo (layout, 0, textLayout->getLength(), &theArraySize, NULL))
+ {
+ theGlyphInfoArrayPtr = (ATSUGlyphInfoArray *) malloc (theArraySize + sizeof(ItemCount) + sizeof(ATSUTextLayout));
+ if (theGlyphInfoArrayPtr)
+ {
+ if (noErr == ATSUGetGlyphInfo (layout, 0, textLayout->getLength(), &theArraySize, theGlyphInfoArrayPtr))
+ {
+ // do not count the first item, which is at the beginning of the line
+ for ( UniCharCount unicodePosition = 1, i = 0; i < len && unicodePosition < theGlyphInfoArrayPtr->numGlyphs; unicodePosition ++ )
+ {
+ // The ideal position is the x coordinate of the glyph, relative to the beginning of the line
+ int position = (int)( theGlyphInfoArrayPtr->glyphs[unicodePosition].idealX + 0.5 ); // These older APIs return float values
+ unsigned char uch = s[i];
+ positions[i++] = position;
+
+ // If we are using unicode (UTF8), map the Unicode position back to the UTF8 characters,
+ // as 1 unicode character can map to multiple UTF8 characters.
+ // See: http://www.tbray.org/ongoing/When/200x/2003/04/26/UTF
+ // Or: http://www.cl.cam.ac.uk/~mgk25/unicode.html
+ if ( unicodeMode )
+ {
+ unsigned char mask = 0xc0;
+ int count = 1;
+ // Add one additonal byte for each extra high order one in the byte
+ while ( uch >= mask && count < 8 )
+ {
+ positions[i++] = position;
+ count ++;
+ mask = mask >> 1 | 0x80; // add an additional one in the highest order position
+ }
+ }
+ }
+ }
+
+ // Free the GlyphInfoArray
+ free (theGlyphInfoArrayPtr);
+ }
+ }
+ free (buf);
+}
+
+int SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
+ if (font_.GetID())
+ {
+ textLayout->setText (reinterpret_cast<const UInt8*>(s), len, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
+
+ // TODO: Maybe I should add some sort of text measurement features to QuartzTextLayout?
+ unsigned long actualNumberOfBounds = 0;
+ ATSTrapezoid glyphBounds;
+
+ // We get a single bound, since the text should only require one. If it requires more, there is an issue
+ if ( ATSUGetGlyphBounds( textLayout->getLayout(), 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &glyphBounds, &actualNumberOfBounds ) != noErr || actualNumberOfBounds != 1 )
+ {
+ Platform::DebugDisplay( "ATSUGetGlyphBounds failed in WidthText" );
+ return 0;
+ }
+
+ //Platform::DebugPrintf( "WidthText: \"%*s\" = %ld\n", len, s, Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x ) );
+ return Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x );
+ }
+ return 1;
+}
+
+int SurfaceImpl::WidthChar(Font &font_, char ch) {
+ char str[2] = { ch, '\0' };
+ if (font_.GetID())
+ {
+ textLayout->setText (reinterpret_cast<const UInt8*>(str), 1, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
+
+ // TODO: Maybe I should add some sort of text measurement features to QuartzTextLayout?
+ unsigned long actualNumberOfBounds = 0;
+ ATSTrapezoid glyphBounds;
+
+ // We get a single bound, since the text should only require one. If it requires more, there is an issue
+ if ( ATSUGetGlyphBounds( textLayout->getLayout(), 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &glyphBounds, &actualNumberOfBounds ) != noErr || actualNumberOfBounds != 1 )
+ {
+ Platform::DebugDisplay( "ATSUGetGlyphBounds failed in WidthChar" );
+ return 0;
+ }
+
+ return Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x );
+ }
+ else
+ return 1;
+}
+
+// Three possible strategies for determining ascent and descent of font:
+// 1) Call ATSUGetGlyphBounds with string containing all letters, numbers and punctuation.
+// 2) Use the ascent and descent fields of the font.
+// 3) Call ATSUGetGlyphBounds with string as 1 but also including accented capitals.
+// Smallest values given by 1 and largest by 3 with 2 in between.
+// Techniques 1 and 2 sometimes chop off extreme portions of ascenders and
+// descenders but are mostly OK except for accented characters which are
+// rarely used in code.
+
+// 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;
+
+ ATSUTextMeasurement ascent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSUAscentTag );
+ return Fix2Long( ascent );
+}
+
+int SurfaceImpl::Descent(Font &font_) {
+ if (!font_.GetID())
+ return 1;
+
+ ATSUTextMeasurement descent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSUDescentTag );
+ return Fix2Long( descent );
+}
+
+int SurfaceImpl::InternalLeading(Font &) {
+ // TODO: How do we get EM_Size?
+ // internal leading = ascent - descent - EM_size
+ return 0;
+}
+
+int SurfaceImpl::ExternalLeading(Font &font_) {
+ if (!font_.GetID())
+ return 1;
+
+ ATSUTextMeasurement lineGap = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSULeadingTag );
+ return Fix2Long( lineGap );
+}
+
+int SurfaceImpl::Height(Font &font_) {
+ return Ascent(font_) + Descent(font_);
+}
+
+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);
+
+ /*
+ ATSUStyle textStyle = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getATSUStyle();
+ ATSUFontID fontID;
+
+ ByteCount actualSize = 0;
+ if ( ATSUGetAttribute( textStyle, kATSUFontTag, sizeof( fontID ), &fontID, &actualSize ) != noErr )
+ {
+ Platform::DebugDisplay( "ATSUGetAttribute failed" );
+ return 1;
+ }
+
+ ATSFontMetrics metrics;
+ memset( &metrics, 0, sizeof( metrics ) );
+ if ( ATSFontGetHorizontalMetrics( fontID, kATSOptionFlagsDefault, &metrics ) != noErr )
+ {
+ Platform::DebugDisplay( "ATSFontGetHorizontalMetrics failed in AverageCharWidth" );
+ return 1;
+ }
+
+ printf( "%f %f %f\n", metrics.avgAdvanceWidth * 32, metrics.ascent * 32, metrics.descent * 32 );
+
+ return (int) (metrics.avgAdvanceWidth + 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) {
+ // TODO: Implement this for code pages != UTF-8
+}
+
+Surface *Surface::Allocate()
+{
+ return new SurfaceImpl();
+}
+
+//----------------- Window -------------------------------------------------------------------------
+
+Window::~Window() {
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Window::Destroy()
+{
+ if (windowRef)
+ {
+ // not used
+ }
+ wid = 0;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+bool Window::HasFocus()
+{
+ NSView* container = reinterpret_cast<NSView*>(wid);
+ return [[container window] firstResponder] == container;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+PRectangle Window::GetPosition()
+{
+ NSRect rect= [reinterpret_cast<NSView*>(wid) frame];
+
+ return PRectangle(NSMinX(rect), NSMinY(rect), NSMaxX(rect), NSMaxY(rect));
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Window::SetPosition(PRectangle rc)
+{
+ // Moves this view inside the parent view
+ if ( wid )
+ {
+ // Set the frame on the view, the function handles the rest
+ // CGRect r = PRectangleToCGRect( rc );
+ // HIViewSetFrame( reinterpret_cast<HIViewRef>( wid ), &r );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Window::SetPositionRelative(PRectangle rc, Window window) {
+ // // used to actually move child windows (ie. listbox/calltip) so we have to move
+ // // the window, not the hiview
+ // if (windowRef) {
+ // // we go through some contortions here to get an accurate location for our
+ // // child windows. This is necessary due to the multiple ways an embedding
+ // // app may be setup. See SciTest/main.c (GOOD && BAD) for test case.
+ // WindowRef relativeWindow = GetControlOwner(reinterpret_cast<HIViewRef>( window.GetID() ));
+ // WindowRef thisWindow = reinterpret_cast<WindowRef>( windowRef );
+ //
+ // Rect portBounds;
+ // ::GetWindowBounds(relativeWindow, kWindowStructureRgn, &portBounds);
+ // //fprintf(stderr, "portBounds %d %d %d %d\n", portBounds.left, portBounds.top, portBounds.right, portBounds.bottom);
+ // PRectangle hbounds = window.GetPosition();
+ // //fprintf(stderr, "hbounds %d %d %d %d\n", hbounds.left, hbounds.top, hbounds.right, hbounds.bottom);
+ // HIViewRef parent = HIViewGetSuperview(reinterpret_cast<HIViewRef>( window.GetID() ));
+ // Rect pbounds;
+ // GetControlBounds(parent, &pbounds);
+ // //fprintf(stderr, "pbounds %d %d %d %d\n", pbounds.left, pbounds.top, pbounds.right, pbounds.bottom);
+ //
+ // PRectangle bounds;
+ // bounds.top = portBounds.top + pbounds.top + hbounds.top + rc.top;
+ // bounds.bottom = bounds.top + rc.Height();
+ // bounds.left = portBounds.left + pbounds.left + hbounds.left + rc.left;
+ // bounds.right = bounds.left + rc.Width();
+ // //fprintf(stderr, "bounds %d %d %d %d\n", bounds.left, bounds.top, bounds.right, bounds.bottom);
+ //
+ // MoveWindow(thisWindow, bounds.left, bounds.top, false);
+ // SizeWindow(thisWindow, bounds.Width(), bounds.Height(), true);
+ //
+ // SetPosition(PRectangle(0,0,rc.Width(),rc.Height()));
+ // } else {
+ // 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 ) {
+ // HIViewSetVisible( reinterpret_cast<HIViewRef>( wid ), show );
+ // }
+ // // this is necessary for calltip/listbox
+ // if (windowRef) {
+ // WindowRef thisWindow = reinterpret_cast<WindowRef>( windowRef );
+ // if (show) {
+ // ShowWindow( thisWindow );
+ // DrawControls( thisWindow );
+ // } else
+ // HideWindow( thisWindow );
+ // }
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Invalidates the entire window (here an NSView) so it is completely redrawn.
+ */
+void Window::InvalidateAll()
+{
+ if (wid)
+ {
+ NSView* container = reinterpret_cast<NSView*>(wid);
+ container.needsDisplay = YES;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Invalidates part of the window (here an NSView) so only this part redrawn.
+ */
+void Window::InvalidateRectangle(PRectangle rc)
+{
+ if (wid)
+ {
+ NSView* container = reinterpret_cast<NSView*>(wid);
+ [container setNeedsDisplayInRect: PRectangleToNSRect(rc)];
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Window::SetFont(Font &)
+{
+ // TODO: Do I need to implement this? MSDN: specifies the font that a control is to use when drawing text.
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * 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)
+ {
+ InnerView* container = reinterpret_cast<InnerView*>(wid);
+ [container setCursor: curs];
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Window::SetTitle(const char *s)
+{
+ // WindowRef window = GetControlOwner(reinterpret_cast<HIViewRef>( wid ));
+ // CFStringRef title = CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingMacRoman);
+ // SetWindowTitleWithCFString(window, title);
+ // CFRelease(title);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+PRectangle Window::GetMonitorRect(Point)
+{
+ return PRectangle();
+}
+
+//----------------- ListBox ------------------------------------------------------------------------
+
+ListBox::ListBox()
+{
+}
+
+//--------------------------------------------------------------------------------------------------
+
+ListBox::~ListBox()
+{
+}
+
+//--------------------------------------------------------------------------------------------------
+
+static const OSType scintillaListBoxType = 'sclb';
+
+enum {
+ kItemsPerContainer = 1,
+ kIconColumn = 'icon',
+ kTextColumn = 'text'
+};
+static SInt32 kScrollBarWidth = 0;
+
+class LineData {
+ int *types;
+ CFStringRef *strings;
+ int len;
+ int maximum;
+public:
+ LineData() :types(0), strings(0), len(0), maximum(0) {}
+ ~LineData() {
+ Clear();
+ }
+ void Clear() {
+ delete []types;
+ types = 0;
+ for (int i=0; i<maximum; i++) {
+ if (strings[i]) CFRelease(strings[i]);
+ }
+ delete []strings;
+ strings = 0;
+ len = 0;
+ maximum = 0;
+ }
+ void Add(int index, int type, CFStringRef str ) {
+ if (index >= maximum) {
+ if (index >= len) {
+ int lenNew = (index+1) * 2;
+ int *typesNew = new int[lenNew];
+ CFStringRef *stringsNew = new CFStringRef[lenNew];
+ for (int i=0; i<maximum; i++) {
+ typesNew[i] = types[i];
+ stringsNew[i] = strings[i];
+ }
+ delete []types;
+ delete []strings;
+ types = typesNew;
+ strings = stringsNew;
+ len = lenNew;
+ }
+ while (maximum < index) {
+ types[maximum] = 0;
+ strings[maximum] = 0;
+ maximum++;
+ }
+ }
+ types[index] = type;
+ strings[index] = str;
+ if (index == maximum) {
+ maximum++;
+ }
+ }
+ int GetType(int index) {
+ if (index < maximum) {
+ return types[index];
+ } else {
+ return 0;
+ }
+ }
+ CFStringRef GetString(int index) {
+ if (index < maximum) {
+ return strings[index];
+ } else {
+ return 0;
+ }
+ }
+};
+
+//----------------- ListBoxImpl --------------------------------------------------------------------
+
+class ListBoxImpl : public ListBox
+{
+private:
+ ControlRef lb;
+ XPMSet xset;
+ int lineHeight;
+ bool unicodeMode;
+ int desiredVisibleRows;
+ unsigned int maxItemWidth;
+ unsigned int aveCharWidth;
+ Font font;
+ int maxWidth;
+
+ void InstallDataBrowserCustomCallbacks();
+ void ConfigureDataBrowser();
+
+ static pascal OSStatus WindowEventHandler(EventHandlerCallRef inCallRef,
+ EventRef inEvent,
+ void *inUserData );
+ EventHandlerRef eventHandler;
+
+protected:
+ WindowRef windowRef;
+
+public:
+ LineData ld;
+ CallBackAction doubleClickAction;
+ void *doubleClickActionData;
+
+ ListBoxImpl() : lb(NULL), lineHeight(10), unicodeMode(false),
+ desiredVisibleRows(5), maxItemWidth(0), aveCharWidth(8),
+ doubleClickAction(NULL), doubleClickActionData(NULL)
+ {
+ if (kScrollBarWidth == 0)
+ ;//GetThemeMetric(kThemeMetricScrollBarWidth, &kScrollBarWidth);
+ }
+
+ ~ListBoxImpl() {};
+ void SetFont(Font &font);
+ void Create(Window &parent, int ctrlID, Scintilla::Point pt, int lineHeight_, bool unicodeMode_);
+ void SetAverageCharWidth(int width);
+ void SetVisibleRows(int rows);
+ int GetVisibleRows() const;
+ PRectangle GetDesiredRect();
+ int CaretFromEdge();
+ void Clear();
+ void Append(char *s, int type = -1);
+ int Length();
+ void Select(int n);
+ int GetSelection();
+ int Find(const char *prefix);
+ void GetValue(int n, char *value, int len);
+ void Sort();
+ void RegisterImage(int type, const char *xpm_data);
+ void ClearRegisteredImages();
+ void SetDoubleClickAction(CallBackAction action, void *data) {
+ doubleClickAction = action;
+ doubleClickActionData = data;
+ }
+
+ int IconWidth();
+ void ShowHideScrollbar();
+#ifdef DB_TABLE_ROW_HEIGHT
+ void SetRowHeight(DataBrowserItemID itemID);
+#endif
+
+ void DrawRow(DataBrowserItemID item,
+ DataBrowserPropertyID property,
+ DataBrowserItemState itemState,
+ const Rect *theRect);
+
+ void SetList(const char* list, char separator, char typesep);
+};
+
+ListBox *ListBox::Allocate() {
+ ListBoxImpl *lb = new ListBoxImpl();
+ return lb;
+}
+
+void ListBoxImpl::Create(Window &/*parent*/, int /*ctrlID*/, Scintilla::Point /*pt*/,
+ int lineHeight_, bool unicodeMode_) {
+ lineHeight = lineHeight_;
+ unicodeMode = unicodeMode_;
+ maxWidth = 2000;
+
+ WindowClass windowClass = kHelpWindowClass;
+ WindowAttributes attributes = kWindowNoAttributes;
+ Rect contentBounds;
+ WindowRef outWindow;
+
+ contentBounds.top = contentBounds.left = 0;
+ contentBounds.right = 100;
+ contentBounds.bottom = lineHeight * desiredVisibleRows;
+
+ //CreateNewWindow(windowClass, attributes, &contentBounds, &outWindow);
+
+ //InstallStandardEventHandler(GetWindowEventTarget(outWindow));
+
+ ControlRef root;
+ //CreateRootControl(outWindow, &root);
+
+ //CreateDataBrowserControl(outWindow, &contentBounds, kDataBrowserListView, &lb);
+
+#ifdef DB_TABLE_ROW_HEIGHT
+ // TODO: does not seem to have any effect
+ //SetDataBrowserTableViewRowHeight(lb, lineHeight);
+#endif
+
+ // get rid of the frame, forces databrowser to the full size
+ // of the window
+ Boolean frameAndFocus = false;
+ //SetControlData(lb, kControlNoPart, kControlDataBrowserIncludesFrameAndFocusTag,
+ // sizeof(frameAndFocus), &frameAndFocus);
+
+ ListBoxImpl* lbThis = this;
+ //SetControlProperty( lb, scintillaListBoxType, 0, sizeof( this ), &lbThis );
+
+ ConfigureDataBrowser();
+ InstallDataBrowserCustomCallbacks();
+
+ // install event handlers
+ static const EventTypeSpec kWindowEvents[] =
+ {
+ { kEventClassMouse, kEventMouseDown },
+ { kEventClassMouse, kEventMouseMoved },
+ };
+
+ eventHandler = NULL;
+ //InstallWindowEventHandler( outWindow, WindowEventHandler,
+ // GetEventTypeCount( kWindowEvents ),
+ // kWindowEvents, this, &eventHandler );
+
+ wid = lb;
+ //SetControlVisibility(lb, true, true);
+ SetControl(lb);
+ SetWindow(outWindow);
+}
+
+pascal OSStatus ListBoxImpl::WindowEventHandler(
+ EventHandlerCallRef inCallRef,
+ EventRef inEvent,
+ void* inUserData )
+{
+
+ switch (kEventClassMouse /* GetEventClass(inEvent) */) {
+ case kEventClassMouse:
+ switch (kEventMouseDown /* GetEventKind(inEvent) */ )
+ {
+ case kEventMouseMoved:
+ {
+ //SetThemeCursor( kThemeArrowCursor );
+ break;
+ }
+ case kEventMouseDown:
+ {
+ // we cannot handle the double click from the databrowser notify callback as
+ // calling doubleClickAction causes the listbox to be destroyed. It is
+ // safe to do it from this event handler since the destroy event will be queued
+ // until we're done here.
+ /*
+ TCarbonEvent event( inEvent );
+ EventMouseButton inMouseButton;
+ event.GetParameter<EventMouseButton>( kEventParamMouseButton, typeMouseButton, &inMouseButton );
+
+ UInt32 inClickCount;
+ event.GetParameter( kEventParamClickCount, &inClickCount );
+ if (inMouseButton == kEventMouseButtonPrimary && inClickCount == 2) {
+ // handle our single mouse click now
+ ListBoxImpl* listbox = reinterpret_cast<ListBoxImpl*>( inUserData );
+ const WindowRef window = GetControlOwner(listbox->lb);
+ const HIViewRef rootView = HIViewGetRoot( window );
+ HIViewRef targetView = NULL;
+ HIViewGetViewForMouseEvent( rootView, inEvent, &targetView );
+ if ( targetView == listbox->lb )
+ {
+ if (listbox->doubleClickAction != NULL) {
+ listbox->doubleClickAction(listbox->doubleClickActionData);
+ }
+ }
+ }
+ */
+ }
+ }
+ }
+ return eventNotHandledErr;
+}
+
+#ifdef DB_TABLE_ROW_HEIGHT
+void ListBoxImpl::SetRowHeight(DataBrowserItemID itemID)
+{
+ // XXX does not seem to have any effect
+ //SetDataBrowserTableViewItemRowHeight(lb, itemID, lineHeight);
+}
+#endif
+
+void ListBoxImpl::DrawRow(DataBrowserItemID item,
+ DataBrowserPropertyID property,
+ DataBrowserItemState itemState,
+ const Rect *theRect)
+{
+ Rect row = *theRect;
+ row.left = 0;
+
+ ColourPair fore;
+
+ if (itemState == kDataBrowserItemIsSelected) {
+ long systemVersion;
+ Gestalt( gestaltSystemVersion, &systemVersion );
+ // Panther DB starts using kThemeBrushSecondaryHighlightColor for inactive browser hilighting
+ if ( (systemVersion >= 0x00001030) )//&& (IsControlActive( lb ) == false) )
+ ;//SetThemePen( kThemeBrushSecondaryHighlightColor, 32, true );
+ else
+ ; //SetThemePen( kThemeBrushAlternatePrimaryHighlightColor, 32, true );
+
+ PaintRect(&row);
+ fore = ColourDesired(0xff,0xff,0xff);
+ }
+
+ int widthPix = xset.GetWidth() + 2;
+ int pixId = ld.GetType(item - 1);
+ XPM *pxpm = xset.Get(pixId);
+
+ char s[255];
+ GetValue(item - 1, s, 255);
+
+ Surface *surfaceItem = Surface::Allocate();
+ if (surfaceItem) {
+ CGContextRef cgContext;
+ GrafPtr port;
+ Rect bounds;
+
+ //GetControlBounds(lb, &bounds);
+ GetPort( &port );
+ QDBeginCGContext( port, &cgContext );
+
+ CGContextSaveGState( cgContext );
+ CGContextTranslateCTM(cgContext, 0, bounds.bottom - bounds.top);
+ CGContextScaleCTM(cgContext, 1.0, -1.0);
+
+ surfaceItem->Init(cgContext, NULL);
+
+ int left = row.left;
+ if (pxpm) {
+ PRectangle rc(left + 1, row.top,
+ left + 1 + widthPix, row.bottom);
+ pxpm->Draw(surfaceItem, rc);
+ }
+
+ // draw the text
+ PRectangle trc(left + 2 + widthPix, row.top, row.right, row.bottom);
+ int ascent = surfaceItem->Ascent(font) - surfaceItem->InternalLeading(font);
+ int ytext = trc.top + ascent + 1;
+ trc.bottom = ytext + surfaceItem->Descent(font) + 1;
+ surfaceItem->DrawTextTransparent( trc, font, ytext, s, strlen(s), fore.allocated );
+
+ CGContextRestoreGState( cgContext );
+ QDEndCGContext( port, &cgContext );
+ delete surfaceItem;
+ }
+}
+
+
+pascal void ListBoxDrawItemCallback(ControlRef browser, DataBrowserItemID item,
+ DataBrowserPropertyID property,
+ DataBrowserItemState itemState,
+ const Rect *theRect, SInt16 gdDepth,
+ Boolean colorDevice)
+{
+ if (property != kIconColumn) return;
+ ListBoxImpl* lbThis = NULL;
+ OSStatus err;
+ //err = GetControlProperty( browser, scintillaListBoxType, 0, sizeof( lbThis ), NULL, &lbThis );
+ // adjust our rect
+ lbThis->DrawRow(item, property, itemState, theRect);
+
+}
+
+void ListBoxImpl::ConfigureDataBrowser()
+{
+ DataBrowserViewStyle viewStyle;
+ DataBrowserSelectionFlags selectionFlags;
+ //GetDataBrowserViewStyle(lb, &viewStyle);
+
+ //SetDataBrowserHasScrollBars(lb, false, true);
+ //SetDataBrowserListViewHeaderBtnHeight(lb, 0);
+ //GetDataBrowserSelectionFlags(lb, &selectionFlags);
+ //SetDataBrowserSelectionFlags(lb, selectionFlags |= kDataBrowserSelectOnlyOne);
+ // if you change the hilite style, also change the style in ListBoxDrawItemCallback
+ //SetDataBrowserTableViewHiliteStyle(lb, kDataBrowserTableViewFillHilite);
+
+ Rect insetRect;
+ //GetDataBrowserScrollBarInset(lb, &insetRect);
+
+ insetRect.right = kScrollBarWidth - 1;
+ //SetDataBrowserScrollBarInset(lb, &insetRect);
+
+ switch (viewStyle)
+ {
+ case kDataBrowserListView:
+ {
+ DataBrowserListViewColumnDesc iconCol;
+ iconCol.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc;
+ iconCol.headerBtnDesc.minimumWidth = 0;
+ iconCol.headerBtnDesc.maximumWidth = maxWidth;
+ iconCol.headerBtnDesc.titleOffset = 0;
+ iconCol.headerBtnDesc.titleString = NULL;
+ iconCol.headerBtnDesc.initialOrder = kDataBrowserOrderIncreasing;
+
+ iconCol.headerBtnDesc.btnFontStyle.flags = kControlUseJustMask;
+ iconCol.headerBtnDesc.btnFontStyle.just = teFlushLeft;
+
+ iconCol.headerBtnDesc.btnContentInfo.contentType = kControlContentTextOnly;
+
+ iconCol.propertyDesc.propertyID = kIconColumn;
+ iconCol.propertyDesc.propertyType = kDataBrowserCustomType;
+ iconCol.propertyDesc.propertyFlags = kDataBrowserListViewSelectionColumn;
+
+ //AddDataBrowserListViewColumn(lb, &iconCol, kDataBrowserListViewAppendColumn);
+ } break;
+
+ }
+}
+
+void ListBoxImpl::InstallDataBrowserCustomCallbacks()
+{
+ /*
+ DataBrowserCustomCallbacks callbacks;
+
+ callbacks.version = kDataBrowserLatestCustomCallbacks;
+ verify_noerr(InitDataBrowserCustomCallbacks(&callbacks));
+ callbacks.u.v1.drawItemCallback = NewDataBrowserDrawItemUPP(ListBoxDrawItemCallback);
+ callbacks.u.v1.hitTestCallback = NULL;//NewDataBrowserHitTestUPP(ListBoxHitTestCallback);
+ callbacks.u.v1.trackingCallback = NULL;//NewDataBrowserTrackingUPP(ListBoxTrackingCallback);
+ callbacks.u.v1.editTextCallback = NULL;
+ callbacks.u.v1.dragRegionCallback = NULL;
+ callbacks.u.v1.acceptDragCallback = NULL;
+ callbacks.u.v1.receiveDragCallback = NULL;
+
+ SetDataBrowserCustomCallbacks(lb, &callbacks);
+ */
+}
+
+void ListBoxImpl::SetFont(Font &font_) {
+ // Having to do this conversion is LAME
+ QuartzTextStyle *ts = reinterpret_cast<QuartzTextStyle*>( font_.GetID() );
+ ControlFontStyleRec style;
+ ATSUAttributeValuePtr value;
+ ATSUFontID fontID;
+ style.flags = kControlUseFontMask | kControlUseSizeMask | kControlAddToMetaFontMask;
+ ts->getAttribute( kATSUFontTag, sizeof(fontID), &fontID, NULL );
+ ATSUFontIDtoFOND(fontID, &style.font, NULL);
+ ts->getAttribute( kATSUSizeTag, sizeof(Fixed), &value, NULL );
+ style.size = ((SInt16)FixRound((SInt32)value));
+ //SetControlFontStyle(lb, &style);
+
+#ifdef DB_TABLE_ROW_HEIGHT
+ // XXX this doesn't *stick*
+ ATSUTextMeasurement ascent = ts->getAttribute<ATSUTextMeasurement>( kATSUAscentTag );
+ ATSUTextMeasurement descent = ts->getAttribute<ATSUTextMeasurement>( kATSUDescentTag );
+ lineHeight = Fix2Long( ascent ) + Fix2Long( descent );
+ //SetDataBrowserTableViewRowHeight(lb, lineHeight + lineLeading);
+#endif
+
+ // !@&^#%$ we cant copy Font, but we need one for our custom drawing
+ Str255 fontName255;
+ char fontName[256];
+ FMGetFontFamilyName(style.font, fontName255);
+
+ CFStringRef fontNameCF = ::CFStringCreateWithPascalString( kCFAllocatorDefault, fontName255, kCFStringEncodingMacRoman );
+ ::CFStringGetCString( fontNameCF, fontName, (CFIndex)255, kCFStringEncodingMacRoman );
+
+ font.Create((const char *)fontName, 0, style.size, false, false);
+}
+
+void ListBoxImpl::SetAverageCharWidth(int width) {
+ aveCharWidth = width;
+}
+
+void ListBoxImpl::SetVisibleRows(int rows) {
+ desiredVisibleRows = rows;
+}
+
+int ListBoxImpl::GetVisibleRows() const {
+ // XXX Windows & GTK do this, but it seems incorrect to me. Other logic
+ // to do with visible rows is essentially the same across platforms.
+ return desiredVisibleRows;
+ /*
+ // This would be more correct
+ int rows = Length();
+ if ((rows == 0) || (rows > desiredVisibleRows))
+ rows = desiredVisibleRows;
+ return rows;
+ */
+}
+
+PRectangle ListBoxImpl::GetDesiredRect() {
+ PRectangle rcDesired = GetPosition();
+
+ // XXX because setting the line height on the table doesnt
+ // *stick*, we'll have to suffer and just use whatever
+ // the table desides is the correct height.
+ UInt16 itemHeight;// = lineHeight;
+ //GetDataBrowserTableViewRowHeight(lb, &itemHeight);
+
+ int rows = Length();
+ if ((rows == 0) || (rows > desiredVisibleRows))
+ rows = desiredVisibleRows;
+
+ rcDesired.bottom = itemHeight * rows;
+ rcDesired.right = rcDesired.left + maxItemWidth + aveCharWidth;
+
+ if (Length() > rows)
+ rcDesired.right += kScrollBarWidth;
+ rcDesired.right += IconWidth();
+
+ // Set the column width
+ //SetDataBrowserTableViewColumnWidth (lb, UInt16 (rcDesired.right - rcDesired.left));
+ return rcDesired;
+}
+
+void ListBoxImpl::ShowHideScrollbar() {
+ int rows = Length();
+ if (rows > desiredVisibleRows) {
+ //SetDataBrowserHasScrollBars(lb, false, true);
+ } else {
+ //SetDataBrowserHasScrollBars(lb, false, false);
+ }
+}
+
+int ListBoxImpl::IconWidth() {
+ return xset.GetWidth() + 2;
+}
+
+int ListBoxImpl::CaretFromEdge() {
+ return 0;
+}
+
+void ListBoxImpl::Clear() {
+ // passing NULL to "items" arg 4 clears the list
+ maxItemWidth = 0;
+ ld.Clear();
+ //AddDataBrowserItems (lb, kDataBrowserNoItem, 0, NULL, kDataBrowserItemNoProperty);
+}
+
+void ListBoxImpl::Append(char *s, int type) {
+ int count = Length();
+ CFStringRef r = CFStringCreateWithCString(NULL, s, kTextEncodingMacRoman);
+ ld.Add(count, type, r);
+
+ Scintilla::SurfaceImpl surface;
+ unsigned int width = surface.WidthText (font, s, strlen (s));
+ if (width > maxItemWidth)
+ maxItemWidth = width;
+
+ DataBrowserItemID items[1];
+ items[0] = count + 1;
+ //AddDataBrowserItems (lb, kDataBrowserNoItem, 1, items, kDataBrowserItemNoProperty);
+ ShowHideScrollbar();
+}
+
+void ListBoxImpl::SetList(const char* list, char separator, char typesep) {
+ // XXX copied from PlatGTK, should be in base class
+ Clear();
+ int count = strlen(list) + 1;
+ char *words = new char[count];
+ if (words) {
+ memcpy(words, list, count);
+ char *startword = words;
+ char *numword = NULL;
+ int i = 0;
+ for (; words[i]; i++) {
+ if (words[i] == separator) {
+ words[i] = '\0';
+ if (numword)
+ *numword = '\0';
+ Append(startword, numword?atoi(numword + 1):-1);
+ startword = words + i + 1;
+ numword = NULL;
+ } else if (words[i] == typesep) {
+ numword = words + i;
+ }
+ }
+ if (startword) {
+ if (numword)
+ *numword = '\0';
+ Append(startword, numword?atoi(numword + 1):-1);
+ }
+ delete []words;
+ }
+}
+
+int ListBoxImpl::Length() {
+ UInt32 numItems = 0;
+ //GetDataBrowserItemCount(lb, kDataBrowserNoItem, false, kDataBrowserItemAnyState, &numItems);
+ return (int)numItems;
+}
+
+void ListBoxImpl::Select(int n) {
+ DataBrowserItemID items[1];
+ items[0] = n + 1;
+ //SetDataBrowserSelectedItems(lb, 1, items, kDataBrowserItemsAssign);
+ //RevealDataBrowserItem(lb, items[0], kIconColumn, kDataBrowserRevealOnly);
+ // force update on selection
+ //Draw1Control(lb);
+}
+
+int ListBoxImpl::GetSelection() {
+ Handle selectedItems = NewHandle(0);
+ //GetDataBrowserItems(lb, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, selectedItems);
+ UInt32 numSelectedItems = GetHandleSize(selectedItems)/sizeof(DataBrowserItemID);
+ if (numSelectedItems == 0) {
+ return -1;
+ }
+ HLock( selectedItems );
+ DataBrowserItemID *individualItem = (DataBrowserItemID*)( *selectedItems );
+ DataBrowserItemID selected[numSelectedItems];
+ selected[0] = *individualItem;
+ HUnlock( selectedItems );
+ return selected[0] - 1;
+}
+
+int ListBoxImpl::Find(const char *prefix) {
+ int count = Length();
+ char s[255];
+ for (int i = 0; i < count; i++) {
+ GetValue(i, s, 255);
+ if (s[0] != NULL && (0 == strncmp(prefix, s, strlen(prefix)))) {
+ return i;
+ }
+ }
+ return - 1;
+}
+
+void ListBoxImpl::GetValue(int n, char *value, int len) {
+ CFStringRef textString = ld.GetString(n);
+ if (textString == NULL) {
+ value[0] = '\0';
+ return;
+ }
+ CFIndex numUniChars = CFStringGetLength( textString );
+
+ // XXX how do we know the encoding of the listbox?
+ CFStringEncoding encoding = kCFStringEncodingUTF8; //( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII);
+ CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1;
+ char* text = new char[maximumByteLength];
+ CFIndex usedBufferLength = 0;
+ CFStringGetBytes( textString, CFRangeMake( 0, numUniChars ), encoding,
+ '?', false, reinterpret_cast<UInt8*>( text ),
+ maximumByteLength, &usedBufferLength );
+ text[usedBufferLength] = '\0'; // null terminate the ASCII/UTF8 string
+
+ if (text && len > 0) {
+ strncpy(value, text, len);
+ value[len - 1] = '\0';
+ } else {
+ value[0] = '\0';
+ }
+ delete text;
+}
+
+void ListBoxImpl::Sort() {
+ // TODO: Implement this
+ fprintf(stderr, "ListBox::Sort\n");
+}
+
+void ListBoxImpl::RegisterImage(int type, const char *xpm_data) {
+ xset.Add(type, xpm_data);
+}
+
+void ListBoxImpl::ClearRegisteredImages() {
+ xset.Clear();
+}
+
+//----------------- Menu ---------------------------------------------------------------------------
+
+Menu::Menu()
+ : mid(0)
+{
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Menu::CreatePopUp() {
+ // TODO: Could I just feed a constant menu ID parameter, or does
+ // it really need to be unique?
+ static int nextMenuID = 1;
+ Destroy();
+ OSStatus err;
+ // err = CreateNewMenu( nextMenuID++, 0, reinterpret_cast<MenuRef*>( &id ) );
+}
+
+void Menu::Destroy() {
+}
+
+void Menu::Show(Point pt, Window &) {
+ /*
+ UInt32 userSelection = 0;
+ SInt16 menuId = 0;
+ MenuItemIndex menuItem = 0;
+ ::Point globalPoint;
+ globalPoint.h = pt.x;
+ globalPoint.v = pt.y;
+ OSStatus err;
+ err = ContextualMenuSelect( reinterpret_cast<MenuRef>( id ), globalPoint,
+ false, kCMHelpItemRemoveHelp, NULL,
+ NULL, &userSelection,
+ &menuId,
+ &menuItem
+ );
+ */
+}
+
+//----------------- ElapsedTime --------------------------------------------------------------------
+
+// TODO: Consider if I should be using GetCurrentEventTime instead of gettimeoday
+ElapsedTime::ElapsedTime() {
+ struct timeval curTime;
+ int retVal;
+ retVal = gettimeofday( &curTime, NULL );
+
+ bigBit = curTime.tv_sec;
+ littleBit = curTime.tv_usec;
+}
+
+double ElapsedTime::Duration(bool reset) {
+ struct timeval curTime;
+ int retVal;
+ retVal = gettimeofday( &curTime, NULL );
+ long endBigBit = curTime.tv_sec;
+ long endLittleBit = curTime.tv_usec;
+ double result = 1000000.0 * (endBigBit - bigBit);
+ result += endLittleBit - littleBit;
+ result /= 1000000.0;
+ if (reset) {
+ bigBit = endBigBit;
+ littleBit = endLittleBit;
+ }
+ return result;
+}
+
+//----------------- Platform -----------------------------------------------------------------------
+
+ColourDesired Platform::Chrome()
+{
+ return ColourDesired(0xE0, 0xE0, 0xE0);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+ColourDesired Platform::ChromeHighlight()
+{
+ return ColourDesired(0xFF, 0xFF, 0xFF);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Returns the currently set system font for the user.
+ */
+const char *Platform::DefaultFont()
+{
+ NSString* name = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSFixedPitchFont"];
+ return [name UTF8String];
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Returns the currently set system font size for the user.
+ */
+int Platform::DefaultFontSize()
+{
+ return [[NSUserDefaults standardUserDefaults] integerForKey: @"NSFixedPitchFontSize"];
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Returns the time span in which two consequtive mouse clicks must occur to be considered as
+ * double click.
+ *
+ * @return
+ */
+unsigned int Platform::DoubleClickTime()
+{
+ float threshold = [[NSUserDefaults standardUserDefaults] floatForKey:
+ @"com.apple.mouse.doubleClickThreshold"];
+ return static_cast<unsigned int>(threshold / kEventDurationMillisecond);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+bool Platform::MouseButtonBounce()
+{
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+bool Platform::IsKeyDown(int keyCode)
+{
+ return false;
+ // TODO: Map Scintilla/Windows key codes to Mac OS X key codes
+ // TODO: Do I need this?
+ /*
+ // Inspired by code at: http://www.sover.net/~jams/Morgan/docs/GameInputMethods.txt
+
+ // Get the keys
+ KeyMap keys;
+ GetKeys( keys );
+
+ // Calculate the key map index
+ long keyMapIndex = keys[keyCode/8];
+ // Calculate the individual bit to check
+ short bitToCheck = keyCode % 8;
+ // Check the status of the key
+ return ( keyMapIndex >> bitToCheck ) & 0x01;
+ */
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Helper method for the backend to reach through to the scintiall window.
+ */
+long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam)
+{
+ return scintilla_send_message( w, msg, wParam, lParam );
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
+ * Helper method for the backend to reach through to the scintiall window.
+ */
+long Platform::SendScintillaPointer(WindowID w, unsigned int msg, unsigned long wParam, void *lParam)
+{
+ return scintilla_send_message(w, msg, wParam, (long) lParam);
+}
+
+//--------------------------------------------------------------------------------------------------
+
+bool Platform::IsDBCSLeadByte(int codePage, char ch)
+{
+ // No support for DBCS.
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+int Platform::DBCSCharLength(int codePage, const char* s)
+{
+ // No support for DBCS.
+ return 1;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+int Platform::DBCSCharMaxLength()
+{
+ // No support for DBCS.
+ return 2;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+int Platform::Minimum(int a, int b)
+{
+ return (a < b) ? a : b;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+int Platform::Maximum(int a, int b) {
+ return (a > b) ? a : b;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+//#define TRACE
+#ifdef TRACE
+
+void Platform::DebugDisplay(const char *s)
+{
+ fprintf( stderr, s );
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Platform::DebugPrintf(const char *format, ...)
+{
+ const int BUF_SIZE = 2000;
+ char buffer[BUF_SIZE];
+
+ va_list pArguments;
+ va_start(pArguments, format);
+ vsnprintf(buffer, BUF_SIZE, format, pArguments);
+ va_end(pArguments);
+ Platform::DebugDisplay(buffer);
+}
+
+#else
+
+void Platform::DebugDisplay(const char *) {}
+
+void Platform::DebugPrintf(const char *, ...) {}
+
+#endif
+
+//--------------------------------------------------------------------------------------------------
+
+static bool assertionPopUps = true;
+
+bool Platform::ShowAssertionPopUps(bool assertionPopUps_)
+{
+ bool ret = assertionPopUps;
+ assertionPopUps = assertionPopUps_;
+ return ret;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void Platform::Assert(const char *c, const char *file, int line)
+{
+ char buffer[2000];
+ sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
+ strcat(buffer, "\r\n");
+ Platform::DebugDisplay(buffer);
+#ifdef DEBUG
+ // Jump into debugger in assert on Mac (CL269835)
+ ::Debugger();
+#endif
+}
+
+//--------------------------------------------------------------------------------------------------
+
+int Platform::Clamp(int val, int minVal, int maxVal)
+{
+ if (val > maxVal)
+ val = maxVal;
+ if (val < minVal)
+ val = minVal;
+ return val;
+}
+
+//----------------- DynamicLibrary -----------------------------------------------------------------
+
+/**
+ * Implements the platform specific part of library loading.
+ *
+ * @param modulePath The path to the module to load.
+ * @return A library instance or NULL if the module could not be found or another problem occured.
+ */
+DynamicLibrary* DynamicLibrary::Load(const char* modulePath)
+{
+ return NULL;
+}
+
+//--------------------------------------------------------------------------------------------------
+