mirror of
				https://github.com/notepad-plus-plus/notepad-plus-plus.git
				synced 2025-10-31 03:24:04 +01:00 
			
		
		
		
	git-svn-id: svn://svn.tuxfamily.org/svnroot/notepadplus/repository/trunk@791 f5eea248-9336-0410-98b8-ebc06183d4e3
		
			
				
	
	
		
			1850 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1850 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Scintilla source code edit control
 | |
| // PlatMacOSX.cxx - implementation of platform facilities on MacOS X/Carbon
 | |
| // Based on work by Evan Jones (c) 2002 <ejones@uwaterloo.ca>
 | |
| // Based on PlatGTK.cxx Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
 | |
| // The License.txt file describes the conditions under which this software may be distributed.
 | |
| 
 | |
| #include <cstring>
 | |
| #include <cstdio>
 | |
| #include <cstdlib>
 | |
| 
 | |
| #include <assert.h>
 | |
| 
 | |
| #include <sys/time.h>
 | |
| 
 | |
| #include <Carbon/Carbon.h>
 | |
| #include "QuartzTextLayout.h"
 | |
| #include "TCarbonEvent.h"
 | |
| 
 | |
| #include "Platform.h"
 | |
| #include "Scintilla.h"
 | |
| #include "PlatMacOSX.h"
 | |
| #include "XPM.h"
 | |
| 
 | |
| using namespace Scintilla;
 | |
| 
 | |
| #include "ScintillaWidget.h"
 | |
| 
 | |
| 
 | |
| extern sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam);
 | |
| 
 | |
| inline CGRect PRectangleToCGRect( PRectangle& rc ) {
 | |
|     return CGRectMake( rc.left, rc.top, rc.Width(), rc.Height() );
 | |
| }
 | |
| 
 | |
