/** * 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 * Based on PlatGTK.cxx Copyright 1998-2002 by Neil Hodgson * The License.txt file describes the conditions under which this software may be distributed. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt). */ #import #include "PlatCocoa.h" #include #include #include #include #include #include #include "XPM.h" #import #import // 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, int 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( 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; } //-------------------------------------------------------------------------------------------------- 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(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(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(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 / 255.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(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(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(s), len, *reinterpret_cast(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(buf), len+1, *reinterpret_cast(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(s), len, *reinterpret_cast(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(str), 1, *reinterpret_cast(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( font_.GetID() )->getAttribute( kATSUAscentTag ); return Fix2Long( ascent ); } int SurfaceImpl::Descent(Font &font_) { if (!font_.GetID()) return 1; ATSUTextMeasurement descent = reinterpret_cast( font_.GetID() )->getAttribute( 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( font_.GetID() )->getAttribute( 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( 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(wid); return [[container window] firstResponder] == container; } //-------------------------------------------------------------------------------------------------- PRectangle Window::GetPosition() { NSRect rect= [reinterpret_cast(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( 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( window.GetID() )); // WindowRef thisWindow = reinterpret_cast( 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( 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( wid ), show ); // } // // this is necessary for calltip/listbox // if (windowRef) { // WindowRef thisWindow = reinterpret_cast( 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(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(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(wid); [container setCursor: curs]; } } //-------------------------------------------------------------------------------------------------- void Window::SetTitle(const char *s) { // WindowRef window = GetControlOwner(reinterpret_cast( 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) { if (index >= len) { int lenNew = (index+1) * 2; int *typesNew = new int[lenNew]; CFStringRef *stringsNew = new CFStringRef[lenNew]; for (int i=0; i( kEventParamMouseButton, typeMouseButton, &inMouseButton ); UInt32 inClickCount; event.GetParameter( kEventParamClickCount, &inClickCount ); if (inMouseButton == kEventMouseButtonPrimary && inClickCount == 2) { // handle our single mouse click now ListBoxImpl* listbox = reinterpret_cast( 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( 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( kATSUAscentTag ); ATSUTextMeasurement descent = ts->getAttribute( 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] != '\0') && (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( 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(); } //----------------- ScintillaContextMenu ----------------------------------------------------------- @implementation ScintillaContextMenu : NSMenu // This NSMenu subclass serves also as target for menu commands and forwards them as // notfication messages to the front end. - (void) handleCommand: (NSMenuItem*) sender { owner->HandleCommand([sender tag]); } //-------------------------------------------------------------------------------------------------- - (void) setOwner: (Scintilla::ScintillaCocoa*) newOwner { owner = newOwner; } @end //----------------- Menu --------------------------------------------------------------------------- Menu::Menu() : mid(0) { } //-------------------------------------------------------------------------------------------------- void Menu::CreatePopUp() { Destroy(); mid = [[ScintillaContextMenu alloc] initWithTitle: @""]; } //-------------------------------------------------------------------------------------------------- void Menu::Destroy() { ScintillaContextMenu* menu = reinterpret_cast(mid); [menu release]; mid = NULL; } //-------------------------------------------------------------------------------------------------- void Menu::Show(Point pt, Window &) { // Cocoa menus are handled a bit differently. We only create the menu. The framework // takes care to show it properly. } //----------------- 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"]; if (threshold == 0) threshold = 0.5; return static_cast(threshold / kEventDurationMillisecond); } //-------------------------------------------------------------------------------------------------- bool Platform::MouseButtonBounce() { return false; } //-------------------------------------------------------------------------------------------------- /** * 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; } //--------------------------------------------------------------------------------------------------