diff options
Diffstat (limited to 'macosx/PlatMacOSX.cxx')
-rw-r--r-- | macosx/PlatMacOSX.cxx | 1922 |
1 files changed, 1922 insertions, 0 deletions
diff --git a/macosx/PlatMacOSX.cxx b/macosx/PlatMacOSX.cxx new file mode 100644 index 000000000..149b99ceb --- /dev/null +++ b/macosx/PlatMacOSX.cxx @@ -0,0 +1,1922 @@ +// Scintilla source code edit control +// PlatMacOSX.cxx - implementation of platform facilities on MacOS X/Carbon +// 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. + +#include <cstring> +#include <cstdio> +#include <cstdlib> + +#include <assert.h> + +#include <sys/time.h> + +#include <Carbon/Carbon.h> +#include "QuartzTextLayout.h" +#include "TCarbonEvent.h" + +#include "Platform.h" +#include "Scintilla.h" +#include "PlatMacOSX.h" +#include "XPM.h" + +using namespace Scintilla; + +#include "ScintillaWidget.h" + + +extern sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam); + +inline CGRect PRectangleToCGRect( PRectangle& rc ) { + return CGRectMake( rc.left, rc.top, rc.Width(), rc.Height() ); +} + +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; +} + +Scintilla::Point Scintilla::Point::FromLong(long lpoint) { + return Scintilla::Point( + Platform::LowShortFromLong(lpoint), + Platform::HighShortFromLong(lpoint)); +} + +// The Palette is just ignored on Mac OS X. OS X runs "Millions" or "Thousands" of colours. +Scintilla::Palette::Palette() { +} + +Scintilla::Palette::~Palette() { +} + +void Scintilla::Palette::Release() { +} + +// Do nothing if it "wants" a colour. Copy the colour from desired to allocated if it is "finding" a colour. +void Scintilla::Palette::WantFind(ColourPair &cp, bool want) { + if (want) { + } else { + cp.allocated.Set(cp.desired.AsLong()); + } +} + +void Scintilla::Palette::Allocate(Window &/*w*/) { + // OS X always runs in thousands or millions of colours +} + +Font::Font() : id(0) {} + +Font::~Font() { Release(); } + + +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(); + + id = new QuartzTextStyle(); + + // 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 }; + reinterpret_cast<QuartzTextStyle*>( id )->setAttributes( attributes, sizeof( attributes ) / sizeof( *attributes ) ); + + //ATSStyleRenderingOptions rendering = kATSStyleNoAntiAliasing; + //reinterpret_cast<QuartzTextStyle*>( id )->setAttribute( kATSUStyleRenderingOptionsTag, sizeof( rendering ), &rendering ); + + // TODO: Why do I have to manually set this? + reinterpret_cast<QuartzTextStyle*>( id )->setFontFeature( kLigaturesType, kCommonLigaturesOffSelector ); +} + +void Font::Release() { + if (id) + delete reinterpret_cast<QuartzTextStyle*>( id ); + + id = 0; +} + +SurfaceImpl::SurfaceImpl() { + bitmapData = NULL; // Release will try and delete bitmapData if != NULL +#ifdef SUPPORT_PORT + port = NULL; +#endif + gc = NULL; + Release(); +} + +SurfaceImpl::~SurfaceImpl() { + Release(); +} + +void SurfaceImpl::Release() { + if ( bitmapData != NULL ) + { + delete[] bitmapData; + // We only "own" the graphics context if we are a bitmap context + if ( gc != NULL ) CGContextRelease( gc ); + } +#ifdef SUPPORT_PORT + if ( port != NULL && gc != NULL) { + QDEndCGContext(port, &gc); + } +#endif + 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(); +#ifdef SUPPORT_PORT + // Note, this seems to be unecessary, I have not seen any functionality loss by + // doing nothing in this method. + + WindowRef window = GetControlOwner(reinterpret_cast<HIViewRef>(wid)); + port = GetWindowPort(window); + QDBeginCGContext(port, &gc); +#endif +} + +void SurfaceImpl::Init(SurfaceID sid, WindowID /*wid*/) { + Release(); + gc = reinterpret_cast<CGContextRef>( sid ); + CGContextSetLineWidth( gc, 1.0 ); +} + +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(); + assert( colorSpace != NULL ); + + // create the bitmap + bitmapData = new uint8_t[ bitmapByteCount ]; + assert( bitmapData != NULL ); + 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; + } + } + + // the context retains the color space, so we can release it + CGColorSpaceRelease( colorSpace ); + + assert( 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(); + assert( colorSpace != 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 ); + assert( dataProvider != NULL ); + + // create the CGImage + CGImageRef image = CGImageCreate( bitmapWidth, + bitmapHeight, + BITS_PER_COMPONENT, + BITS_PER_PIXEL, + bitmapBytesPerRow, + colorSpace, + kCGImageAlphaPremultipliedLast, + dataProvider, + NULL, + 0, + kCGRenderingIntentDefault ); + assert( image != NULL ); + + // 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; +} + +int SurfaceImpl::LogPixelsY() { + return 72; +} + +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 ) { + //CGContextBeginPath( gc ); + FillColour(back); + + CGRect rect = PRectangleToCGRect( rc ); + + CGContextFillRect( gc, rect ); + //CGContextDrawPath( gc, kCGPathFill ); + } +} + +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; + } + + assert( image != NULL ); + + 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 + ); + assert( pattern != NULL ); + + // Create a pattern color space + CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern( NULL ); + assert( 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; + CGPatternRelease( pattern ); + pattern = NULL; + CGImageRelease( image ); + image = 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 SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourAllocated fill, int alphaFill, + ColourAllocated outline, int alphaOutline, int flags) { + // XXX TODO + if ( gc ) { + CGContextBeginPath( gc ); + //FillColour(fill); + PenColour(outline); + + // 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, kCGPathFill ); + } +} + +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; + } + + assert( image != NULL ); + + // 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; +} + +QuartzTextLayout* SurfaceImpl::GetTextLayout( Font &font_, const char *s, int len ) +{ + // create the text layout + QuartzTextLayout* textLayout = new QuartzTextLayout( gc ); + OSStatus err = textLayout->setText( reinterpret_cast<const UInt8*>( s ), len, ( unicodeMode ? kCFStringEncodingUTF8 : kCFStringEncodingASCII ) ); + if (err != noErr) { + fprintf(stderr, "SurfaceImpl::GetTextLayout error calling textLayout->setText %d %s\n", err, unicodeMode?"Invalid UTF8":"Unknown error"); + if (unicodeMode) + err = textLayout->setText( reinterpret_cast<const UInt8*>( s ), len, kCFStringEncodingASCII ); + if (err != noErr) + return NULL; + } + + textLayout->setStyle( *reinterpret_cast<QuartzTextStyle*>( font_.GetID() ) ); + + // TODO: If I could modify Scintilla to use ATSUHighlightText, this would not be required. + // However, using this setting makes Scintilla's rendering match TextEdit's (except for the antialiasing rules) + ATSLineLayoutOptions rendering = kATSLineUseDeviceMetrics; + textLayout->setControl( kATSULineLayoutOptionsTag, sizeof( rendering ), &rendering ); + + return textLayout; +} + +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) { + PLATFORM_ASSERT( gc != NULL ); + QuartzTextLayout* textLayout = GetTextLayout( font_, s, len ); + if (!textLayout) return; + // The Quartz RGB fill color influences the ATSUI color + FillColour(fore); + + // Draw the text, with the Y axis flipped + textLayout->draw( rc.left, ybase, true ); + + // Get rid of the layout object + delete textLayout; + textLayout = NULL; +} + +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. + QuartzTextLayout* textLayout = GetTextLayout( font_, s, len ); + if (!textLayout) return; + + // Get the UNICODE character length + UniCharCount unicodeLength, unicodePosition; + OSStatus err; + err = ATSUGetTextLocation( textLayout->getLayout(), NULL, NULL, NULL, &unicodeLength, NULL ); + assert( err == noErr ); + + ATSLayoutRecord *layoutRecords; + ItemCount numRecords; + // Get the arrays of glyph information + verify_noerr( ATSUDirectGetLayoutDataArrayPtrFromTextLayout( + textLayout->getLayout(), 0, + kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, + (void **)&layoutRecords, &numRecords) ); + + int i, count; + long position; + unsigned char uch; + unsigned char mask; + FontID fontid = font_.GetID(); + + for ( unicodePosition = 0, i = 0; i < len && unicodePosition < numRecords; unicodePosition ++ ) { + if (fontid) { + + // note: an extra layoutRecord is provided (eg. numRecords will be one + // more than unicodeLength). Each record contains the left x + // coordinate, but we need the right x coordinate, so we skip + // the first layoutRecord, thus unicodePosition+1. + position = Fix2Long( layoutRecords[unicodePosition+1].realPos ); + 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 ) { + mask = 0xc0; + count = 1; + // Add one additonal byte for each extra high order one in the byte + while ( uch >= mask && count < 8 ) { + assert( i < len ); + positions[i++] = position; + count ++; + mask = mask >> 1 | 0x80; // add an additional one in the highest order position + } + assert( count <= 8 ); + } + } else { + positions[i++] = (i == 0) ? 1 : positions[i-1] + 1; + } + } + verify_noerr( ATSUDirectReleaseLayoutDataArrayPtr(NULL, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void **)&layoutRecords) ); + // Get rid of the layout object + delete textLayout; + textLayout = NULL; +} + +int SurfaceImpl::WidthText(Font &font_, const char *s, int len) { + if (font_.GetID()) + { + QuartzTextLayout* textLayout = GetTextLayout( font_, s, len ); + if (!textLayout) return 0; + + // 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" ); + // Get rid of the layout object + delete textLayout; + textLayout = NULL; + return 0; + } + + //Platform::DebugPrintf( "WidthText: \"%*s\" = %ld\n", len, s, Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x ) ); + + // Get rid of the layout object + delete textLayout; + textLayout = NULL; + 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()) + { + QuartzTextLayout* textLayout = GetTextLayout( font_, str, 1 ); + if (!textLayout) return 0; + + // 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" ); + // Get rid of the layout object + delete textLayout; + textLayout = NULL; + return 0; + } + + // Get rid of the layout object + delete textLayout; + textLayout = NULL; + 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 +} + +Surface *Surface::Allocate() { + return new SurfaceImpl( ); +} + +Window::~Window() {} + +void Window::Destroy() { + if (windowRef) { + DisposeWindow(reinterpret_cast<WindowRef>( windowRef )); + } + id = 0; +} + +bool Window::HasFocus() { + // TODO: Test this + return HIViewSubtreeContainsFocus( reinterpret_cast<HIViewRef>( id ) ); +} + +PRectangle Window::GetPosition() { + // Before any size allocated pretend its 1000 wide so not scrolled + PRectangle rc(0, 0, 1000, 1000); + + // The frame rectangle gives the position of this view inside the parent view + if (id) { + HIRect controlFrame; + HIViewGetFrame( reinterpret_cast<HIViewRef>( id ), &controlFrame ); + rc = CGRectToPRectangle( controlFrame ); + } + + return rc; +} + +void Window::SetPosition(PRectangle rc) { + // Moves this view inside the parent view + if ( id ) + { + // Set the frame on the view, the function handles the rest + CGRect r = PRectangleToCGRect( rc ); + HIViewSetFrame( reinterpret_cast<HIViewRef>( id ), &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 ( id ) { + HIViewSetVisible( reinterpret_cast<HIViewRef>( id ), show ); + } + // this is necessary for calltip/listbox + if (windowRef) { + WindowRef thisWindow = reinterpret_cast<WindowRef>( windowRef ); + if (show) { + ShowWindow( thisWindow ); + DrawControls( thisWindow ); + } else + HideWindow( thisWindow ); + } +} + +void Window::InvalidateAll() { + if ( id ) { + OSStatus err; + err = HIViewSetNeedsDisplay( reinterpret_cast<HIViewRef>( id ), true ); + assert( err == noErr ); + } +} + +void Window::InvalidateRectangle(PRectangle rc) { + if (id) { + // Create a rectangular region + RgnHandle region = NewRgn(); + assert( region != NULL ); + SetRectRgn( region, rc.left, rc.top, rc.right, rc.bottom ); + + // Make that region invalid + OSStatus err; + err = HIViewSetNeedsDisplayInRegion( reinterpret_cast<HIViewRef>( id ), region, true ); + assert( err == noErr ); + DisposeRgn( region ); + } +} + +void Window::SetFont(Font &) { + // TODO: Do I need to implement this? MSDN: specifies the font that a control is to use when drawing text. +} + +void Window::SetCursor(Cursor curs) { + if (id) { + // TODO: This isn't really implemented correctly. I should be using + // mouse tracking rectangles to only set the mouse cursor when it is over the control + ThemeCursor cursor; + + switch ( curs ) { + case cursorText: + cursor = kThemeIBeamCursor; + break; + case cursorArrow: + cursor = kThemeArrowCursor; + break; + case cursorWait: + cursor = kThemeWatchCursor; + break; + case cursorHoriz: + cursor = kThemeResizeLeftRightCursor; + break; + case cursorVert: + case cursorReverseArrow: + case cursorUp: + default: + cursor = kThemeArrowCursor; + break; + } + + SetThemeCursor( cursor ); + } +} + +void Window::SetTitle(const char *s) { + WindowRef window = GetControlOwner(reinterpret_cast<HIViewRef>( id )); + CFStringRef title = CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingMacRoman); + SetWindowTitleWithCFString(window, title); + CFRelease(title); +} + +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; + } + } +}; + +class ListBoxImpl : public ListBox { +private: + ControlRef lb; + XPMSet xset; + int lineHeight; + bool unicodeMode; + int desiredVisibleRows; + unsigned int maxItemCharacters; + 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), maxItemCharacters(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 = 1000; + + 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 + // XXX 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 ); + + id = lb; + SetControlVisibility(lb, true, true); + SetControl(lb); + SetWindow(outWindow); +} + +pascal OSStatus ListBoxImpl::WindowEventHandler( + EventHandlerCallRef inCallRef, + EventRef inEvent, + void* inUserData ) +{ + + switch (GetEventClass(inEvent)) { + case kEventClassMouse: + switch (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( kThemeBrushPrimaryHighlightColor, 32, true ); + 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 fontName; + if (FMGetFontFamilyName(style.font, fontName) != kFMInvalidFontFamilyErr) { + // make sure we conver to a proper C string first. + char cFontName[255]; + CopyPascalStringToC(fontName, cFontName); + font.Create((const char *)cFontName, 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; + int width = maxItemCharacters; + if (width < 12) + width = 12; + // XXX use the quartz text stuff to figure out the correct string width + rcDesired.right = rcDesired.left + width * (aveCharWidth+aveCharWidth/4); + if (rcDesired.right > maxWidth) { + rcDesired.right = maxWidth; + } + if (Length() > rows) + rcDesired.right += kScrollBarWidth; + rcDesired.right += IconWidth(); + 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 + maxItemCharacters = 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); + + DataBrowserItemID items[1]; + items[0] = count + 1; + AddDataBrowserItems (lb, kDataBrowserNoItem, 1, items, kDataBrowserItemNoProperty); + ShowHideScrollbar(); + + size_t len = strlen(s); + if (maxItemCharacters < len) + maxItemCharacters = len; + +} + +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() : id(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 ) ); + assert( noErr == err && id != NULL ); +} + +void Menu::Destroy() { + if ( id != NULL ) + { + ReleaseMenu( reinterpret_cast<MenuRef>( id ) ); + id = NULL; + } +} + +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 + ); + // The system should always handle the command for us, so we should get userCanceledErr + assert( /*noErr == err ||*/ userCanceledErr == err ); +} + +// TODO: Consider if I should be using GetCurrentEventTime instead of gettimeoday +ElapsedTime::ElapsedTime() { + struct timeval curTime; + int retVal; + retVal = gettimeofday( &curTime, NULL ); + assert( retVal == 0 ); + + bigBit = curTime.tv_sec; + littleBit = curTime.tv_usec; +} + +double ElapsedTime::Duration(bool reset) { + struct timeval curTime; + int retVal; + retVal = gettimeofday( &curTime, NULL ); + assert( retVal == 0 ); + 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; +} + +ColourDesired Platform::Chrome() { + RGBColor c; + GetThemeBrushAsColor(kThemeBrushButtonActiveDarkShadow , 24, true, &c); + return ColourDesired(c.red>>8, c.green>>8, c.blue>>8); +} + +ColourDesired Platform::ChromeHighlight() { + RGBColor c; + GetThemeBrushAsColor(kThemeBrushButtonInactiveLightShadow , 24, true, &c); + return ColourDesired(c.red>>8, c.green>>8, c.blue>>8); +} + +static Str255 PlatformDefaultFontName; +const char *Platform::DefaultFont() { + long id = HighShortFromLong(GetScriptVariable(smCurrentScript, smScriptAppFondSize)); + FMGetFontFamilyName(id, PlatformDefaultFontName); + return (const char *)PlatformDefaultFontName; +} + +int Platform::DefaultFontSize() { + return LowShortFromLong(GetScriptVariable(smCurrentScript, smScriptAppFondSize)); +} + +unsigned int Platform::DoubleClickTime() { + // Convert from ticks to milliseconds. I think it would be better to use the events to tell us + // when we have a double and triple click, but what do I know? + return static_cast<unsigned int>( TicksToEventTime( GetDblTime() ) / kEventDurationMillisecond ); +} + +bool Platform::MouseButtonBounce() { + return false; +} + +bool Platform::WaitMouseMoved(Scintilla::Point pt) { + ::Point mpt; + mpt.v = pt.x; + mpt.h = pt.y; + return ::WaitMouseMoved(mpt); +} + +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; + */ +} + +long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) { + return scintilla_send_message( w, msg, wParam, lParam ); +} + +bool Platform::IsDBCSLeadByte(int /*codePage*/, char /*ch*/) { + // TODO: Implement this + return false; +} + +int Platform::DBCSCharLength(int /*codePage*/, const char* /*s*/) { + // TODO: Implement this + return 1; +} + +int Platform::DBCSCharMaxLength() { + // TODO: Implement this + //return CFStringGetMaximumSizeForEncoding( 1, CFStringEncoding encoding ); + return 2; +} + +// These are utility functions not really tied to a platform +int Platform::Minimum(int a, int b) { + if (a < b) + return a; + else + return b; +} + +int Platform::Maximum(int a, int b) { + if (a > b) + return a; + else + return 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 + +// Not supported for GTK+ +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); + abort(); +} + +int Platform::Clamp(int val, int minVal, int maxVal) { + assert( minVal <= maxVal ); + if (val > maxVal) + val = maxVal; + if (val < minVal) + val = minVal; + return val; +} |