/** * 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). */ #include #include #include #include #include #include #include #include #include #include #import #import "Platform.h" #include "StringCopy.h" #include "XPM.h" #import "ScintillaView.h" #import "ScintillaCocoa.h" #import "PlatCocoa.h" 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(const 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(static_cast(rc.origin.x), static_cast(rc.origin.y), static_cast(NSMaxX(rc)), static_cast(NSMaxY(rc))); } //-------------------------------------------------------------------------------------------------- /** * Converts a PRectangle 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) ); } //----------------- Font --------------------------------------------------------------------------- Font::Font(): fid(0) { } //-------------------------------------------------------------------------------------------------- Font::~Font() { Release(); } //-------------------------------------------------------------------------------------------------- static int FontCharacterSet(Font &f) { return static_cast(f.GetID())->getCharacterSet(); } /** * Creates a CTFontRef with the given properties. */ void Font::Create(const FontParameters &fp) { Release(); QuartzTextStyle *style = new QuartzTextStyle(); fid = style; // Create the font with attributes QuartzFont font(fp.faceName, strlen(fp.faceName), fp.size, fp.weight, fp.italic); CTFontRef fontRef = font.getFontID(); style->setFontRef(fontRef, fp.characterSet); } //-------------------------------------------------------------------------------------------------- void Font::Release() { if (fid) delete static_cast(fid); fid = 0; } //----------------- SurfaceImpl -------------------------------------------------------------------- SurfaceImpl::SurfaceImpl() { unicodeMode = true; x = 0; y = 0; gc = NULL; textLayout.reset(new QuartzTextLayout(nullptr)); codePage = 0; verticalDeviceResolution = 0; bitmapData.reset(); // Release will try and delete bitmapData if != nullptr bitmapWidth = 0; bitmapHeight = 0; Release(); } //-------------------------------------------------------------------------------------------------- SurfaceImpl::~SurfaceImpl() { Release(); } //-------------------------------------------------------------------------------------------------- void SurfaceImpl::Release() { textLayout->setContext(nullptr); if (bitmapData) { bitmapData.reset(); // We only "own" the graphics context if we are a bitmap context if (gc) CGContextRelease(gc); } gc = NULL; bitmapWidth = 0; bitmapHeight = 0; x = 0; y = 0; } //-------------------------------------------------------------------------------------------------- bool SurfaceImpl::Initialised() { // We are initalised if the graphics context is not null return gc != NULL;// || port != NULL; } //-------------------------------------------------------------------------------------------------- void SurfaceImpl::Init(WindowID) { // To be able to draw, the surface must get a CGContext handle. We save the graphics port, // then acquire/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 // acquire/release of the context. Release(); } //-------------------------------------------------------------------------------------------------- void SurfaceImpl::Init(SurfaceID sid, WindowID) { Release(); gc = static_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.reset(new uint8_t[bitmapByteCount]); // create the context gc = CGBitmapContextCreate(bitmapData.get(), 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 bitmapData.reset(); } textLayout->setContext(gc); // the context retains the color space, so we can release it CGColorSpaceRelease(colorSpace); if (gc && bitmapData) { // "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)); } if (surface_) { SurfaceImpl *psurfOther = static_cast(surface_); unicodeMode = psurfOther->unicodeMode; codePage = psurfOther->codePage; } else { unicodeMode = true; codePage = SC_CP_UTF8; } } //-------------------------------------------------------------------------------------------------- void SurfaceImpl::PenColour(ColourDesired 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 ColourDesired &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) return nullptr; CGContextFlush(gc); // Create an RGB color space. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); if (colorSpace == NULL) return NULL; const int bitmapBytesPerRow = ((int) bitmapWidth * BYTES_PER_PIXEL); const int bitmapByteCount = (bitmapBytesPerRow * (int) bitmapHeight); // Make a copy of the bitmap data for the image creation and divorce it // From the SurfaceImpl lifetime CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, bitmapData.get(), bitmapByteCount); // Create a data provider. CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef); CGImageRef image = NULL; if (dataProvider != NULL) { // Create the CGImage. image = CGImageCreate(bitmapWidth, bitmapHeight, BITS_PER_COMPONENT, BITS_PER_PIXEL, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast, dataProvider, NULL, 0, kCGRenderingIntentDefault); } // The image retains the color space, so we can release it. CGColorSpaceRelease(colorSpace); colorSpace = NULL; // Done with the data provider. CGDataProviderRelease(dataProvider); dataProvider = NULL; // Done with the data provider. CFRelease(dataRef); return image; } //-------------------------------------------------------------------------------------------------- /** * Returns the vertical logical device resolution of the main monitor. * This is no longer called. * For Cocoa, all screens are treated as 72 DPI, even retina displays. */ int SurfaceImpl::LogPixelsY() { return 72; } //-------------------------------------------------------------------------------------------------- /** * Converts the logical font height in points into a device height. * For Cocoa, points are always used for the result even on retina displays. */ int SurfaceImpl::DeviceHeightFont(int points) { return points; } //-------------------------------------------------------------------------------------------------- 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 // division 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, ColourDesired fore, ColourDesired back) { // Allocate memory for the array of points. std::vector points(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.data(), npts); // Explicitly close the path, so it is closed for stroking AND filling (implicit close = filling only) CGContextClosePath(gc); CGContextDrawPath(gc, kCGPathFillStroke); } //-------------------------------------------------------------------------------------------------- void SurfaceImpl::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) { if (gc) { CGContextBeginPath(gc); FillColour(back); PenColour(fore); // Quartz integer -> float point conversion fun (see comment in SurfaceImpl::LineTo) // We subtract 1 from the Width() and Height() so that all our drawing is within the area defined // by the PRectangle. Otherwise, we draw one pixel too far to the right and bottom. CGContextAddRect(gc, CGRectMake(rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1)); CGContextDrawPath(gc, kCGPathFillStroke); } } //-------------------------------------------------------------------------------------------------- void SurfaceImpl::FillRectangle(PRectangle rc, ColourDesired back) { if (gc) { FillColour(back); // Snap rectangle boundaries to nearest int rc.left = lround(rc.left); rc.right = lround(rc.right); CGRect rect = PRectangleToCGRect(rc); CGContextFillRect(gc, rect); } } //-------------------------------------------------------------------------------------------------- static void drawImageRefCallback(void *info, CGContextRef gc) { CGImageRef pattern = static_cast(info); CGContextDrawImage(gc, CGRectMake(0, 0, CGImageGetWidth(pattern), CGImageGetHeight(pattern)), pattern); } //-------------------------------------------------------------------------------------------------- static void releaseImageRefCallback(void *info) { CGImageRelease(static_cast(info)); } //-------------------------------------------------------------------------------------------------- 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, ColourDesired(0)); return; } const CGPatternCallbacks drawImageCallbacks = { 0, drawImageRefCallback, releaseImageRefCallback }; CGPatternRef pattern = CGPatternCreate(image, CGRectMake(0, 0, patternSurface.bitmapWidth, patternSurface.bitmapHeight), CGAffineTransformIdentity, patternSurface.bitmapWidth, patternSurface.bitmapHeight, kCGPatternTilingNoDistortion, true, &drawImageCallbacks ); if (pattern != NULL) { // Create a pattern color space CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern(NULL); if (colorSpace != NULL) { CGContextSaveGState(gc); CGContextSetFillColorSpace(gc, colorSpace); // Unlike the documentation, you MUST pass in a "components" parameter: // For coloured patterns it is the alpha value. const CGFloat alpha = 1.0; CGContextSetFillPattern(gc, pattern, &alpha); CGContextFillRect(gc, PRectangleToCGRect(rc)); CGContextRestoreGState(gc); // Free the color space, the pattern and image CGColorSpaceRelease(colorSpace); } /* colorSpace != NULL */ colorSpace = NULL; CGPatternRelease(pattern); pattern = NULL; } /* pattern != NULL */ } void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) { // This is only called from the margin marker drawing code for SC_MARK_ROUNDRECT // The Win32 version does // ::RoundRect(hdc, rc.left + 1, rc.top, rc.right - 1, rc.bottom, 8, 8 ); // which is a rectangle with rounded corners each having a radius of 4 pixels. // It would be almost as good just cutting off the corners with lines at // 45 degrees as is done on GTK+. // Create a rectangle with semicircles at the corners const int MAX_RADIUS = 4; const int radius = std::min(MAX_RADIUS, static_cast(std::min(rc.Height()/2, rc.Width()/2))); // Points go clockwise, starting from just below the top left // Corners are kept together, so we can easily create arcs to connect them CGPoint corners[4][3] = { { { rc.left, rc.top + radius }, { rc.left, rc.top }, { rc.left + radius, rc.top }, }, { { rc.right - radius - 1, rc.top }, { rc.right - 1, rc.top }, { rc.right - 1, rc.top + radius }, }, { { rc.right - 1, rc.bottom - radius - 1 }, { rc.right - 1, rc.bottom - 1 }, { rc.right - radius - 1, rc.bottom - 1 }, }, { { rc.left + radius, rc.bottom - 1 }, { rc.left, rc.bottom - 1 }, { rc.left, rc.bottom - radius - 1 }, }, }; // Align the points in the middle of the pixels for (int i = 0; i < 4; ++ i) { for (int j = 0; j < 3; ++ j) { corners[i][j].x += 0.5; corners[i][j].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); } // DrawChamferedRectangle is a helper function for AlphaRectangle that either fills or strokes a // rectangle with its corners chamfered at 45 degrees. static void DrawChamferedRectangle(CGContextRef gc, PRectangle rc, int cornerSize, CGPathDrawingMode mode) { // Points go clockwise, starting from just below the top left CGPoint corners[4][2] = { { { rc.left, rc.top + cornerSize }, { rc.left + cornerSize, rc.top }, }, { { rc.right - cornerSize - 1, rc.top }, { rc.right - 1, rc.top + cornerSize }, }, { { rc.right - 1, rc.bottom - cornerSize - 1 }, { rc.right - cornerSize - 1, rc.bottom - 1 }, }, { { rc.left + cornerSize, rc.bottom - 1 }, { rc.left, rc.bottom - cornerSize - 1 }, }, }; // Align the points in the middle of the pixels for (int i = 0; i < 4; ++ i) { for (int j = 0; j < 2; ++ j) { corners[i][j].x += 0.5; corners[i][j].y += 0.5; } } // Move to the last point to begin the path CGContextBeginPath(gc); CGContextMoveToPoint(gc, corners[3][1].x, corners[3][1].y); for (int i = 0; i < 4; ++ i) { CGContextAddLineToPoint(gc, corners[i][0].x, corners[i][0].y); CGContextAddLineToPoint(gc, corners[i][1].x, corners[i][1].y); } // Close the path to enclose it for stroking and for filling, then draw it CGContextClosePath(gc); CGContextDrawPath(gc, mode); } void Scintilla::SurfaceImpl::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int /*flags*/) { if (gc) { // Snap rectangle boundaries to nearest int rc.left = lround(rc.left); rc.right = lround(rc.right); // Set the Fill color to match CGContextSetRGBFillColor(gc, fill.GetRed() / 255.0, fill.GetGreen() / 255.0, fill.GetBlue() / 255.0, alphaFill / 255.0); CGContextSetRGBStrokeColor(gc, outline.GetRed() / 255.0, outline.GetGreen() / 255.0, outline.GetBlue() / 255.0, alphaOutline / 255.0); PRectangle rcFill = rc; if (cornerSize == 0) { // A simple rectangle, no rounded corners if ((fill == outline) && (alphaFill == alphaOutline)) { // Optimization for simple case CGRect rect = PRectangleToCGRect(rcFill); CGContextFillRect(gc, rect); } else { rcFill.left += 1.0; rcFill.top += 1.0; rcFill.right -= 1.0; rcFill.bottom -= 1.0; CGRect rect = PRectangleToCGRect(rcFill); CGContextFillRect(gc, rect); CGContextAddRect(gc, CGRectMake(rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1)); CGContextStrokePath(gc); } } else { // Approximate rounded corners with 45 degree chamfers. // Drawing real circular arcs often leaves some over- or under-drawn pixels. if ((fill == outline) && (alphaFill == alphaOutline)) { // Specializing this case avoids a few stray light/dark pixels in corners. rcFill.left -= 0.5; rcFill.top -= 0.5; rcFill.right += 0.5; rcFill.bottom += 0.5; DrawChamferedRectangle(gc, rcFill, cornerSize, kCGPathFill); } else { rcFill.left += 0.5; rcFill.top += 0.5; rcFill.right -= 0.5; rcFill.bottom -= 0.5; DrawChamferedRectangle(gc, rcFill, cornerSize-1, kCGPathFill); DrawChamferedRectangle(gc, rc, cornerSize, kCGPathStroke); } } } } static void ProviderReleaseData(void *, const void *data, size_t) { const unsigned char *pixels = static_cast(data); delete []pixels; } static CGImageRef ImageCreateFromRGBA(int width, int height, const unsigned char *pixelsImage, bool invert) { CGImageRef image = 0; // Create an RGB color space. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); if (colorSpace) { const int bitmapBytesPerRow = ((int) width * 4); const int bitmapByteCount = (bitmapBytesPerRow * (int) height); // Create a data provider. CGDataProviderRef dataProvider = 0; if (invert) { unsigned char *pixelsUpsideDown = new unsigned char[bitmapByteCount]; for (int y=0; y(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)) { CGFloat sx = CGRectGetWidth(dst) / CGRectGetWidth(src); CGFloat sy = CGRectGetHeight(dst) / CGRectGetHeight(src); CGFloat dx = CGRectGetMinX(dst) - (CGRectGetMinX(src) * sx); CGFloat dy = CGRectGetMinY(dst) - (CGRectGetMinY(src) * sy); drawRect = CGRectMake(dx, dy, w*sx, h*sy); } CGContextSaveGState(gc); CGContextClipToRect(gc, dst); CGContextDrawImage(gc, drawRect, image); CGContextRestoreGState(gc); CGImageRelease(image); } void SurfaceImpl::Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSource) { // Maybe we have to make the Surface two contexts: // a bitmap context which we do all the drawing on, and then a "real" context // which we copy the output to when we call "Synchronize". Ugh! Gross and slow! // For now, assume that copy can only be called on PixMap surfaces SurfaceImpl &source = static_cast(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, ColourDesired(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_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back) { FillRectangle(rc, back); DrawTextTransparent(rc, font_, ybase, s, len, fore); } //-------------------------------------------------------------------------------------------------- void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back) { CGContextSaveGState(gc); CGContextClipToRect(gc, PRectangleToCGRect(rc)); DrawTextNoClip(rc, font_, ybase, s, len, fore, back); CGContextRestoreGState(gc); } //-------------------------------------------------------------------------------------------------- CFStringEncoding EncodingFromCharacterSet(bool unicode, int characterSet) { if (unicode) return kCFStringEncodingUTF8; // Unsupported -> Latin1 as reasonably safe enum { notSupported = kCFStringEncodingISOLatin1}; switch (characterSet) { case SC_CHARSET_ANSI: return kCFStringEncodingISOLatin1; case SC_CHARSET_DEFAULT: return kCFStringEncodingISOLatin1; case SC_CHARSET_BALTIC: return kCFStringEncodingWindowsBalticRim; case SC_CHARSET_CHINESEBIG5: return kCFStringEncodingBig5; case SC_CHARSET_EASTEUROPE: return kCFStringEncodingWindowsLatin2; case SC_CHARSET_GB2312: return kCFStringEncodingGB_18030_2000; case SC_CHARSET_GREEK: return kCFStringEncodingWindowsGreek; case SC_CHARSET_HANGUL: return kCFStringEncodingEUC_KR; case SC_CHARSET_MAC: return kCFStringEncodingMacRoman; case SC_CHARSET_OEM: return kCFStringEncodingISOLatin1; case SC_CHARSET_RUSSIAN: return kCFStringEncodingKOI8_R; case SC_CHARSET_CYRILLIC: return kCFStringEncodingWindowsCyrillic; case SC_CHARSET_SHIFTJIS: return kCFStringEncodingShiftJIS; case SC_CHARSET_SYMBOL: return kCFStringEncodingMacSymbol; case SC_CHARSET_TURKISH: return kCFStringEncodingWindowsLatin5; case SC_CHARSET_JOHAB: return kCFStringEncodingWindowsKoreanJohab; case SC_CHARSET_HEBREW: return kCFStringEncodingWindowsHebrew; case SC_CHARSET_ARABIC: return kCFStringEncodingWindowsArabic; case SC_CHARSET_VIETNAMESE: return kCFStringEncodingWindowsVietnamese; case SC_CHARSET_THAI: return kCFStringEncodingISOLatinThai; case SC_CHARSET_8859_15: return kCFStringEncodingISOLatin1; default: return notSupported; } } void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore) { CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_)); ColourDesired colour(fore.AsLong()); CGColorRef color = CGColorCreateGenericRGB(colour.GetRed()/255.0, colour.GetGreen()/255.0, colour.GetBlue()/255.0, 1.0); QuartzTextStyle *style = static_cast(font_.GetID()); style->setCTStyleColor(color); CGColorRelease(color); textLayout->setText(reinterpret_cast(s), len, encoding, *static_cast(font_.GetID())); textLayout->draw(rc.left, ybase); } static size_t utf8LengthFromLead(unsigned char uch) { if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) { return 4; } else if (uch >= (0x80 + 0x40 + 0x20)) { return 3; } else if (uch >= (0x80)) { return 2; } else { return 1; } } //-------------------------------------------------------------------------------------------------- void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, XYPOSITION *positions) { CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_)); textLayout->setText(reinterpret_cast(s), len, encoding, *static_cast(font_.GetID())); CTLineRef mLine = textLayout->getCTLine(); assert(mLine != NULL); if (unicodeMode) { // Map the widths given for UTF-16 characters back onto the UTF-8 input string CFIndex fit = textLayout->getStringLength(); int ui=0; const unsigned char *us = reinterpret_cast(s); int i=0; while (ui(xPosition); } ui += codeUnits; } XYPOSITION lastPos = 0.0f; if (i > 0) lastPos = positions[i-1]; while (i(xPosition); } ui++; } } else { // Single byte encoding for (int i=0; i(xPosition); } } } XYPOSITION SurfaceImpl::WidthText(Font &font_, const char *s, int len) { if (font_.GetID()) { CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_)); textLayout->setText(reinterpret_cast(s), len, encoding, *static_cast(font_.GetID())); return static_cast(textLayout->MeasureStringWidth()); } return 1; } XYPOSITION SurfaceImpl::WidthChar(Font &font_, char ch) { char str[2] = { ch, '\0' }; if (font_.GetID()) { CFStringEncoding encoding = EncodingFromCharacterSet(unicodeMode, FontCharacterSet(font_)); textLayout->setText(reinterpret_cast(str), 1, encoding, *static_cast(font_.GetID())); return textLayout->MeasureStringWidth(); } else return 1; } // This string contains a good range of characters to test for size. const char sizeString[] = "`~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890" "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; XYPOSITION SurfaceImpl::Ascent(Font &font_) { if (!font_.GetID()) return 1; float ascent = static_cast(font_.GetID())->getAscent(); return ascent + 0.5f; } XYPOSITION SurfaceImpl::Descent(Font &font_) { if (!font_.GetID()) return 1; float descent = static_cast(font_.GetID())->getDescent(); return descent + 0.5f; } XYPOSITION SurfaceImpl::InternalLeading(Font &) { return 0; } XYPOSITION SurfaceImpl::ExternalLeading(Font &font_) { if (!font_.GetID()) return 1; float leading = static_cast(font_.GetID())->getLeading(); return leading + 0.5f; } XYPOSITION SurfaceImpl::Height(Font &font_) { return Ascent(font_) + Descent(font_); } XYPOSITION SurfaceImpl::AverageCharWidth(Font &font_) { if (!font_.GetID()) return 1; const int sizeStringLength = ELEMENTS(sizeString); XYPOSITION width = WidthText(font_, sizeString, sizeStringLength); return (int)((width / (float) sizeStringLength) + 0.5); } void SurfaceImpl::SetClip(PRectangle rc) { CGContextClipToRect(gc, PRectangleToCGRect(rc)); } void SurfaceImpl::FlushCachedState() { CGContextSynchronize(gc); } void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) { unicodeMode = unicodeMode_; } void SurfaceImpl::SetDBCSMode(int codePage_) { if (codePage_ && (codePage_ != SC_CP_UTF8)) codePage = codePage_; } Surface *Surface::Allocate(int) { return new SurfaceImpl(); } //----------------- Window ------------------------------------------------------------------------- // Cocoa uses different types for windows and views, so a Window may // be either an NSWindow or NSView and the code will check the type // before performing an action. Window::~Window() { } // Window::Destroy needs to see definition of ListBoxImpl so is located after ListBoxImpl //-------------------------------------------------------------------------------------------------- bool Window::HasFocus() { NSView *container = (__bridge NSView *)(wid); return container.window.firstResponder == container; } //-------------------------------------------------------------------------------------------------- static CGFloat ScreenMax() { return NSMaxY([NSScreen mainScreen].frame); } //-------------------------------------------------------------------------------------------------- PRectangle Window::GetPosition() { if (wid) { NSRect rect; id idWin = (__bridge id)(wid); NSWindow *win; if ([idWin isKindOfClass: [NSView class]]) { // NSView NSView *view = idWin; win = view.window; rect = [view convertRect: view.bounds toView: nil]; rect = [win convertRectToScreen: rect]; } else { // NSWindow win = idWin; rect = win.frame; } CGFloat screenHeight = ScreenMax(); // Invert screen positions to match Scintilla return PRectangle( static_cast(NSMinX(rect)), static_cast(screenHeight - NSMaxY(rect)), static_cast(NSMaxX(rect)), static_cast(screenHeight - NSMinY(rect))); } else { return PRectangle(0, 0, 1, 1); } } //-------------------------------------------------------------------------------------------------- void Window::SetPosition(PRectangle rc) { if (wid) { id idWin = (__bridge id)(wid); if ([idWin isKindOfClass: [NSView class]]) { // NSView // Moves this view inside the parent view NSRect nsrc = NSMakeRect(rc.left, rc.bottom, rc.Width(), rc.Height()); NSView *view = idWin; nsrc = [view.window convertRectFromScreen: nsrc]; view.frame = nsrc; } else { // NSWindow PLATFORM_ASSERT([idWin isKindOfClass: [NSWindow class]]); NSWindow *win = idWin; CGFloat screenHeight = ScreenMax(); NSRect nsrc = NSMakeRect(rc.left, screenHeight - rc.bottom, rc.Width(), rc.Height()); [win setFrame: nsrc display: YES]; } } } //-------------------------------------------------------------------------------------------------- void Window::SetPositionRelative(PRectangle rc, Window window) { PRectangle rcOther = window.GetPosition(); rc.left += rcOther.left; rc.right += rcOther.left; rc.top += rcOther.top; rc.bottom += rcOther.top; SetPosition(rc); } //-------------------------------------------------------------------------------------------------- PRectangle Window::GetClientPosition() { // This means, in MacOS X terms, get the "frame bounds". Call GetPosition, just like on Win32. return GetPosition(); } //-------------------------------------------------------------------------------------------------- void Window::Show(bool show) { if (wid) { id idWin = (__bridge id)(wid); if ([idWin isKindOfClass: [NSWindow class]]) { NSWindow *win = idWin; if (show) { [win orderFront: nil]; } else { [win orderOut: nil]; } } } } //-------------------------------------------------------------------------------------------------- /** * Invalidates the entire window or view so it is completely redrawn. */ void Window::InvalidateAll() { if (wid) { id idWin = (__bridge id)(wid); NSView *container; if ([idWin isKindOfClass: [NSView class]]) { container = idWin; } else { // NSWindow NSWindow *win = idWin; container = win.contentView; container.needsDisplay = YES; } container.needsDisplay = YES; } } //-------------------------------------------------------------------------------------------------- /** * Invalidates part of the window or view so only this part redrawn. */ void Window::InvalidateRectangle(PRectangle rc) { if (wid) { id idWin = (__bridge id)(wid); NSView *container; if ([idWin isKindOfClass: [NSView class]]) { container = idWin; } else { // NSWindow NSWindow *win = idWin; container = win.contentView; } [container setNeedsDisplayInRect: PRectangleToNSRect(rc)]; } } //-------------------------------------------------------------------------------------------------- void Window::SetFont(Font &) { // Implemented on list subclass on Cocoa. } //-------------------------------------------------------------------------------------------------- /** * Converts the Scintilla cursor enum into an NSCursor and stores it in the associated NSView, * which then will take care to set up a new mouse tracking rectangle. */ void Window::SetCursor(Cursor curs) { if (wid) { id idWin = (__bridge id)(wid); if ([idWin isKindOfClass: [SCIContentView class]]) { SCIContentView *container = idWin; [container setCursor: curs]; } } } //-------------------------------------------------------------------------------------------------- void Window::SetTitle(const char *s) { if (wid) { id idWin = (__bridge id)(wid); if ([idWin isKindOfClass: [NSWindow class]]) { NSWindow *win = idWin; NSString *sTitle = @(s); win.title = sTitle; } } } //-------------------------------------------------------------------------------------------------- PRectangle Window::GetMonitorRect(Point) { if (wid) { id idWin = (__bridge id)(wid); if ([idWin isKindOfClass: [NSView class]]) { NSView *view = idWin; idWin = view.window; } if ([idWin isKindOfClass: [NSWindow class]]) { PRectangle rcPosition = GetPosition(); NSWindow *win = idWin; NSScreen *screen = win.screen; NSRect rect = screen.visibleFrame; CGFloat screenHeight = rect.origin.y + rect.size.height; // Invert screen positions to match Scintilla PRectangle rcWork( static_cast(NSMinX(rect)), static_cast(screenHeight - NSMaxY(rect)), static_cast(NSMaxX(rect)), static_cast(screenHeight - NSMinY(rect))); PRectangle rcMonitor(rcWork.left - rcPosition.left, rcWork.top - rcPosition.top, rcWork.right - rcPosition.left, rcWork.bottom - rcPosition.top); return rcMonitor; } } return PRectangle(); } //----------------- ImageFromXPM ------------------------------------------------------------------- // Convert an XPM image into an NSImage for use with Cocoa static NSImage *ImageFromXPM(XPM *pxpm) { NSImage *img = nil; if (pxpm) { const int width = pxpm->GetWidth(); const int height = pxpm->GetHeight(); PRectangle rcxpm(0, 0, width, height); std::unique_ptr surfaceXPM(Surface::Allocate(SC_TECHNOLOGY_DEFAULT)); surfaceXPM->InitPixMap(width, height, NULL, NULL); SurfaceImpl *surfaceIXPM = static_cast(surfaceXPM.get()); CGContextClearRect(surfaceIXPM->GetContext(), CGRectMake(0, 0, width, height)); pxpm->Draw(surfaceXPM.get(), rcxpm); CGImageRef imageRef = surfaceIXPM->GetImage(); img = [[NSImage alloc] initWithCGImage: imageRef size: NSZeroSize]; CGImageRelease(imageRef); } return img; } //----------------- ListBox and related classes ---------------------------------------------------- //----------------- IListBox ----------------------------------------------------------------------- namespace { // unnamed namespace hides IListBox interface class IListBox { public: virtual int Rows() = 0; virtual NSImage *ImageForRow(NSInteger row) = 0; virtual NSString *TextForRow(NSInteger row) = 0; virtual void DoubleClick() = 0; }; } // unnamed namespace //----------------- AutoCompletionDataSource ------------------------------------------------------- @interface AutoCompletionDataSource : NSObject #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 #endif { IListBox *box; } @property IListBox *box; @end @implementation AutoCompletionDataSource @synthesize box; - (void) doubleClick: (id) sender { #pragma unused(sender) if (box) { box->DoubleClick(); } } - (id) tableView: (NSTableView *) aTableView objectValueForTableColumn: (NSTableColumn *) aTableColumn row: (NSInteger) rowIndex { #pragma unused(aTableView) if (!box) return nil; if ([(NSString *)aTableColumn.identifier isEqualToString: @"icon"]) { return box->ImageForRow(rowIndex); } else { return box->TextForRow(rowIndex); } } - (void) tableView: (NSTableView *) aTableView setObjectValue: anObject forTableColumn: (NSTableColumn *) aTableColumn row: (NSInteger) rowIndex { #pragma unused(aTableView) #pragma unused(anObject) #pragma unused(aTableColumn) #pragma unused(rowIndex) } - (NSInteger) numberOfRowsInTableView: (NSTableView *) aTableView { #pragma unused(aTableView) if (!box) return 0; return box->Rows(); } @end //----------------- ListBoxImpl -------------------------------------------------------------------- namespace { // unnamed namespace hides ListBoxImpl and associated classes struct RowData { int type; std::string text; RowData(int type_, const char *text_) : type(type_), text(text_) { } }; class LinesData { std::vector lines; public: LinesData() { } ~LinesData() { } int Length() const { return static_cast(lines.size()); } void Clear() { lines.clear(); } void Add(int /* index */, int type, char *str) { lines.push_back(RowData(type, str)); } int GetType(size_t index) const { if (index < lines.size()) { return lines[index].type; } else { return 0; } } const char *GetString(size_t index) const { if (index < lines.size()) { return lines[index].text.c_str(); } else { return 0; } } }; class ListBoxImpl : public ListBox, IListBox { private: NSMutableDictionary *images; int lineHeight; bool unicodeMode; int desiredVisibleRows; XYPOSITION maxItemWidth; unsigned int aveCharWidth; XYPOSITION maxIconWidth; Font font; int maxWidth; NSTableView *table; NSScrollView *scroller; NSTableColumn *colIcon; NSTableColumn *colText; AutoCompletionDataSource *ds; LinesData ld; CallBackAction doubleClickAction; void *doubleClickActionData; public: ListBoxImpl() : images(nil), lineHeight(10), unicodeMode(false), desiredVisibleRows(5), maxItemWidth(0), aveCharWidth(8), maxIconWidth(0), maxWidth(2000), table(nil), scroller(nil), colIcon(nil), colText(nil), ds(nil), doubleClickAction(nullptr), doubleClickActionData(nullptr) { images = [[NSMutableDictionary alloc] init]; } ~ListBoxImpl() override { } // ListBox methods void SetFont(Font &font) override; void Create(Window &parent, int ctrlID, Scintilla::Point pt, int lineHeight_, bool unicodeMode_, int technology_) override; void SetAverageCharWidth(int width) override; void SetVisibleRows(int rows) override; int GetVisibleRows() const override; PRectangle GetDesiredRect() override; int CaretFromEdge() override; void Clear() override; void Append(char *s, int type = -1) override; int Length() override; void Select(int n) override; int GetSelection() override; int Find(const char *prefix) override; void GetValue(int n, char *value, int len) override; void RegisterImage(int type, const char *xpm_data) override; void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override; void ClearRegisteredImages() override; void SetDoubleClickAction(CallBackAction action, void *data) override { doubleClickAction = action; doubleClickActionData = data; } void SetList(const char *list, char separator, char typesep) override; // To clean up when closed void ReleaseViews(); // For access from AutoCompletionDataSource implement IListBox int Rows() override; NSImage *ImageForRow(NSInteger row) override; NSString *TextForRow(NSInteger row) override; void DoubleClick() override; }; void ListBoxImpl::Create(Window & /*parent*/, int /*ctrlID*/, Scintilla::Point pt, int lineHeight_, bool unicodeMode_, int) { lineHeight = lineHeight_; unicodeMode = unicodeMode_; maxWidth = 2000; NSRect lbRect = NSMakeRect(pt.x, pt.y, 120, lineHeight * desiredVisibleRows); NSWindow *winLB = [[NSWindow alloc] initWithContentRect: lbRect styleMask: NSBorderlessWindowMask backing: NSBackingStoreBuffered defer: NO]; [winLB setLevel: NSFloatingWindowLevel]; [winLB setHasShadow: YES]; NSRect scRect = NSMakeRect(0, 0, lbRect.size.width, lbRect.size.height); scroller = [[NSScrollView alloc] initWithFrame: scRect]; [scroller setHasVerticalScroller: YES]; table = [[NSTableView alloc] initWithFrame: scRect]; [table setHeaderView: nil]; scroller.documentView = table; colIcon = [[NSTableColumn alloc] initWithIdentifier: @"icon"]; colIcon.width = 20; [colIcon setEditable: NO]; [colIcon setHidden: YES]; NSImageCell *imCell = [[NSImageCell alloc] init]; colIcon.dataCell = imCell; [table addTableColumn: colIcon]; colText = [[NSTableColumn alloc] initWithIdentifier: @"name"]; colText.resizingMask = NSTableColumnAutoresizingMask; [colText setEditable: NO]; [table addTableColumn: colText]; ds = [[AutoCompletionDataSource alloc] init]; ds.box = this; table.dataSource = ds; // Weak reference scroller.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; [winLB.contentView addSubview: scroller]; table.target = ds; table.doubleAction = @selector(doubleClick:); table.selectionHighlightStyle = NSTableViewSelectionHighlightStyleSourceList; wid = (__bridge_retained WindowID)winLB; } void ListBoxImpl::SetFont(Font &font_) { // NSCell setFont takes an NSFont* rather than a CTFontRef but they // are the same thing toll-free bridged. QuartzTextStyle *style = static_cast(font_.GetID()); font.Release(); font.SetID(new QuartzTextStyle(*style)); NSFont *pfont = (__bridge NSFont *)style->getFontRef(); [colText.dataCell setFont: pfont]; CGFloat itemHeight = ceil(pfont.boundingRectForFont.size.height); table.rowHeight = itemHeight; } void ListBoxImpl::SetAverageCharWidth(int width) { aveCharWidth = width; } void ListBoxImpl::SetVisibleRows(int rows) { desiredVisibleRows = rows; } int ListBoxImpl::GetVisibleRows() const { return desiredVisibleRows; } PRectangle ListBoxImpl::GetDesiredRect() { PRectangle rcDesired; rcDesired = GetPosition(); // There appears to be an extra pixel above and below the row contents CGFloat itemHeight = table.rowHeight + 2; int rows = Length(); if ((rows == 0) || (rows > desiredVisibleRows)) rows = desiredVisibleRows; rcDesired.bottom = rcDesired.top + static_cast(itemHeight * rows); rcDesired.right = rcDesired.left + maxItemWidth + aveCharWidth; rcDesired.right += 4; // Ensures no truncation of text if (Length() > rows) { [scroller setHasVerticalScroller: YES]; rcDesired.right += [NSScroller scrollerWidthForControlSize: NSRegularControlSize scrollerStyle: NSScrollerStyleLegacy]; } else { [scroller setHasVerticalScroller: NO]; } rcDesired.right += maxIconWidth; rcDesired.right += 6; // For icon space return rcDesired; } int ListBoxImpl::CaretFromEdge() { if (colIcon.hidden) return 3; else return 6 + static_cast(colIcon.width); } void ListBoxImpl::ReleaseViews() { [table setDataSource: nil]; table = nil; scroller = nil; colIcon = nil; colText = nil; ds = nil; } void ListBoxImpl::Clear() { maxItemWidth = 0; maxIconWidth = 0; ld.Clear(); } void ListBoxImpl::Append(char *s, int type) { int count = Length(); ld.Add(count, type, s); Scintilla::SurfaceImpl surface; XYPOSITION width = surface.WidthText(font, s, static_cast(strlen(s))); if (width > maxItemWidth) { maxItemWidth = width; colText.width = maxItemWidth; } NSImage *img = images[@(type)]; if (img) { XYPOSITION widthIcon = static_cast(img.size.width); if (widthIcon > maxIconWidth) { [colIcon setHidden: NO]; maxIconWidth = widthIcon; colIcon.width = maxIconWidth; } } } void ListBoxImpl::SetList(const char *list, char separator, char typesep) { Clear(); size_t count = strlen(list) + 1; std::vector words(list, list+count); char *startword = words.data(); 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.data() + i + 1; numword = NULL; } else if (words[i] == typesep) { numword = words.data() + i; } } if (startword) { if (numword) *numword = '\0'; Append(startword, numword?atoi(numword + 1):-1); } [table reloadData]; } int ListBoxImpl::Length() { return ld.Length(); } void ListBoxImpl::Select(int n) { [table selectRowIndexes: [NSIndexSet indexSetWithIndex: n] byExtendingSelection: NO]; [table scrollRowToVisible: n]; } int ListBoxImpl::GetSelection() { return static_cast(table.selectedRow); } int ListBoxImpl::Find(const char *prefix) { int count = Length(); for (int i = 0; i < count; i++) { const char *s = ld.GetString(i); if (s && (s[0] != '\0') && (0 == strncmp(prefix, s, strlen(prefix)))) { return i; } } return - 1; } void ListBoxImpl::GetValue(int n, char *value, int len) { const char *textString = ld.GetString(n); if (textString == NULL) { value[0] = '\0'; return; } strlcpy(value, textString, len); } void ListBoxImpl::RegisterImage(int type, const char *xpm_data) { XPM xpm(xpm_data); NSImage *img = ImageFromXPM(&xpm); images[@(type)] = img; } void ListBoxImpl::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) { CGImageRef imageRef = ImageCreateFromRGBA(width, height, pixelsImage, false); NSImage *img = [[NSImage alloc] initWithCGImage: imageRef size: NSZeroSize]; CGImageRelease(imageRef); images[@(type)] = img; } void ListBoxImpl::ClearRegisteredImages() { [images removeAllObjects]; } int ListBoxImpl::Rows() { return ld.Length(); } NSImage *ListBoxImpl::ImageForRow(NSInteger row) { return images[@(ld.GetType(row))]; } NSString *ListBoxImpl::TextForRow(NSInteger row) { const char *textString = ld.GetString(row); NSString *sTitle; if (unicodeMode) sTitle = @(textString); else sTitle = [NSString stringWithCString: textString encoding: NSWindowsCP1252StringEncoding]; return sTitle; } void ListBoxImpl::DoubleClick() { if (doubleClickAction) { doubleClickAction(doubleClickActionData); } } } // unnamed namespace //----------------- ListBox ------------------------------------------------------------------------ ListBox::ListBox() { } ListBox::~ListBox() { } ListBox *ListBox::Allocate() { ListBoxImpl *lb = new ListBoxImpl(); return lb; } //-------------------------------------------------------------------------------------------------- void Window::Destroy() { ListBoxImpl *listbox = dynamic_cast(this); if (listbox) { listbox->ReleaseViews(); } if (wid) { id idWin = (__bridge id)(wid); if ([idWin isKindOfClass: [NSWindow class]]) { CFBridgingRelease(wid); } } wid = nullptr; } //----------------- ScintillaContextMenu ----------------------------------------------------------- @implementation ScintillaContextMenu : NSMenu // This NSMenu subclass serves also as target for menu commands and forwards them as // notification 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 = (__bridge_retained MenuID)[[ScintillaContextMenu alloc] initWithTitle: @""]; } //-------------------------------------------------------------------------------------------------- void Menu::Destroy() { CFBridgingRelease(mid); mid = nullptr; } //-------------------------------------------------------------------------------------------------- void Menu::Show(Point, Window &) { // Cocoa menus are handled a bit differently. We only create the menu. The framework // takes care to show it properly. } //----------------- ElapsedTime -------------------------------------------------------------------- // ElapsedTime is used for precise performance measurements during development // and not for anything a user sees. ElapsedTime::ElapsedTime() { struct timeval curTime; gettimeofday(&curTime, NULL); bigBit = curTime.tv_sec; littleBit = curTime.tv_usec; } double ElapsedTime::Duration(bool reset) { struct timeval curTime; 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 static_cast([[NSUserDefaults standardUserDefaults] integerForKey: @"NSFixedPitchFontSize"]); } //-------------------------------------------------------------------------------------------------- /** * Returns the time span in which two consecutive mouse clicks must occur to be considered as * double click. * * @return time span in milliseconds */ unsigned int Platform::DoubleClickTime() { float threshold = [[NSUserDefaults standardUserDefaults] floatForKey: @"com.apple.mouse.doubleClickThreshold"]; if (threshold == 0) threshold = 0.5; return static_cast(threshold * 1000.0); } //-------------------------------------------------------------------------------------------------- bool Platform::MouseButtonBounce() { return false; } //-------------------------------------------------------------------------------------------------- /** * Helper method for the backend to reach through to the scintilla 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 scintilla 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) { // Byte ranges found in Wikipedia articles with relevant search strings in each case unsigned char uch = static_cast(ch); switch (codePage) { case 932: // Shift_jis return ((uch >= 0x81) && (uch <= 0x9F)) || ((uch >= 0xE0) && (uch <= 0xFC)); // Lead bytes F0 to FC may be a Microsoft addition. case 936: // GBK return (uch >= 0x81) && (uch <= 0xFE); case 949: // Korean Wansung KS C-5601-1987 return (uch >= 0x81) && (uch <= 0xFE); case 950: // Big5 return (uch >= 0x81) && (uch <= 0xFE); case 1361: // Korean Johab KS C-5601-1992 return ((uch >= 0x84) && (uch <= 0xD3)) || ((uch >= 0xD8) && (uch <= 0xDE)) || ((uch >= 0xE0) && (uch <= 0xF9)); } return false; } //-------------------------------------------------------------------------------------------------- int Platform::DBCSCharLength(int /* codePage */, const char * /* s */) { // DBCS no longer uses this. return 1; } //-------------------------------------------------------------------------------------------------- int Platform::DBCSCharMaxLength() { 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", 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]; snprintf(buffer, sizeof(buffer), "Assertion [%s] failed at %s %d\r\n", c, file, line); 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 occurred. */ DynamicLibrary *DynamicLibrary::Load(const char * /* modulePath */) { // Not implemented. return NULL; } //--------------------------------------------------------------------------------------------------