diff options
Diffstat (limited to 'cocoa/PlatCocoa.mm')
-rw-r--r-- | cocoa/PlatCocoa.mm | 2130 |
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; +} + +//-------------------------------------------------------------------------------------------------- + |