| inline PRectangle CGRectToPRectangle( const CGRect& rect ) {
 | |
|     PRectangle rc;
 | |
|     rc.left = (int)( rect.origin.x + 0.5 );
 | |
|     rc.top = (int)( rect.origin.y + 0.5 );
 | |
|     rc.right = (int)( rect.origin.x + rect.size.width + 0.5 );
 | |
|     rc.bottom = (int)( rect.origin.y + rect.size.height + 0.5 );
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| Scintilla::Point Scintilla::Point::FromLong(long lpoint) {
 | |
|   return Scintilla::Point(
 | |
|                Platform::LowShortFromLong(lpoint),
 | |
|                Platform::HighShortFromLong(lpoint));
 | |
| }
 | |
| 
 | |
| // The Palette is just ignored on Mac OS X. OS X runs "Millions" or "Thousands" of colours.
 | |
| Scintilla::Palette::Palette() {
 | |
| }
 | |
| 
 | |
| Scintilla::Palette::~Palette() {
 | |
| }
 | |
| 
 | |
| void Scintilla::Palette::Release() {
 | |
| }
 | |
| 
 | |
| // Do nothing if it "wants" a colour. Copy the colour from desired to allocated if it is "finding" a colour.
 | |
| void Scintilla::Palette::WantFind(ColourPair &cp, bool want) {
 | |
|     if (want) {
 | |
|     } else {
 | |
|         cp.allocated.Set(cp.desired.AsLong());
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Scintilla::Palette::Allocate(Window &/*w*/) {
 | |
|     // OS X always runs in thousands or millions of colours
 | |
| }
 | |
| 
 | |
| Font::Font() : fid(0) {}
 | |
| 
 | |
| Font::~Font() { Release(); }
 | |
| 
 | |
| 
 | |
| 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();
 | |
| 
 | |
|     fid = new QuartzTextStyle();
 | |
| 
 | |
|     // Find the font
 | |
|     QuartzFont font( faceName, strlen( faceName ) );
 | |
| 
 | |
|     // We set Font, Size, Bold, Italic
 | |
|     QuartzTextSize textSize( size );
 | |
|     QuartzTextBold isBold( bold );
 | |
|     QuartzTextItalic isItalic( italic );
 | |
| 
 | |
|     // Actually set the attributes
 | |
|     QuartzTextStyleAttribute* attributes[] = { &font, &textSize, &isBold, &isItalic };
 | |
|     reinterpret_cast<QuartzTextStyle*>( fid )->setAttributes( attributes, sizeof( attributes ) / sizeof( *attributes ) );
 | |
| 
 | |
|     //ATSStyleRenderingOptions rendering = kATSStyleNoAntiAliasing;
 | |
|     //reinterpret_cast<QuartzTextStyle*>( fid )->setAttribute( kATSUStyleRenderingOptionsTag, sizeof( rendering ), &rendering );
 | |
| 
 | |
|     // TODO: Why do I have to manually set this?
 | |
|     reinterpret_cast<QuartzTextStyle*>( fid )->setFontFeature( kLigaturesType, kCommonLigaturesOffSelector );
 | |
| }
 | |
| 
 | |
| void Font::Release() {
 | |
|     if (fid)
 | |
|             delete reinterpret_cast<QuartzTextStyle*>( fid );
 | |
| 
 | |
|     fid = 0;
 | |
| }
 | |
| 
 | |
| SurfaceImpl::SurfaceImpl() {
 | |
|     bitmapData = NULL; // Release will try and delete bitmapData if != NULL
 | |
|     gc = NULL;
 | |
|     textLayout = new QuartzTextLayout(NULL);
 | |
|     Release();
 | |
| }
 | |
| 
 | |
| SurfaceImpl::~SurfaceImpl() {
 | |
|     Release();
 | |
|     delete textLayout;
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::Release() {
 | |
|     textLayout->setContext (NULL);
 | |
|     if ( bitmapData != NULL )
 | |
|         {
 | |
|         delete[] bitmapData;
 | |
|         // We only "own" the graphics context if we are a bitmap context
 | |
|         if ( gc != NULL ) CGContextRelease( gc );
 | |
|         }
 | |
|     bitmapData = NULL;
 | |
|     gc = NULL;
 | |
| 
 | |
|     bitmapWidth = 0;
 | |
|     bitmapHeight = 0;
 | |
|     x = 0;
 | |
|     y = 0;
 | |
|     //inited = false;
 | |
| }
 | |
| 
 | |
| bool SurfaceImpl::Initialised() {
 | |
|     // We are initalised if the graphics context is not null
 | |
|   return gc != NULL;// || port != NULL;
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::Init(WindowID /*wid*/) {
 | |
|     // To be able to draw, the surface must get a CGContext handle.  We save the graphics port,
 | |
|     // then aquire/release the context on an as-need basis (see above).
 | |
|     // XXX Docs on QDBeginCGContext are light, a better way to do this would be good.
 | |
|     // AFAIK we should not hold onto a context retrieved this way, thus the need for
 | |
|     // aquire/release of the context.
 | |
| 
 | |
|     Release();
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::Init(SurfaceID sid, WindowID /*wid*/) {
 | |
|     Release();
 | |
|     gc = reinterpret_cast<CGContextRef>( sid );
 | |
|     CGContextSetLineWidth( gc, 1.0 );
 | |
|     textLayout->setContext (gc);
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::InitPixMap(int width, int height, Surface* /*surface_*/, WindowID /*wid*/) {
 | |
|     Release();
 | |
|     // Create a new bitmap context, along with the RAM for the bitmap itself
 | |
|     bitmapWidth = width;
 | |
|     bitmapHeight = height;
 | |
| 
 | |
|     const int bitmapBytesPerRow   = (width * BYTES_PER_PIXEL);
 | |
|     const int bitmapByteCount     = (bitmapBytesPerRow * height);
 | |
| 
 | |
|     // create an RGB color space
 | |
|     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 | |
|     if( colorSpace == NULL )
 | |
|         return;
 | |
| 
 | |
|     // create the bitmap
 | |
|     bitmapData = new uint8_t[ bitmapByteCount ];
 | |
|     if( bitmapData != NULL ) {
 | |
|         // create the context
 | |
|         gc = CGBitmapContextCreate( bitmapData,
 | |
|                                     width,
 | |
|                                     height,
 | |
|                                     BITS_PER_COMPONENT,
 | |
|                                     bitmapBytesPerRow,
 | |
|                                     colorSpace,
 | |
|                                     kCGImageAlphaPremultipliedLast);
 | |
| 
 | |
|         if( gc == NULL ) {
 | |
|             // the context couldn't be created for some reason,
 | |
|             // and we have no use for the bitmap without the context
 | |
|             delete[] bitmapData;
 | |
|             bitmapData = NULL;
 | |
|         }
 | |
|         textLayout->setContext (gc);
 | |
|     }
 | |
| 
 | |
|     // the context retains the color space, so we can release it
 | |
|     CGColorSpaceRelease( colorSpace );
 | |
| 
 | |
|     if ( gc != NULL && bitmapData != NULL )
 | |
|     {
 | |
|         // "Erase" to white
 | |
|         CGContextClearRect( gc, CGRectMake( 0, 0, width, height ) );
 | |
|         CGContextSetRGBFillColor( gc, 1.0, 1.0, 1.0, 1.0 );
 | |
|         CGContextFillRect( gc, CGRectMake( 0, 0, width, height ) );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::PenColour(ColourAllocated fore) {
 | |
|     if (gc) {
 | |
|         ColourDesired colour( fore.AsLong() );
 | |
| 
 | |
|         // Set the Stroke color to match
 | |
|         CGContextSetRGBStrokeColor( gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, colour.GetBlue() / 255.0, 1.0 );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::FillColour(const ColourAllocated& back) {
 | |
|     if (gc) {
 | |
|         ColourDesired colour( back.AsLong() );
 | |
| 
 | |
|         // Set the Fill color to match
 | |
|         CGContextSetRGBFillColor( gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, colour.GetBlue() / 255.0, 1.0 );
 | |
|     }
 | |
| }
 | |
| 
 | |
| CGImageRef SurfaceImpl::GetImage() {
 | |
|     // For now, assume that GetImage can only be called on PixMap surfaces
 | |
|     if ( bitmapData == NULL ) return NULL;
 | |
| 
 | |
|     CGContextFlush( gc );
 | |
| 
 | |
|     // create an RGB color space
 | |
|     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 | |
|     if( colorSpace == NULL )
 | |
|         return NULL;
 | |
| 
 | |
|     const int bitmapBytesPerRow   = ((int) bitmapWidth * BYTES_PER_PIXEL);
 | |
|     const int bitmapByteCount     = (bitmapBytesPerRow * (int) bitmapHeight);
 | |
| 
 | |
|     // Create a data provider
 | |
|     CGDataProviderRef dataProvider = CGDataProviderCreateWithData( NULL, bitmapData, bitmapByteCount, NULL );
 | |
|     CGImageRef image = NULL;
 | |
|     if ( dataProvider != NULL )
 | |
|     {
 | |
|         // create the CGImage
 | |
|         image = CGImageCreate( bitmapWidth,
 | |
|                             bitmapHeight,
 | |
|                             BITS_PER_COMPONENT,
 | |
|                             BITS_PER_PIXEL,
 | |
|                             bitmapBytesPerRow,
 | |
|                             colorSpace,
 | |
|                             kCGImageAlphaPremultipliedLast,
 | |
|                             dataProvider,
 | |
|                             NULL,
 | |
|                             0,
 | |
|                             kCGRenderingIntentDefault );
 | |
|     }
 | |
| 
 | |
|     // the image retains the color space, so we can release it
 | |
|     CGColorSpaceRelease( colorSpace );
 | |
|     colorSpace = NULL;
 | |
| 
 | |
|     // Done with the data provider
 | |
|     CGDataProviderRelease( dataProvider );
 | |
|     dataProvider = NULL;
 | |
| 
 | |
|     return image;
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::LogPixelsY() {
 | |
|     return 72;
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::DeviceHeightFont(int points) {
 | |
|     int logPix = LogPixelsY();
 | |
|     return (points * logPix + logPix / 2) / 72;
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::MoveTo(int x_, int y_) {
 | |
|     x = x_;
 | |
|     y = y_;
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::LineTo(int x_, int y_) {
 | |
|     CGContextBeginPath( gc );
 | |
|     // Because Quartz is based on floating point, lines are drawn with half their colour
 | |
|     // on each side of the line. Integer coordinates specify the INTERSECTION of the pixel
 | |
|     // divison lines. If you specify exact pixel values, you get a line that
 | |
|     // is twice as thick but half as intense. To get pixel aligned rendering,
 | |
|     // we render the "middle" of the pixels by adding 0.5 to the coordinates.
 | |
|     CGContextMoveToPoint( gc, x + 0.5, y + 0.5 );
 | |
|     CGContextAddLineToPoint( gc, x_ + 0.5, y_ + 0.5 );
 | |
|     CGContextStrokePath( gc );
 | |
|     x = x_;
 | |
|     y = y_;
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::Polygon(Scintilla::Point *pts, int npts, ColourAllocated fore,
 | |
|                       ColourAllocated back) {
 | |
|     // Allocate memory for the array of points
 | |
|     CGPoint *points = new CGPoint[ npts ];
 | |
| 
 | |
|     for (int i = 0;i < npts;i++) {
 | |
|         // Quartz floating point issues: plot the MIDDLE of the pixels
 | |
|         points[i].x = pts[i].x + 0.5;
 | |
|         points[i].y = pts[i].y + 0.5;
 | |
|     }
 | |
| 
 | |
|     CGContextBeginPath( gc );
 | |
| 
 | |
|     // Set colours
 | |
|     FillColour(back);
 | |
|     PenColour(fore);
 | |
| 
 | |
|     // Draw the polygon
 | |
|     CGContextAddLines( gc, points, npts );
 | |
|     // 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.
 | |
|         CGContextAddRect( gc, CGRectMake( rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1 ) );
 | |
|         CGContextDrawPath( gc, kCGPathFillStroke );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::FillRectangle(PRectangle rc, ColourAllocated back) {
 | |
|     if ( gc ) {
 | |
|         //CGContextBeginPath( gc );
 | |
|         FillColour(back);
 | |
| 
 | |
|         CGRect rect = PRectangleToCGRect( rc );
 | |
| 
 | |
|         CGContextFillRect( gc, rect );
 | |
|         //CGContextDrawPath( gc, kCGPathFill );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void drawImageRefCallback( CGImageRef pattern, CGContextRef gc )
 | |
| {
 | |
|     CGContextDrawImage( gc, CGRectMake( 0, 0, CGImageGetWidth( pattern ), CGImageGetHeight( pattern ) ), pattern );
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
 | |
|     SurfaceImpl& patternSurface = static_cast<SurfaceImpl &>(surfacePattern);
 | |
| 
 | |
|     // For now, assume that copy can only be called on PixMap surfaces
 | |
|     // Shows up black
 | |
|     CGImageRef image = patternSurface.GetImage();
 | |
|     if ( image == NULL )
 | |
|         {
 | |
|         FillRectangle(rc, ColourAllocated(0));
 | |
|         return;
 | |
|         }
 | |
| 
 | |
|     const CGPatternCallbacks drawImageCallbacks = { 0, reinterpret_cast<CGPatternDrawPatternCallback>( drawImageRefCallback ), NULL };
 | |
| 
 | |
|     CGPatternRef pattern = CGPatternCreate( image,
 | |
|                                          CGRectMake( 0, 0, patternSurface.bitmapWidth, patternSurface.bitmapHeight ),
 | |
|                                          CGAffineTransformIdentity,
 | |
|                                          patternSurface.bitmapWidth,
 | |
|                                          patternSurface.bitmapHeight,
 | |
|                                          kCGPatternTilingNoDistortion,
 | |
|                                          true,
 | |
|                                          &drawImageCallbacks
 | |
|                                          );
 | |
|     if( pattern != NULL ) {
 | |
| 
 | |
|         // Create a pattern color space
 | |
|         CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern( NULL );
 | |
|         if( colorSpace != NULL ) {
 | |
| 
 | |
|             CGContextSaveGState( gc );
 | |
|             CGContextSetFillColorSpace( gc, colorSpace );
 | |
| 
 | |
|             // Unlike the documentation, you MUST pass in a "components" parameter:
 | |
|             // For coloured patterns it is the alpha value.
 | |
|             const float alpha = 1.0;
 | |
|             CGContextSetFillPattern( gc, pattern, &alpha );
 | |
|             CGContextFillRect( gc, PRectangleToCGRect( rc ) );
 | |
|             CGContextRestoreGState( gc );
 | |
|             // Free the color space, the pattern and image
 | |
|             CGColorSpaceRelease( colorSpace );
 | |
|         } /* colorSpace != NULL */
 | |
|         colorSpace = NULL;
 | |
|         CGPatternRelease( pattern );
 | |
|         pattern = NULL;
 | |
|         CGImageRelease( image );
 | |
|         image = NULL;
 | |
|     } /* pattern != NULL */
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
 | |
|     // 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;
 | |
|     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
 | |
|     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<SurfaceImpl &>(surfaceSource);
 | |
|     CGImageRef image = source.GetImage();
 | |
| 
 | |
|     CGRect src = PRectangleToCGRect(srcRect);
 | |
|     CGRect dst = PRectangleToCGRect(dstRect);
 | |
| 
 | |
|     /* source from QuickDrawToQuartz2D.pdf on developer.apple.com */
 | |
|     float w = (float) CGImageGetWidth(image);
 | |
|     float h = (float) CGImageGetHeight(image);
 | |
|     CGRect drawRect = CGRectMake (0, 0, w, h);
 | |
|     if (!CGRectEqualToRect (src, dst))
 | |
|     {
 | |
|         float sx = CGRectGetWidth(dst) / CGRectGetWidth(src);
 | |
|         float sy = CGRectGetHeight(dst) / CGRectGetHeight(src);
 | |
|         float dx = CGRectGetMinX(dst) - (CGRectGetMinX(src) * sx);
 | |
|         float dy = CGRectGetMinY(dst) - (CGRectGetMinY(src) * sy);
 | |
|         drawRect = CGRectMake (dx, dy, w*sx, h*sy);
 | |
|     }
 | |
|     CGContextSaveGState (gc);
 | |
|     CGContextClipToRect (gc, dst);
 | |
|     CGContextDrawImage (gc, drawRect, image);
 | |
|     CGContextRestoreGState (gc);
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSource) {
 | |
|     // Maybe we have to make the Surface two contexts:
 | |
|     // a bitmap context which we do all the drawing on, and then a "real" context
 | |
|     // which we copy the output to when we call "Synchronize". Ugh! Gross and slow!
 | |
| 
 | |
|     // For now, assume that copy can only be called on PixMap surfaces
 | |
|     SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
 | |
| 
 | |
|     // Get the CGImageRef
 | |
|     CGImageRef image = source.GetImage();
 | |
|     // If we could not get an image reference, fill the rectangle black
 | |
|     if ( image == NULL )
 | |
|         {
 | |
|         FillRectangle( rc, ColourAllocated( 0 ) );
 | |
|         return;
 | |
|         }
 | |
| 
 | |
|     // Now draw the image on the surface
 | |
| 
 | |
|     // Some fancy clipping work is required here: draw only inside of rc
 | |
|     CGContextSaveGState( gc );
 | |
|     CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
 | |
| 
 | |
|     //Platform::DebugPrintf(stderr, "Copy: CGContextDrawImage: (%d, %d) - (%d X %d)\n", rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight );
 | |
|     CGContextDrawImage( gc, CGRectMake( rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight ), image );
 | |
| 
 | |
|     // Undo the clipping fun
 | |
|     CGContextRestoreGState( gc );
 | |
| 
 | |
|     // Done with the image
 | |
|     CGImageRelease( image );
 | |
|     image = NULL;
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len,
 | |
|                                  ColourAllocated fore, ColourAllocated back) {
 | |
| 
 | |
|     FillRectangle(rc, back);
 | |
|     DrawTextTransparent( rc, font_, ybase, s, len, fore );
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len,
 | |
|                                   ColourAllocated fore, ColourAllocated back) {
 | |
| 
 | |
|     CGContextSaveGState( gc );
 | |
|     CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
 | |
|     DrawTextNoClip( rc, font_, ybase, s, len, fore, back );
 | |
|     CGContextRestoreGState( gc );
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore) {
 | |
|     textLayout->setText (reinterpret_cast<const UInt8*>(s), len, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
 | |
| 
 | |
|     // The Quartz RGB fill color influences the ATSUI color
 | |
|     FillColour(fore);
 | |
|     // Draw the text, with the Y axis flipped
 | |
|     textLayout->draw( rc.left, ybase, true );
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::MeasureWidths(Font &font_, const char *s, int len, int *positions) {
 | |
|     // sample at http://developer.apple.com/samplecode/ATSUICurveAccessDemo/listing1.html
 | |
|     // sample includes use of ATSUGetGlyphInfo which would be better for older
 | |
|     // OSX systems.  We should expand to using that on older systems as well.
 | |
|     for (int i = 0; i < len; i++)
 | |
|             positions [i] = 0;
 | |
| 
 | |
|     // We need the right X coords, so we have to append a char to get the left coord of thast extra char
 | |
|     char* buf = (char*) malloc (len+1);
 | |
|     if (!buf)
 | |
|             return;
 | |
| 
 | |
|     memcpy (buf, s, len);
 | |
|     buf [len] = '.';
 | |
| 
 | |
|     textLayout->setText (reinterpret_cast<const UInt8*>(buf), len+1, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
 | |
|     ATSUGlyphInfoArray* theGlyphInfoArrayPtr;
 | |
|     ByteCount theArraySize;
 | |
| 
 | |
|     // Get the GlyphInfoArray
 | |
|     ATSUTextLayout layout = textLayout->getLayout();
 | |
|     if ( noErr == ATSUGetGlyphInfo (layout, 0, textLayout->getLength(), &theArraySize, NULL))
 | |
|     {
 | |
|         theGlyphInfoArrayPtr = (ATSUGlyphInfoArray *) malloc (theArraySize + sizeof(ItemCount) + sizeof(ATSUTextLayout));
 | |
|         if (theGlyphInfoArrayPtr)
 | |
|         {
 | |
|             if (noErr == ATSUGetGlyphInfo (layout, 0, textLayout->getLength(), &theArraySize, theGlyphInfoArrayPtr))
 | |
|             {
 | |
|                 // do not count the first item, which is at the beginning of the line
 | |
|                 for ( UniCharCount unicodePosition = 1, i = 0; i < len && unicodePosition < theGlyphInfoArrayPtr->numGlyphs; unicodePosition ++ )
 | |
|                 {
 | |
|                     // The ideal position is the x coordinate of the glyph, relative to the beginning of the line
 | |
|                     int position = (int)( theGlyphInfoArrayPtr->glyphs[unicodePosition].idealX + 0.5 );    // These older APIs return float values
 | |
|                     unsigned char uch = s[i];
 | |
|                     positions[i++] = position;
 | |
| 
 | |
|                     // If we are using unicode (UTF8), map the Unicode position back to the UTF8 characters,
 | |
|                     // as 1 unicode character can map to multiple UTF8 characters.
 | |
|                     // See: http://www.tbray.org/ongoing/When/200x/2003/04/26/UTF
 | |
|                     // Or: http://www.cl.cam.ac.uk/~mgk25/unicode.html
 | |
|                     if ( unicodeMode )
 | |
|                     {
 | |
|                         unsigned char mask = 0xc0;
 | |
|                         int count = 1;
 | |
|                         // Add one additonal byte for each extra high order one in the byte
 | |
|                         while ( uch >= mask && count < 8 )
 | |
|                         {
 | |
|                             positions[i++] = position;
 | |
|                             count ++;
 | |
|                             mask = mask >> 1 | 0x80; // add an additional one in the highest order position
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Free the GlyphInfoArray
 | |
|             free (theGlyphInfoArrayPtr);
 | |
|         }
 | |
|     }
 | |
|     free (buf);
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::WidthText(Font &font_, const char *s, int len) {
 | |
|     if (font_.GetID())
 | |
|     {
 | |
|         textLayout->setText (reinterpret_cast<const UInt8*>(s), len, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
 | |
| 
 | |
|         // TODO: Maybe I should add some sort of text measurement features to QuartzTextLayout?
 | |
|         unsigned long actualNumberOfBounds = 0;
 | |
|         ATSTrapezoid glyphBounds;
 | |
| 
 | |
|         // We get a single bound, since the text should only require one. If it requires more, there is an issue
 | |
|         if ( ATSUGetGlyphBounds( textLayout->getLayout(), 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &glyphBounds, &actualNumberOfBounds ) != noErr || actualNumberOfBounds != 1 )
 | |
|         {
 | |
|             Platform::DebugDisplay( "ATSUGetGlyphBounds failed in WidthText" );
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         //Platform::DebugPrintf( "WidthText: \"%*s\" = %ld\n", len, s, Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x ) );
 | |
|         return Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x );
 | |
|     }
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::WidthChar(Font &font_, char ch) {
 | |
|     char str[2] = { ch, '\0' };
 | |
|     if (font_.GetID())
 | |
|     {
 | |
|         textLayout->setText (reinterpret_cast<const UInt8*>(str), 1, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
 | |
| 
 | |
|         // TODO: Maybe I should add some sort of text measurement features to QuartzTextLayout?
 | |
|         unsigned long actualNumberOfBounds = 0;
 | |
|         ATSTrapezoid glyphBounds;
 | |
| 
 | |
|         // We get a single bound, since the text should only require one. If it requires more, there is an issue
 | |
|         if ( ATSUGetGlyphBounds( textLayout->getLayout(), 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &glyphBounds, &actualNumberOfBounds ) != noErr || actualNumberOfBounds != 1 )
 | |
|         {
 | |
|             Platform::DebugDisplay( "ATSUGetGlyphBounds failed in WidthChar" );
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         return Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x );
 | |
|     }
 | |
|     else
 | |
|         return 1;
 | |
| }
 | |
| 
 | |
| // Three possible strategies for determining ascent and descent of font:
 | |
| // 1) Call ATSUGetGlyphBounds with string containing all letters, numbers and punctuation.
 | |
| // 2) Use the ascent and descent fields of the font.
 | |
| // 3) Call ATSUGetGlyphBounds with string as 1 but also including accented capitals.
 | |
| // Smallest values given by 1 and largest by 3 with 2 in between.
 | |
| // Techniques 1 and 2 sometimes chop off extreme portions of ascenders and
 | |
| // descenders but are mostly OK except for accented characters which are
 | |
| // rarely used in code.
 | |
| 
 | |
| // This string contains a good range of characters to test for size.
 | |
| const char sizeString[] = "`~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890"
 | |
|                           "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 | |
| 
 | |
| int SurfaceImpl::Ascent(Font &font_) {
 | |
|     if (!font_.GetID())
 | |
|         return 1;
 | |
| 
 | |
|     ATSUTextMeasurement ascent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSUAscentTag );
 | |
|     return Fix2Long( ascent );
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::Descent(Font &font_) {
 | |
|     if (!font_.GetID())
 | |
|         return 1;
 | |
| 
 | |
|     ATSUTextMeasurement descent = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSUDescentTag );
 | |
|     return Fix2Long( descent );
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::InternalLeading(Font &) {
 | |
|     // TODO: How do we get EM_Size?
 | |
|     // internal leading = ascent - descent - EM_size
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::ExternalLeading(Font &font_) {
 | |
|     if (!font_.GetID())
 | |
|         return 1;
 | |
| 
 | |
|     ATSUTextMeasurement lineGap = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getAttribute<ATSUTextMeasurement>( kATSULeadingTag );
 | |
|     return Fix2Long( lineGap );
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::Height(Font &font_) {
 | |
|     return Ascent(font_) + Descent(font_);
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::AverageCharWidth(Font &font_) {
 | |
| 
 | |
|     if (!font_.GetID())
 | |
|         return 1;
 | |
| 
 | |
|     const int sizeStringLength = (sizeof( sizeString ) / sizeof( sizeString[0] ) - 1);
 | |
|     int width = WidthText( font_, sizeString, sizeStringLength  );
 | |
| 
 | |
|     return (int) ((width / (float) sizeStringLength) + 0.5);
 | |
| 
 | |
|     /*
 | |
|     ATSUStyle textStyle = reinterpret_cast<QuartzTextStyle*>( font_.GetID() )->getATSUStyle();
 | |
|     ATSUFontID fontID;
 | |
| 
 | |
|     ByteCount actualSize = 0;
 | |
|     if ( ATSUGetAttribute( textStyle, kATSUFontTag, sizeof( fontID ), &fontID, &actualSize ) != noErr )
 | |
|         {
 | |
| Platform::DebugDisplay( "ATSUGetAttribute failed" );
 | |
|         return 1;
 | |
|         }
 | |
| 
 | |
|     ATSFontMetrics metrics;
 | |
|     memset( &metrics, 0, sizeof( metrics ) );
 | |
|     if ( ATSFontGetHorizontalMetrics( fontID, kATSOptionFlagsDefault, &metrics ) != noErr )
 | |
|     {
 | |
|         Platform::DebugDisplay( "ATSFontGetHorizontalMetrics failed in AverageCharWidth" );
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     printf( "%f %f %f\n", metrics.avgAdvanceWidth * 32, metrics.ascent * 32, metrics.descent * 32 );
 | |
| 
 | |
|     return (int) (metrics.avgAdvanceWidth + 0.5);*/
 | |
| }
 | |
| 
 | |
| int SurfaceImpl::SetPalette(Scintilla::Palette *, bool) {
 | |
|     // Mac OS X is always true colour (I think) so this doesn't matter
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::SetClip(PRectangle rc) {
 | |
|     CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::FlushCachedState() {
 | |
|     CGContextSynchronize( gc );
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::SetUnicodeMode(bool unicodeMode_) {
 | |
|     unicodeMode = unicodeMode_;
 | |
| }
 | |
| 
 | |
| void SurfaceImpl::SetDBCSMode(int codePage) {
 | |
|     // TODO: Implement this for code pages != UTF-8
 | |
| }
 | |
| 
 | |
| Surface *Surface::Allocate() {
 | |
|     return new SurfaceImpl(  );
 | |
| }
 | |
| 
 | |
| Window::~Window() {
 | |
| }
 | |
| 
 | |
| void Window::Destroy() {
 | |
|     if (windowRef) {
 | |
|         DisposeWindow(reinterpret_cast<WindowRef>( windowRef ));
 | |
|     }
 | |
|     wid = 0;
 | |
| }
 | |
| 
 | |
| bool Window::HasFocus() {
 | |
|     // TODO: Test this
 | |
|     return HIViewSubtreeContainsFocus( reinterpret_cast<HIViewRef>( wid ) );
 | |
| }
 | |
| 
 | |
| PRectangle Window::GetPosition() {
 | |
|     // Before any size allocated pretend its 1000 wide so not scrolled
 | |
|     PRectangle rc(0, 0, 1000, 1000);
 | |
| 
 | |
|     // The frame rectangle gives the position of this view inside the parent view
 | |
|     if (wid) {
 | |
|         HIRect controlFrame;
 | |
|         HIViewGetFrame( reinterpret_cast<HIViewRef>( wid ), &controlFrame );
 | |
|         rc = CGRectToPRectangle( controlFrame );
 | |
|     }
 | |
| 
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| void Window::SetPosition(PRectangle rc) {
 | |
|     // Moves this view inside the parent view
 | |
|     if ( wid )
 | |
|     {
 | |
|         // Set the frame on the view, the function handles the rest
 | |
|         CGRect r = PRectangleToCGRect( rc );
 | |
|         HIViewSetFrame( reinterpret_cast<HIViewRef>( wid ), &r );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Window::SetPositionRelative(PRectangle rc, Window window) {
 | |
|     // used to actually move child windows (ie. listbox/calltip) so we have to move
 | |
|     // the window, not the hiview
 | |
|     if (windowRef) {
 | |
|         // we go through some contortions here to get an accurate location for our
 | |
|         // child windows.  This is necessary due to the multiple ways an embedding
 | |
|         // app may be setup.  See SciTest/main.c (GOOD && BAD) for test case.
 | |
|         WindowRef relativeWindow = GetControlOwner(reinterpret_cast<HIViewRef>( window.GetID() ));
 | |
|         WindowRef thisWindow = reinterpret_cast<WindowRef>( windowRef );
 | |
| 
 | |
|         Rect portBounds;
 | |
|         ::GetWindowBounds(relativeWindow, kWindowStructureRgn, &portBounds);
 | |
|         //fprintf(stderr, "portBounds %d %d %d %d\n", portBounds.left, portBounds.top, portBounds.right, portBounds.bottom);
 | |
|         PRectangle hbounds = window.GetPosition();
 | |
|         //fprintf(stderr, "hbounds %d %d %d %d\n", hbounds.left, hbounds.top, hbounds.right, hbounds.bottom);
 | |
|         HIViewRef parent = HIViewGetSuperview(reinterpret_cast<HIViewRef>( window.GetID() ));
 | |
|         Rect pbounds;
 | |
|         GetControlBounds(parent, &pbounds);
 | |
|         //fprintf(stderr, "pbounds %d %d %d %d\n", pbounds.left, pbounds.top, pbounds.right, pbounds.bottom);
 | |
| 
 | |
|         PRectangle bounds;
 | |
|         bounds.top = portBounds.top + pbounds.top + hbounds.top + rc.top;
 | |
|         bounds.bottom = bounds.top + rc.Height();
 | |
|         bounds.left = portBounds.left + pbounds.left + hbounds.left + rc.left;
 | |
|         bounds.right = bounds.left + rc.Width();
 | |
|         //fprintf(stderr, "bounds %d %d %d %d\n", bounds.left, bounds.top, bounds.right, bounds.bottom);
 | |
| 
 | |
|         MoveWindow(thisWindow, bounds.left, bounds.top, false);
 | |
|         SizeWindow(thisWindow, bounds.Width(), bounds.Height(), true);
 | |
| 
 | |
|         SetPosition(PRectangle(0,0,rc.Width(),rc.Height()));
 | |
|     } else {
 | |
|         SetPosition(rc);
 | |
|     }
 | |
| }
 | |
| 
 | |
| PRectangle Window::GetClientPosition() {
 | |
|     // This means, in MacOS X terms, get the "frame bounds". Call GetPosition, just like on Win32.
 | |
|     return GetPosition();
 | |
| }
 | |
| 
 | |
| void Window::Show(bool show) {
 | |
|     if ( wid ) {
 | |
|         HIViewSetVisible( reinterpret_cast<HIViewRef>( wid ), show );
 | |
|     }
 | |
|     // this is necessary for calltip/listbox
 | |
|     if (windowRef) {
 | |
|         WindowRef thisWindow = reinterpret_cast<WindowRef>( windowRef );
 | |
|         if (show) {
 | |
|             ShowWindow( thisWindow );
 | |
|             DrawControls( thisWindow );
 | |
|         } else
 | |
|             HideWindow( thisWindow );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Window::InvalidateAll() {
 | |
|     if ( wid ) {
 | |
|         HIViewSetNeedsDisplay( reinterpret_cast<HIViewRef>( wid ), true );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Window::InvalidateRectangle(PRectangle rc) {
 | |
|     if (wid) {
 | |
|         // Create a rectangular region
 | |
|         RgnHandle region = NewRgn();
 | |
|         SetRectRgn( region, rc.left, rc.top, rc.right, rc.bottom );
 | |
| 
 | |
|         // Make that region invalid
 | |
|         HIViewSetNeedsDisplayInRegion( reinterpret_cast<HIViewRef>( wid ), region, true );
 | |
|         DisposeRgn( region );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Window::SetFont(Font &) {
 | |
|     // TODO: Do I need to implement this? MSDN: specifies the font that a control is to use when drawing text.
 | |
| }
 | |
| 
 | |
| void Window::SetCursor(Cursor curs) {
 | |
|     if (wid) {
 | |
|         // TODO: This isn't really implemented correctly. I should be using
 | |
|         // mouse tracking rectangles to only set the mouse cursor when it is over the control
 | |
|         ThemeCursor cursor;
 | |
| 
 | |
|         switch ( curs ) {
 | |
|             case cursorText:
 | |
|                 cursor = kThemeIBeamCursor;
 | |
|                 break;
 | |
|             case cursorArrow:
 | |
|                 cursor = kThemeArrowCursor;
 | |
|                 break;
 | |
|             case cursorWait:
 | |
|                 cursor = kThemeWatchCursor;
 | |
|                 break;
 | |
|             case cursorHoriz:
 | |
|                 cursor = kThemeResizeLeftRightCursor;
 | |
|                 break;
 | |
|             case cursorVert:
 | |
|                 cursor = kThemeResizeUpDownCursor;
 | |
|                 break;
 | |
|             case cursorReverseArrow:
 | |
|             case cursorUp:
 | |
|             default:
 | |
|                 cursor = kThemeArrowCursor;
 | |
|                 break;
 | |
|         }
 | |
| 
 | |
|         SetThemeCursor( cursor );
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Window::SetTitle(const char *s) {
 | |
|     WindowRef window = GetControlOwner(reinterpret_cast<HIViewRef>( wid ));
 | |
|     CFStringRef title = CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingMacRoman);
 | |
|     SetWindowTitleWithCFString(window, title);
 | |
|     CFRelease(title);
 | |
| }
 | |
| 
 | |
| PRectangle Window::GetMonitorRect(Point) {
 | |
| 	return PRectangle();
 | |
| }
 | |
| 
 | |
| ListBox::ListBox() {}
 | |
| 
 | |
| ListBox::~ListBox() {}
 | |
| 
 | |
| static const OSType scintillaListBoxType = 'sclb';
 | |
| 
 | |
| enum {
 | |
|     kItemsPerContainer = 1,
 | |
|     kIconColumn = 'icon',
 | |
|     kTextColumn = 'text'
 | |
| };
 | |
| static SInt32 kScrollBarWidth = 0;
 | |
| 
 | |
| class LineData {
 | |
|     int *types;
 | |
|         CFStringRef *strings;
 | |
|     int len;
 | |
|     int maximum;
 | |
| public:
 | |
|     LineData() :types(0), strings(0), len(0), maximum(0) {}
 | |
|     ~LineData() {
 | |
|         Clear();
 | |
|     }
 | |
|     void Clear() {
 | |
|         delete []types;
 | |
|         types = 0;
 | |
|         for (int i=0; i<maximum; i++) {
 | |
|           if (strings[i]) CFRelease(strings[i]);
 | |
|         }
 | |
|         delete []strings;
 | |
|         strings = 0;
 | |
|         len = 0;
 | |
|         maximum = 0;
 | |
|     }
 | |
|     void Add(int index, int type, CFStringRef str ) {
 | |
|         if (index >= maximum) {
 | |
|             if (index >= len) {
 | |
|                 int lenNew = (index+1) * 2;
 | |
|                 int *typesNew = new int[lenNew];
 | |
|                 CFStringRef *stringsNew = new CFStringRef[lenNew];
 | |
|                 for (int i=0; i<maximum; i++) {
 | |
|                     typesNew[i] = types[i];
 | |
|                     stringsNew[i] = strings[i];
 | |
|                 }
 | |
|                 delete []types;
 | |
|                 delete []strings;
 | |
|                 types = typesNew;
 | |
|                 strings = stringsNew;
 | |
|                 len = lenNew;
 | |
|             }
 | |
|             while (maximum < index) {
 | |
|                 types[maximum] = 0;
 | |
|                 strings[maximum] = 0;
 | |
|                 maximum++;
 | |
|             }
 | |
|         }
 | |
|         types[index] = type;
 | |
|         strings[index] = str;
 | |
|         if (index == maximum) {
 | |
|             maximum++;
 | |
|         }
 | |
|     }
 | |
|     int GetType(int index) {
 | |
|         if (index < maximum) {
 | |
|             return types[index];
 | |
|         } else {
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
|     CFStringRef GetString(int index) {
 | |
|         if (index < maximum) {
 | |
|             return strings[index];
 | |
|         } else {
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
| };
 | |
| 
 | |
| class ListBoxImpl : public ListBox {
 | |
| private:
 | |
|     ControlRef lb;
 | |
|     XPMSet xset;
 | |
|     int lineHeight;
 | |
|     bool unicodeMode;
 | |
|     int desiredVisibleRows;
 | |
|     unsigned int maxItemWidth;
 | |
|     unsigned int aveCharWidth;
 | |
|     Font font;
 | |
|     int maxWidth;
 | |
| 
 | |
|     void InstallDataBrowserCustomCallbacks();
 | |
|     void ConfigureDataBrowser();
 | |
| 
 | |
|     static pascal OSStatus  WindowEventHandler(EventHandlerCallRef  inCallRef,
 | |
|                                                EventRef inEvent,
 | |
|                                                void *inUserData );
 | |
|     EventHandlerRef eventHandler;
 | |
| 
 | |
| protected:
 | |
|     WindowRef windowRef;
 | |
| 
 | |
| public:
 | |
|     LineData ld;
 | |
|     CallBackAction doubleClickAction;
 | |
|     void *doubleClickActionData;
 | |
| 
 | |
|     ListBoxImpl() : lb(NULL), lineHeight(10), unicodeMode(false),
 | |
|             desiredVisibleRows(5), maxItemWidth(0), aveCharWidth(8),
 | |
|             doubleClickAction(NULL), doubleClickActionData(NULL)
 | |
|     {
 | |
|       if (kScrollBarWidth == 0)
 | |
|         GetThemeMetric(kThemeMetricScrollBarWidth, &kScrollBarWidth);
 | |
|     }
 | |
| 
 | |
|     ~ListBoxImpl() {};
 | |
|     void SetFont(Font &font);
 | |
|     void Create(Window &parent, int ctrlID, Scintilla::Point pt, int lineHeight_, bool unicodeMode_);
 | |
|     void SetAverageCharWidth(int width);
 | |
|     void SetVisibleRows(int rows);
 | |
|     int GetVisibleRows() const;
 | |
|     PRectangle GetDesiredRect();
 | |
|     int CaretFromEdge();
 | |
|     void Clear();
 | |
|     void Append(char *s, int type = -1);
 | |
|     int Length();
 | |
|     void Select(int n);
 | |
|     int GetSelection();
 | |
|     int Find(const char *prefix);
 | |
|     void GetValue(int n, char *value, int len);
 | |
|     void RegisterImage(int type, const char *xpm_data);
 | |
|     void ClearRegisteredImages();
 | |
|     void SetDoubleClickAction(CallBackAction action, void *data) {
 | |
|         doubleClickAction = action;
 | |
|         doubleClickActionData = data;
 | |
|     }
 | |
| 
 | |
|     int IconWidth();
 | |
|     void ShowHideScrollbar();
 | |
| #ifdef DB_TABLE_ROW_HEIGHT
 | |
|     void SetRowHeight(DataBrowserItemID itemID);
 | |
| #endif
 | |
| 
 | |
|     void DrawRow(DataBrowserItemID item,
 | |
|                  DataBrowserPropertyID property,
 | |
|                  DataBrowserItemState itemState,
 | |
|                  const Rect *theRect);
 | |
| 
 | |
|     void SetList(const char* list, char separator, char typesep);
 | |
| };
 | |
| 
 | |
| ListBox *ListBox::Allocate() {
 | |
|     ListBoxImpl *lb = new ListBoxImpl();
 | |
|     return lb;
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::Create(Window &/*parent*/, int /*ctrlID*/, Scintilla::Point /*pt*/,
 | |
|                          int lineHeight_, bool unicodeMode_) {
 | |
|     lineHeight = lineHeight_;
 | |
|     unicodeMode = unicodeMode_;
 | |
|     maxWidth = 2000;
 | |
| 
 | |
|     WindowClass windowClass = kHelpWindowClass;
 | |
|     WindowAttributes attributes = kWindowNoAttributes;
 | |
|     Rect contentBounds;
 | |
|     WindowRef outWindow;
 | |
| 
 | |
|     contentBounds.top = contentBounds.left = 0;
 | |
|     contentBounds.right = 100;
 | |
|     contentBounds.bottom = lineHeight * desiredVisibleRows;
 | |
| 
 | |
|     CreateNewWindow(windowClass, attributes, &contentBounds, &outWindow);
 | |
| 
 | |
|     InstallStandardEventHandler(GetWindowEventTarget(outWindow));
 | |
| 
 | |
|     ControlRef root;
 | |
|     CreateRootControl(outWindow, &root);
 | |
| 
 | |
|     CreateDataBrowserControl(outWindow, &contentBounds, kDataBrowserListView, &lb);
 | |
| 
 | |
| #ifdef DB_TABLE_ROW_HEIGHT
 | |
|     // XXX does not seem to have any effect
 | |
|     SetDataBrowserTableViewRowHeight(lb, lineHeight);
 | |
| #endif
 | |
| 
 | |
|     // get rid of the frame, forces databrowser to the full size
 | |
|     // of the window
 | |
|     Boolean frameAndFocus = false;
 | |
|     SetControlData(lb, kControlNoPart, kControlDataBrowserIncludesFrameAndFocusTag,
 | |
|            sizeof(frameAndFocus), &frameAndFocus);
 | |
| 
 | |
|     ListBoxImpl* lbThis = this;
 | |
|     SetControlProperty( lb, scintillaListBoxType, 0, sizeof( this ), &lbThis );
 | |
| 
 | |
|     ConfigureDataBrowser();
 | |
|     InstallDataBrowserCustomCallbacks();
 | |
| 
 | |
|     // install event handlers
 | |
|     static const EventTypeSpec kWindowEvents[] =
 | |
|         {
 | |
|           { kEventClassMouse, kEventMouseDown },
 | |
|           { kEventClassMouse, kEventMouseMoved },
 | |
|         };
 | |
| 
 | |
|     eventHandler = NULL;
 | |
|     InstallWindowEventHandler( outWindow, WindowEventHandler,
 | |
|                    GetEventTypeCount( kWindowEvents ),
 | |
|                    kWindowEvents, this, &eventHandler );
 | |
| 
 | |
|     wid = lb;
 | |
|     SetControlVisibility(lb, true, true);
 | |
|     SetControl(lb);
 | |
|     SetWindow(outWindow);
 | |
| }
 | |
| 
 | |
| pascal OSStatus ListBoxImpl::WindowEventHandler(
 | |
|     EventHandlerCallRef inCallRef,
 | |
|     EventRef            inEvent,
 | |
|     void*               inUserData )
 | |
| {
 | |
| 
 | |
|     switch (GetEventClass(inEvent)) {
 | |
|     case kEventClassMouse:
 | |
|       switch (GetEventKind(inEvent))
 | |
|         {
 | |
|         case kEventMouseMoved:
 | |
|           {
 | |
|             SetThemeCursor( kThemeArrowCursor );
 | |
|             break;
 | |
|           }
 | |
|         case kEventMouseDown:
 | |
|           {
 | |
|             // we cannot handle the double click from the databrowser notify callback as
 | |
|             // calling doubleClickAction causes the listbox to be destroyed.  It is
 | |
|             // safe to do it from this event handler since the destroy event will be queued
 | |
|             // until we're done here.
 | |
|             TCarbonEvent        event( inEvent );
 | |
|             EventMouseButton inMouseButton;
 | |
|             event.GetParameter<EventMouseButton>( kEventParamMouseButton, typeMouseButton, &inMouseButton );
 | |
| 
 | |
|             UInt32 inClickCount;
 | |
|             event.GetParameter( kEventParamClickCount, &inClickCount );
 | |
|             if (inMouseButton == kEventMouseButtonPrimary && inClickCount == 2) {
 | |
|               // handle our single mouse click now
 | |
|               ListBoxImpl* listbox = reinterpret_cast<ListBoxImpl*>( inUserData );
 | |
|               const WindowRef window = GetControlOwner(listbox->lb);
 | |
|               const HIViewRef rootView = HIViewGetRoot( window );
 | |
|               HIViewRef targetView = NULL;
 | |
|               HIViewGetViewForMouseEvent( rootView, inEvent, &targetView );
 | |
|               if ( targetView == listbox->lb )
 | |
|                 {
 | |
|                   if (listbox->doubleClickAction != NULL) {
 | |
|                 listbox->doubleClickAction(listbox->doubleClickActionData);
 | |
|                   }
 | |
|                 }
 | |
|             }
 | |
|           }
 | |
|       }
 | |
|     }
 | |
|     return eventNotHandledErr;
 | |
| }
 | |
| 
 | |
| #ifdef DB_TABLE_ROW_HEIGHT
 | |
| void ListBoxImpl::SetRowHeight(DataBrowserItemID itemID)
 | |
| {
 | |
|     // XXX does not seem to have any effect
 | |
|     SetDataBrowserTableViewItemRowHeight(lb, itemID, lineHeight);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void ListBoxImpl::DrawRow(DataBrowserItemID item,
 | |
|          DataBrowserPropertyID property,
 | |
|          DataBrowserItemState itemState,
 | |
|          const Rect *theRect)
 | |
| {
 | |
|     Rect row = *theRect;
 | |
|     row.left = 0;
 | |
| 
 | |
|     ColourPair fore;
 | |
| 
 | |
|     if (itemState == kDataBrowserItemIsSelected) {
 | |
|       long        systemVersion;
 | |
|       Gestalt( gestaltSystemVersion, &systemVersion );
 | |
|       //  Panther DB starts using kThemeBrushSecondaryHighlightColor for inactive browser hilighting
 | |
|       if ( (systemVersion >= 0x00001030) && (IsControlActive( lb ) == false) )
 | |
|           SetThemePen( kThemeBrushSecondaryHighlightColor, 32, true );
 | |
|       else
 | |
|         //SetThemePen( kThemeBrushPrimaryHighlightColor, 32, true );
 | |
|         SetThemePen( kThemeBrushAlternatePrimaryHighlightColor, 32, true );
 | |
| 
 | |
|       PaintRect(&row);
 | |
|       fore = ColourDesired(0xff,0xff,0xff);
 | |
|     }
 | |
| 
 | |
|     int widthPix = xset.GetWidth() + 2;
 | |
|     int pixId = ld.GetType(item - 1);
 | |
|     XPM *pxpm = xset.Get(pixId);
 | |
| 
 | |
|     char s[255];
 | |
|     GetValue(item - 1, s, 255);
 | |
| 
 | |
|     Surface *surfaceItem = Surface::Allocate();
 | |
|     if (surfaceItem) {
 | |
|         CGContextRef    cgContext;
 | |
|         GrafPtr        port;
 | |
|         Rect bounds;
 | |
| 
 | |
|         GetControlBounds(lb, &bounds);
 | |
|         GetPort( &port );
 | |
|         QDBeginCGContext( port, &cgContext );
 | |
| 
 | |
|         CGContextSaveGState( cgContext );
 | |
|         CGContextTranslateCTM(cgContext, 0, bounds.bottom - bounds.top);
 | |
|         CGContextScaleCTM(cgContext, 1.0, -1.0);
 | |
| 
 | |
|         surfaceItem->Init(cgContext, NULL);
 | |
| 
 | |
|         int left = row.left;
 | |
|         if (pxpm) {
 | |
|             PRectangle rc(left + 1, row.top,
 | |
|                     left + 1 + widthPix, row.bottom);
 | |
|             pxpm->Draw(surfaceItem, rc);
 | |
|         }
 | |
| 
 | |
|         // draw the text
 | |
|         PRectangle trc(left + 2 + widthPix, row.top, row.right, row.bottom);
 | |
|         int ascent = surfaceItem->Ascent(font) - surfaceItem->InternalLeading(font);
 | |
|         int ytext = trc.top + ascent + 1;
 | |
|         trc.bottom = ytext + surfaceItem->Descent(font) + 1;
 | |
|         surfaceItem->DrawTextTransparent( trc, font, ytext, s, strlen(s), fore.allocated );
 | |
| 
 | |
|         CGContextRestoreGState( cgContext );
 | |
|         QDEndCGContext( port, &cgContext );
 | |
|         delete surfaceItem;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| pascal void ListBoxDrawItemCallback(ControlRef browser, DataBrowserItemID item,
 | |
|                     DataBrowserPropertyID property,
 | |
|                     DataBrowserItemState itemState,
 | |
|                     const Rect *theRect, SInt16 gdDepth,
 | |
|                     Boolean colorDevice)
 | |
| {
 | |
|     if (property != kIconColumn) return;
 | |
|     ListBoxImpl* lbThis = NULL;
 | |
|     OSStatus err;
 | |
|     err = GetControlProperty( browser, scintillaListBoxType, 0, sizeof( lbThis ), NULL, &lbThis );
 | |
|     // adjust our rect
 | |
|     lbThis->DrawRow(item, property, itemState, theRect);
 | |
| 
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::ConfigureDataBrowser()
 | |
| {
 | |
|     DataBrowserViewStyle viewStyle;
 | |
|     DataBrowserSelectionFlags selectionFlags;
 | |
|     ::GetDataBrowserViewStyle(lb, &viewStyle);
 | |
| 
 | |
|     ::SetDataBrowserHasScrollBars(lb, false, true);
 | |
|     ::SetDataBrowserListViewHeaderBtnHeight(lb, 0);
 | |
|     ::GetDataBrowserSelectionFlags(lb, &selectionFlags);
 | |
|     ::SetDataBrowserSelectionFlags(lb, selectionFlags |= kDataBrowserSelectOnlyOne);
 | |
|     // if you change the hilite style, also change the style in ListBoxDrawItemCallback
 | |
|     ::SetDataBrowserTableViewHiliteStyle(lb, kDataBrowserTableViewFillHilite);
 | |
| 
 | |
|     Rect insetRect;
 | |
|     ::GetDataBrowserScrollBarInset(lb, &insetRect);
 | |
| 
 | |
|     insetRect.right = kScrollBarWidth - 1;
 | |
|     ::SetDataBrowserScrollBarInset(lb, &insetRect);
 | |
| 
 | |
|     switch (viewStyle)
 | |
|     {
 | |
|       case kDataBrowserListView:
 | |
|       {
 | |
|         DataBrowserListViewColumnDesc iconCol;
 | |
|         iconCol.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc;
 | |
|         iconCol.headerBtnDesc.minimumWidth = 0;
 | |
|         iconCol.headerBtnDesc.maximumWidth = maxWidth;
 | |
|         iconCol.headerBtnDesc.titleOffset = 0;
 | |
|         iconCol.headerBtnDesc.titleString = NULL;
 | |
|         iconCol.headerBtnDesc.initialOrder = kDataBrowserOrderIncreasing;
 | |
| 
 | |
|         iconCol.headerBtnDesc.btnFontStyle.flags = kControlUseJustMask;
 | |
|         iconCol.headerBtnDesc.btnFontStyle.just = teFlushLeft;
 | |
| 
 | |
|         iconCol.headerBtnDesc.btnContentInfo.contentType = kControlContentTextOnly;
 | |
| 
 | |
|         iconCol.propertyDesc.propertyID = kIconColumn;
 | |
|         iconCol.propertyDesc.propertyType = kDataBrowserCustomType;
 | |
|         iconCol.propertyDesc.propertyFlags = kDataBrowserListViewSelectionColumn;
 | |
| 
 | |
|         ::AddDataBrowserListViewColumn(lb, &iconCol, kDataBrowserListViewAppendColumn);
 | |
|       }  break;
 | |
| 
 | |
|     }
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::InstallDataBrowserCustomCallbacks()
 | |
| {
 | |
|     DataBrowserCustomCallbacks callbacks;
 | |
| 
 | |
|     callbacks.version = kDataBrowserLatestCustomCallbacks;
 | |
|     verify_noerr(InitDataBrowserCustomCallbacks(&callbacks));
 | |
|     callbacks.u.v1.drawItemCallback = NewDataBrowserDrawItemUPP(ListBoxDrawItemCallback);
 | |
|     callbacks.u.v1.hitTestCallback = NULL;//NewDataBrowserHitTestUPP(ListBoxHitTestCallback);
 | |
|     callbacks.u.v1.trackingCallback = NULL;//NewDataBrowserTrackingUPP(ListBoxTrackingCallback);
 | |
|     callbacks.u.v1.editTextCallback = NULL;
 | |
|     callbacks.u.v1.dragRegionCallback = NULL;
 | |
|     callbacks.u.v1.acceptDragCallback = NULL;
 | |
|     callbacks.u.v1.receiveDragCallback = NULL;
 | |
| 
 | |
|     SetDataBrowserCustomCallbacks(lb, &callbacks);
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::SetFont(Font &font_) {
 | |
|     // Having to do this conversion is LAME
 | |
|     QuartzTextStyle *ts = reinterpret_cast<QuartzTextStyle*>( font_.GetID() );
 | |
|     ControlFontStyleRec style;
 | |
|     ATSUAttributeValuePtr value;
 | |
|     ATSUFontID        fontID;
 | |
|     style.flags = kControlUseFontMask | kControlUseSizeMask | kControlAddToMetaFontMask;
 | |
|     ts->getAttribute( kATSUFontTag, sizeof(fontID), &fontID, NULL );
 | |
|     ATSUFontIDtoFOND(fontID, &style.font, NULL);
 | |
|     ts->getAttribute( kATSUSizeTag, sizeof(Fixed), &value, NULL );
 | |
|     style.size = ((SInt16)FixRound((SInt32)value));
 | |
|     SetControlFontStyle(lb, &style);
 | |
| 
 | |
| #ifdef DB_TABLE_ROW_HEIGHT
 | |
|     //  XXX this doesn't *stick*
 | |
|     ATSUTextMeasurement ascent = ts->getAttribute<ATSUTextMeasurement>( kATSUAscentTag );
 | |
|     ATSUTextMeasurement descent = ts->getAttribute<ATSUTextMeasurement>( kATSUDescentTag );
 | |
|     lineHeight = Fix2Long( ascent ) + Fix2Long( descent );
 | |
|     SetDataBrowserTableViewRowHeight(lb, lineHeight + lineLeading);
 | |
| #endif
 | |
| 
 | |
|     // !@&^#%$ we cant copy Font, but we need one for our custom drawing
 | |
|     Str255 fontName255;
 | |
|     char fontName[256];
 | |
|     FMGetFontFamilyName(style.font, fontName255);
 | |
| 
 | |
|     CFStringRef fontNameCF = ::CFStringCreateWithPascalString( kCFAllocatorDefault, fontName255, kCFStringEncodingMacRoman );
 | |
|     ::CFStringGetCString( fontNameCF, fontName, (CFIndex)255, kCFStringEncodingMacRoman );
 | |
| 
 | |
|     font.Create((const char *)fontName, 0, style.size, false, false);
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::SetAverageCharWidth(int width) {
 | |
|         aveCharWidth = width;
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::SetVisibleRows(int rows) {
 | |
|     desiredVisibleRows = rows;
 | |
| }
 | |
| 
 | |
| int ListBoxImpl::GetVisibleRows() const {
 | |
|     // XXX Windows & GTK do this, but it seems incorrect to me.  Other logic
 | |
|     //     to do with visible rows is essentially the same across platforms.
 | |
|     return desiredVisibleRows;
 | |
|     /*
 | |
|     // This would be more correct
 | |
|     int rows = Length();
 | |
|     if ((rows == 0) || (rows > desiredVisibleRows))
 | |
|         rows = desiredVisibleRows;
 | |
|     return rows;
 | |
|     */
 | |
| }
 | |
| 
 | |
| PRectangle ListBoxImpl::GetDesiredRect() {
 | |
|     PRectangle rcDesired = GetPosition();
 | |
| 
 | |
|     // XXX because setting the line height on the table doesnt
 | |
|     //     *stick*, we'll have to suffer and just use whatever
 | |
|     //     the table desides is the correct height.
 | |
|     UInt16 itemHeight;// = lineHeight;
 | |
|     GetDataBrowserTableViewRowHeight(lb, &itemHeight);
 | |
| 
 | |
|     int rows = Length();
 | |
|     if ((rows == 0) || (rows > desiredVisibleRows))
 | |
|         rows = desiredVisibleRows;
 | |
| 
 | |
|     rcDesired.bottom = itemHeight * rows;
 | |
|     rcDesired.right = rcDesired.left + maxItemWidth + aveCharWidth;
 | |
| 
 | |
|     if (Length() > rows)
 | |
|         rcDesired.right += kScrollBarWidth;
 | |
|     rcDesired.right += IconWidth();
 | |
| 
 | |
|     // Set the column width
 | |
|     ::SetDataBrowserTableViewColumnWidth (lb, UInt16 (rcDesired.right - rcDesired.left));
 | |
|     return rcDesired;
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::ShowHideScrollbar() {
 | |
|     int rows = Length();
 | |
|     if (rows > desiredVisibleRows) {
 | |
|         ::SetDataBrowserHasScrollBars(lb, false, true);
 | |
|     } else {
 | |
|         ::SetDataBrowserHasScrollBars(lb, false, false);
 | |
|     }
 | |
| }
 | |
| 
 | |
| int ListBoxImpl::IconWidth() {
 | |
|     return xset.GetWidth() + 2;
 | |
| }
 | |
| 
 | |
| int ListBoxImpl::CaretFromEdge() {
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::Clear() {
 | |
|     // passing NULL to "items" arg 4 clears the list
 | |
|     maxItemWidth = 0;
 | |
|     ld.Clear();
 | |
|     AddDataBrowserItems (lb, kDataBrowserNoItem, 0, NULL, kDataBrowserItemNoProperty);
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::Append(char *s, int type) {
 | |
|     int count = Length();
 | |
|     CFStringRef r = CFStringCreateWithCString(NULL, s, kTextEncodingMacRoman);
 | |
|     ld.Add(count, type, r);
 | |
| 
 | |
|     Scintilla::SurfaceImpl surface;
 | |
|     unsigned int width = surface.WidthText (font, s, strlen (s));
 | |
|     if (width > maxItemWidth)
 | |
|         maxItemWidth = width;
 | |
| 
 | |
|     DataBrowserItemID items[1];
 | |
|     items[0] = count + 1;
 | |
|     AddDataBrowserItems (lb, kDataBrowserNoItem, 1, items, kDataBrowserItemNoProperty);
 | |
|     ShowHideScrollbar();
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::SetList(const char* list, char separator, char typesep) {
 | |
|     // XXX copied from PlatGTK, should be in base class
 | |
|     Clear();
 | |
|     int count = strlen(list) + 1;
 | |
|     char *words = new char[count];
 | |
|     if (words) {
 | |
|         memcpy(words, list, count);
 | |
|         char *startword = words;
 | |
|         char *numword = NULL;
 | |
|         int i = 0;
 | |
|         for (; words[i]; i++) {
 | |
|             if (words[i] == separator) {
 | |
|                 words[i] = '\0';
 | |
|                 if (numword)
 | |
|                     *numword = '\0';
 | |
|                 Append(startword, numword?atoi(numword + 1):-1);
 | |
|                 startword = words + i + 1;
 | |
|                 numword = NULL;
 | |
|             } else if (words[i] == typesep) {
 | |
|                 numword = words + i;
 | |
|             }
 | |
|         }
 | |
|         if (startword) {
 | |
|             if (numword)
 | |
|                 *numword = '\0';
 | |
|             Append(startword, numword?atoi(numword + 1):-1);
 | |
|         }
 | |
|         delete []words;
 | |
|     }
 | |
| }
 | |
| 
 | |
| int ListBoxImpl::Length() {
 | |
|     UInt32 numItems = 0;
 | |
|     GetDataBrowserItemCount(lb, kDataBrowserNoItem, false, kDataBrowserItemAnyState, &numItems);
 | |
|     return (int)numItems;
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::Select(int n) {
 | |
|     DataBrowserItemID items[1];
 | |
|     items[0] = n + 1;
 | |
|     SetDataBrowserSelectedItems(lb, 1, items, kDataBrowserItemsAssign);
 | |
|     RevealDataBrowserItem(lb, items[0], kIconColumn, kDataBrowserRevealOnly);
 | |
|     // force update on selection
 | |
|     Draw1Control(lb);
 | |
| }
 | |
| 
 | |
| int ListBoxImpl::GetSelection() {
 | |
|     Handle selectedItems = NewHandle(0);
 | |
|     GetDataBrowserItems(lb, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, selectedItems);
 | |
|     UInt32 numSelectedItems = GetHandleSize(selectedItems)/sizeof(DataBrowserItemID);
 | |
|     if (numSelectedItems == 0) {
 | |
|         return -1;
 | |
|     }
 | |
|     HLock( selectedItems );
 | |
|     DataBrowserItemID *individualItem = (DataBrowserItemID*)( *selectedItems );
 | |
|     DataBrowserItemID selected[numSelectedItems];
 | |
|     selected[0] = *individualItem;
 | |
|     HUnlock( selectedItems );
 | |
|     return selected[0] - 1;
 | |
| }
 | |
| 
 | |
| int ListBoxImpl::Find(const char *prefix) {
 | |
|     int count = Length();
 | |
|     char s[255];
 | |
|     for (int i = 0; i < count; i++) {
 | |
|         GetValue(i, s, 255);
 | |
|         if (s[0] != NULL && (0 == strncmp(prefix, s, strlen(prefix)))) {
 | |
|             return i;
 | |
|         }
 | |
|     }
 | |
|     return - 1;
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::GetValue(int n, char *value, int len) {
 | |
|     CFStringRef textString = ld.GetString(n);
 | |
|     if (textString == NULL) {
 | |
|         value[0] = '\0';
 | |
|         return;
 | |
|     }
 | |
|     CFIndex numUniChars = CFStringGetLength( textString );
 | |
| 
 | |
|     // XXX how do we know the encoding of the listbox?
 | |
|     CFStringEncoding encoding = kCFStringEncodingUTF8; //( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII);
 | |
|     CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1;
 | |
|     char* text = new char[maximumByteLength];
 | |
|     CFIndex usedBufferLength = 0;
 | |
|     CFStringGetBytes( textString, CFRangeMake( 0, numUniChars ), encoding,
 | |
|               '?', false, reinterpret_cast<UInt8*>( text ),
 | |
|               maximumByteLength, &usedBufferLength );
 | |
|     text[usedBufferLength] = '\0'; // null terminate the ASCII/UTF8 string
 | |
| 
 | |
|     if (text && len > 0) {
 | |
|         strncpy(value, text, len);
 | |
|         value[len - 1] = '\0';
 | |
|     } else {
 | |
|         value[0] = '\0';
 | |
|     }
 | |
|     delete []text;
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::RegisterImage(int type, const char *xpm_data) {
 | |
|     xset.Add(type, xpm_data);
 | |
| }
 | |
| 
 | |
| void ListBoxImpl::ClearRegisteredImages() {
 | |
|     xset.Clear();
 | |
| }
 | |
| 
 | |
| Menu::Menu() : mid(0) { }
 | |
| 
 | |
| void Menu::CreatePopUp() {
 | |
|     // TODO: Could I just feed a constant menu ID parameter, or does
 | |
|     // it really need to be unique?
 | |
|     static int nextMenuID = 1;
 | |
|     Destroy();
 | |
|     OSStatus err;
 | |
|     err = CreateNewMenu( nextMenuID++, 0, reinterpret_cast<MenuRef*>( &mid ) );
 | |
| }
 | |
| 
 | |
| void Menu::Destroy() {
 | |
|     if ( mid != NULL )
 | |
|     {
 | |
|         ReleaseMenu( reinterpret_cast<MenuRef>( mid ) );
 | |
|         mid = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Menu::Show(Point pt, Window &) {
 | |
|     UInt32 userSelection = 0;
 | |
|     SInt16 menuId = 0;
 | |
|     MenuItemIndex menuItem = 0;
 | |
|     ::Point globalPoint;
 | |
|     globalPoint.h = pt.x;
 | |
|     globalPoint.v = pt.y;
 | |
|     OSStatus err;
 | |
|     err = ContextualMenuSelect( reinterpret_cast<MenuRef>( mid ), globalPoint,
 | |
|                                 false, kCMHelpItemRemoveHelp, NULL,
 | |
|                                 NULL, &userSelection,
 | |
|                                 &menuId,
 | |
|                                 &menuItem
 | |
|                                 );
 | |
| }
 | |
| 
 | |
| // ElapsedTime is used for precise performance measurements during development
 | |
| // and not for anything a user sees.
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| ColourDesired Platform::Chrome() {
 | |
|     RGBColor c;
 | |
|     GetThemeBrushAsColor(kThemeBrushButtonActiveDarkShadow , 24, true, &c);
 | |
|     return ColourDesired(c.red>>8, c.green>>8, c.blue>>8);
 | |
| }
 | |
| 
 | |
| ColourDesired Platform::ChromeHighlight() {
 | |
|     RGBColor c;
 | |
|     GetThemeBrushAsColor(kThemeBrushButtonInactiveLightShadow , 24, true, &c);
 | |
|     return ColourDesired(c.red>>8, c.green>>8, c.blue>>8);
 | |
| }
 | |
| 
 | |
| static Str255 PlatformDefaultFontName;
 | |
| const char *Platform::DefaultFont() {
 | |
|     long fid = HighShortFromLong(GetScriptVariable(smCurrentScript, smScriptAppFondSize));
 | |
|     FMGetFontFamilyName(fid, PlatformDefaultFontName);
 | |
|     char* defaultFontName = (char*) PlatformDefaultFontName;
 | |
|     defaultFontName[defaultFontName[0]+1] = 0;
 | |
|     ++defaultFontName;
 | |
| 
 | |
|     return defaultFontName;
 | |
| }
 | |
| 
 | |
| int Platform::DefaultFontSize() {
 | |
|     return LowShortFromLong(GetScriptVariable(smCurrentScript, smScriptAppFondSize));
 | |
| }
 | |
| 
 | |
| unsigned int Platform::DoubleClickTime() {
 | |
|     // Convert from ticks to milliseconds. I think it would be better to use the events to tell us
 | |
|     // when we have a double and triple click, but what do I know?
 | |
|     return static_cast<unsigned int>( TicksToEventTime( GetDblTime() ) / kEventDurationMillisecond );
 | |
| }
 | |
| 
 | |
| bool Platform::MouseButtonBounce() {
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| bool Platform::IsKeyDown(int keyCode) {
 | |
|     return false;
 | |
|     // TODO: Map Scintilla/Windows key codes to Mac OS X key codes
 | |
|     // TODO: Do I need this?
 | |
|     /*
 | |
|     // Inspired by code at: http://www.sover.net/~jams/Morgan/docs/GameInputMethods.txt
 | |
| 
 | |
|     // Get the keys
 | |
|     KeyMap keys;
 | |
|     GetKeys( keys );
 | |
| 
 | |
|     // Calculate the key map index
 | |
|     long keyMapIndex = keys[keyCode/8];
 | |
|     // Calculate the individual bit to check
 | |
|     short bitToCheck = keyCode % 8;
 | |
|     // Check the status of the key
 | |
|     return ( keyMapIndex >> bitToCheck ) & 0x01;
 | |
|      */
 | |
| }
 | |
| 
 | |
| long Platform::SendScintilla(WindowID w, unsigned int msg, unsigned long wParam, long lParam) {
 | |
|     return scintilla_send_message( w, msg, wParam, lParam );
 | |
| }
 | |
| 
 | |
| bool Platform::IsDBCSLeadByte(int /*codePage*/, char /*ch*/) {
 | |
|     // TODO: Implement this for code pages != UTF-8
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| int Platform::DBCSCharLength(int /*codePage*/, const char* /*s*/) {
 | |
|     // TODO: Implement this for code pages != UTF-8
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| int Platform::DBCSCharMaxLength() {
 | |
|     // TODO: Implement this for code pages != UTF-8
 | |
|     //return CFStringGetMaximumSizeForEncoding( 1, CFStringEncoding encoding );
 | |
|     return 2;
 | |
| }
 | |
| 
 | |
| // These are utility functions not really tied to a platform
 | |
| int Platform::Minimum(int a, int b) {
 | |
|     if (a < b)
 | |
|         return a;
 | |
|     else
 | |
|         return b;
 | |
| }
 | |
| 
 | |
| int Platform::Maximum(int a, int b) {
 | |
|     if (a > b)
 | |
|         return a;
 | |
|     else
 | |
|         return b;
 | |
| }
 | |
| 
 | |
| //#define TRACE
 | |
| #ifdef TRACE
 | |
| 
 | |
| void Platform::DebugDisplay(const char *s) {
 | |
|     fprintf( stderr, s );
 | |
| }
 | |
| 
 | |
| void Platform::DebugPrintf(const char *format, ...) {
 | |
|     const int BUF_SIZE = 2000;
 | |
|     char buffer[BUF_SIZE];
 | |
| 
 | |
|     va_list pArguments;
 | |
|     va_start(pArguments, format);
 | |
|     vsnprintf(buffer, BUF_SIZE, format, pArguments);
 | |
|     va_end(pArguments);
 | |
|     Platform::DebugDisplay(buffer);
 | |
| }
 | |
| 
 | |
| #else
 | |
| 
 | |
| void Platform::DebugDisplay(const char *) {}
 | |
| 
 | |
| void Platform::DebugPrintf(const char *, ...) {}
 | |
| 
 | |
| #endif
 | |
| 
 | |
| // Not supported for GTK+
 | |
| static bool assertionPopUps = true;
 | |
| 
 | |
| bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
 | |
|     bool ret = assertionPopUps;
 | |
|     assertionPopUps = assertionPopUps_;
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| void Platform::Assert(const char *c, const char *file, int line) {
 | |
|     char buffer[2000];
 | |
|     sprintf(buffer, "Assertion [%s] failed at %s %d", c, file, line);
 | |
|     strcat(buffer, "\r\n");
 | |
|     Platform::DebugDisplay(buffer);
 | |
| #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;
 | |
| }
 |