1853 lines
62 KiB
C++
1853 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 );
|
||
|
// TODO: Should the path be automatically closed, or is that the caller's responsability?
|
||
|
// Explicitly close the path, so it is closed for stroking AND filling (implicit close = filling only)
|
||
|
CGContextClosePath( gc );
|
||
|
CGContextDrawPath( gc, kCGPathFillStroke );
|
||
|
|
||
|
// Deallocate memory
|
||
|
delete [] points;
|
||
|
points = NULL;
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::RectangleDraw(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
|
||
|
if ( gc ) {
|
||
|
CGContextBeginPath( gc );
|
||
|
FillColour(back);
|
||
|
PenColour(fore);
|
||
|
|
||
|
// Quartz integer -> float point conversion fun (see comment in SurfaceImpl::LineTo)
|
||
|
// We subtract 1 from the Width() and Height() so that all our drawing is within the area defined
|
||
|
// by the PRectangle. Otherwise, we draw one pixel too far to the right and bottom.
|
||
|
// TODO: Create some version of PRectangleToCGRect to do this conversion for us?
|
||
|
CGContextAddRect( gc, CGRectMake( rc.left + 0.5, rc.top + 0.5, rc.Width() - 1, rc.Height() - 1 ) );
|
||
|
CGContextDrawPath( gc, kCGPathFillStroke );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::FillRectangle(PRectangle rc, ColourAllocated back) {
|
||
|
if ( gc ) {
|
||
|
//CGContextBeginPath( gc );
|
||
|
FillColour(back);
|
||
|
|
||
|
CGRect rect = PRectangleToCGRect( rc );
|
||
|
|
||
|
CGContextFillRect( gc, rect );
|
||
|
//CGContextDrawPath( gc, kCGPathFill );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void drawImageRefCallback( CGImageRef pattern, CGContextRef gc )
|
||
|
{
|
||
|
CGContextDrawImage( gc, CGRectMake( 0, 0, CGImageGetWidth( pattern ), CGImageGetHeight( pattern ) ), pattern );
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::FillRectangle(PRectangle rc, Surface &surfacePattern) {
|
||
|
SurfaceImpl& patternSurface = static_cast<SurfaceImpl &>(surfacePattern);
|
||
|
|
||
|
// For now, assume that copy can only be called on PixMap surfaces
|
||
|
// Shows up black
|
||
|
CGImageRef image = patternSurface.GetImage();
|
||
|
if ( image == NULL )
|
||
|
{
|
||
|
FillRectangle(rc, ColourAllocated(0));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const CGPatternCallbacks drawImageCallbacks = { 0, reinterpret_cast<CGPatternDrawPatternCallback>( drawImageRefCallback ), NULL };
|
||
|
|
||
|
CGPatternRef pattern = CGPatternCreate( image,
|
||
|
CGRectMake( 0, 0, patternSurface.bitmapWidth, patternSurface.bitmapHeight ),
|
||
|
CGAffineTransformIdentity,
|
||
|
patternSurface.bitmapWidth,
|
||
|
patternSurface.bitmapHeight,
|
||
|
kCGPatternTilingNoDistortion,
|
||
|
true,
|
||
|
&drawImageCallbacks
|
||
|
);
|
||
|
if( pattern != NULL ) {
|
||
|
|
||
|
// Create a pattern color space
|
||
|
CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern( NULL );
|
||
|
if( colorSpace != NULL ) {
|
||
|
|
||
|
CGContextSaveGState( gc );
|
||
|
CGContextSetFillColorSpace( gc, colorSpace );
|
||
|
|
||
|
// Unlike the documentation, you MUST pass in a "components" parameter:
|
||
|
// For coloured patterns it is the alpha value.
|
||
|
const float alpha = 1.0;
|
||
|
CGContextSetFillPattern( gc, pattern, &alpha );
|
||
|
CGContextFillRect( gc, PRectangleToCGRect( rc ) );
|
||
|
CGContextRestoreGState( gc );
|
||
|
// Free the color space, the pattern and image
|
||
|
CGColorSpaceRelease( colorSpace );
|
||
|
} /* colorSpace != NULL */
|
||
|
colorSpace = NULL;
|
||
|
CGPatternRelease( pattern );
|
||
|
pattern = NULL;
|
||
|
CGImageRelease( image );
|
||
|
image = NULL;
|
||
|
} /* pattern != NULL */
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::RoundedRectangle(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
|
||
|
// TODO: Look at the Win32 API to determine what this is supposed to do:
|
||
|
// ::RoundRect(hdc, rc.left + 1, rc.top, rc.right - 1, rc.bottom, 8, 8 );
|
||
|
|
||
|
// Create a rectangle with semicircles at the corners
|
||
|
const int MAX_RADIUS = 4;
|
||
|
int radius = Platform::Minimum( MAX_RADIUS, rc.Height()/2 );
|
||
|
radius = Platform::Minimum( radius, rc.Width()/2 );
|
||
|
|
||
|
// Points go clockwise, starting from just below the top left
|
||
|
// Corners are kept together, so we can easily create arcs to connect them
|
||
|
CGPoint corners[4][3] =
|
||
|
{
|
||
|
{
|
||
|
{ rc.left, rc.top + radius },
|
||
|
{ rc.left, rc.top },
|
||
|
{ rc.left + radius, rc.top },
|
||
|
},
|
||
|
{
|
||
|
{ rc.right - radius - 1, rc.top },
|
||
|
{ rc.right - 1, rc.top },
|
||
|
{ rc.right - 1, rc.top + radius },
|
||
|
},
|
||
|
{
|
||
|
{ rc.right - 1, rc.bottom - radius - 1 },
|
||
|
{ rc.right - 1, rc.bottom - 1 },
|
||
|
{ rc.right - radius - 1, rc.bottom - 1 },
|
||
|
},
|
||
|
{
|
||
|
{ rc.left + radius, rc.bottom - 1 },
|
||
|
{ rc.left, rc.bottom - 1 },
|
||
|
{ rc.left, rc.bottom - radius - 1 },
|
||
|
},
|
||
|
};
|
||
|
|
||
|
// Align the points in the middle of the pixels
|
||
|
// TODO: Should I include these +0.5 in the array creation code above?
|
||
|
for( int i = 0; i < 4*3; ++ i )
|
||
|
{
|
||
|
CGPoint* c = (CGPoint*) corners;
|
||
|
c[i].x += 0.5;
|
||
|
c[i].y += 0.5;
|
||
|
}
|
||
|
|
||
|
PenColour( fore );
|
||
|
FillColour( back );
|
||
|
|
||
|
// Move to the last point to begin the path
|
||
|
CGContextBeginPath( gc );
|
||
|
CGContextMoveToPoint( gc, corners[3][2].x, corners[3][2].y );
|
||
|
|
||
|
for ( int i = 0; i < 4; ++ i )
|
||
|
{
|
||
|
CGContextAddLineToPoint( gc, corners[i][0].x, corners[i][0].y );
|
||
|
CGContextAddArcToPoint( gc, corners[i][1].x, corners[i][1].y, corners[i][2].x, corners[i][2].y, radius );
|
||
|
}
|
||
|
|
||
|
// Close the path to enclose it for stroking and for filling, then draw it
|
||
|
CGContextClosePath( gc );
|
||
|
CGContextDrawPath( gc, kCGPathFillStroke );
|
||
|
}
|
||
|
|
||
|
void Scintilla::SurfaceImpl::AlphaRectangle(PRectangle rc, int /*cornerSize*/, ColourAllocated fill, int alphaFill,
|
||
|
ColourAllocated /*outline*/, int /*alphaOutline*/, int /*flags*/)
|
||
|
{
|
||
|
if ( gc ) {
|
||
|
ColourDesired colour( fill.AsLong() );
|
||
|
|
||
|
// Set the Fill color to match
|
||
|
CGContextSetRGBFillColor( gc, colour.GetRed() / 255.0, colour.GetGreen() / 255.0, colour.GetBlue() / 255.0, alphaFill / 100.0 );
|
||
|
CGRect rect = PRectangleToCGRect( rc );
|
||
|
CGContextFillRect( gc, rect );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::Ellipse(PRectangle rc, ColourAllocated fore, ColourAllocated back) {
|
||
|
// Drawing an ellipse with bezier curves. Code modified from:
|
||
|
// http://www.codeguru.com/gdi/ellipse.shtml
|
||
|
// MAGICAL CONSTANT to map ellipse to beziers 2/3*(sqrt(2)-1)
|
||
|
const double EToBConst = 0.2761423749154;
|
||
|
|
||
|
CGSize offset = CGSizeMake((int)(rc.Width() * EToBConst), (int)(rc.Height() * EToBConst));
|
||
|
CGPoint centre = CGPointMake((rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2);
|
||
|
|
||
|
// The control point array
|
||
|
CGPoint cCtlPt[13];
|
||
|
|
||
|
// Assign values to all the control points
|
||
|
cCtlPt[0].x =
|
||
|
cCtlPt[1].x =
|
||
|
cCtlPt[11].x =
|
||
|
cCtlPt[12].x = rc.left + 0.5;
|
||
|
cCtlPt[5].x =
|
||
|
cCtlPt[6].x =
|
||
|
cCtlPt[7].x = rc.right - 0.5;
|
||
|
cCtlPt[2].x =
|
||
|
cCtlPt[10].x = centre.x - offset.width + 0.5;
|
||
|
cCtlPt[4].x =
|
||
|
cCtlPt[8].x = centre.x + offset.width + 0.5;
|
||
|
cCtlPt[3].x =
|
||
|
cCtlPt[9].x = centre.x + 0.5;
|
||
|
|
||
|
cCtlPt[2].y =
|
||
|
cCtlPt[3].y =
|
||
|
cCtlPt[4].y = rc.top + 0.5;
|
||
|
cCtlPt[8].y =
|
||
|
cCtlPt[9].y =
|
||
|
cCtlPt[10].y = rc.bottom - 0.5;
|
||
|
cCtlPt[7].y =
|
||
|
cCtlPt[11].y = centre.y + offset.height + 0.5;
|
||
|
cCtlPt[1].y =
|
||
|
cCtlPt[5].y = centre.y - offset.height + 0.5;
|
||
|
cCtlPt[0].y =
|
||
|
cCtlPt[12].y =
|
||
|
cCtlPt[6].y = centre.y + 0.5;
|
||
|
|
||
|
FillColour(back);
|
||
|
PenColour(fore);
|
||
|
|
||
|
CGContextBeginPath( gc );
|
||
|
CGContextMoveToPoint( gc, cCtlPt[0].x, cCtlPt[0].y );
|
||
|
|
||
|
for ( int i = 1; i < 13; i += 3 )
|
||
|
{
|
||
|
CGContextAddCurveToPoint( gc, cCtlPt[i].x, cCtlPt[i].y, cCtlPt[i+1].x, cCtlPt[i+1].y, cCtlPt[i+2].x, cCtlPt[i+2].y );
|
||
|
}
|
||
|
|
||
|
// Close the path to enclose it for stroking and for filling, then draw it
|
||
|
CGContextClosePath( gc );
|
||
|
CGContextDrawPath( gc, kCGPathFillStroke );
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::CopyImageRectangle(Surface &surfaceSource, PRectangle srcRect, PRectangle dstRect)
|
||
|
{
|
||
|
SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
|
||
|
CGImageRef image = source.GetImage();
|
||
|
|
||
|
CGRect src = PRectangleToCGRect(srcRect);
|
||
|
CGRect dst = PRectangleToCGRect(dstRect);
|
||
|
|
||
|
/* source from QuickDrawToQuartz2D.pdf on developer.apple.com */
|
||
|
float w = (float) CGImageGetWidth(image);
|
||
|
float h = (float) CGImageGetHeight(image);
|
||
|
CGRect drawRect = CGRectMake (0, 0, w, h);
|
||
|
if (!CGRectEqualToRect (src, dst))
|
||
|
{
|
||
|
float sx = CGRectGetWidth(dst) / CGRectGetWidth(src);
|
||
|
float sy = CGRectGetHeight(dst) / CGRectGetHeight(src);
|
||
|
float dx = CGRectGetMinX(dst) - (CGRectGetMinX(src) * sx);
|
||
|
float dy = CGRectGetMinY(dst) - (CGRectGetMinY(src) * sy);
|
||
|
drawRect = CGRectMake (dx, dy, w*sx, h*sy);
|
||
|
}
|
||
|
CGContextSaveGState (gc);
|
||
|
CGContextClipToRect (gc, dst);
|
||
|
CGContextDrawImage (gc, drawRect, image);
|
||
|
CGContextRestoreGState (gc);
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSource) {
|
||
|
// Maybe we have to make the Surface two contexts:
|
||
|
// a bitmap context which we do all the drawing on, and then a "real" context
|
||
|
// which we copy the output to when we call "Synchronize". Ugh! Gross and slow!
|
||
|
|
||
|
// For now, assume that copy can only be called on PixMap surfaces
|
||
|
SurfaceImpl& source = static_cast<SurfaceImpl &>(surfaceSource);
|
||
|
|
||
|
// Get the CGImageRef
|
||
|
CGImageRef image = source.GetImage();
|
||
|
// If we could not get an image reference, fill the rectangle black
|
||
|
if ( image == NULL )
|
||
|
{
|
||
|
FillRectangle( rc, ColourAllocated( 0 ) );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Now draw the image on the surface
|
||
|
|
||
|
// Some fancy clipping work is required here: draw only inside of rc
|
||
|
CGContextSaveGState( gc );
|
||
|
CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
|
||
|
|
||
|
//Platform::DebugPrintf(stderr, "Copy: CGContextDrawImage: (%d, %d) - (%d X %d)\n", rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight );
|
||
|
CGContextDrawImage( gc, CGRectMake( rc.left - from.x, rc.top - from.y, source.bitmapWidth, source.bitmapHeight ), image );
|
||
|
|
||
|
// Undo the clipping fun
|
||
|
CGContextRestoreGState( gc );
|
||
|
|
||
|
// Done with the image
|
||
|
CGImageRelease( image );
|
||
|
image = NULL;
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::DrawTextNoClip(PRectangle rc, Font &font_, int ybase, const char *s, int len,
|
||
|
ColourAllocated fore, ColourAllocated back) {
|
||
|
|
||
|
FillRectangle(rc, back);
|
||
|
DrawTextTransparent( rc, font_, ybase, s, len, fore );
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::DrawTextClipped(PRectangle rc, Font &font_, int ybase, const char *s, int len,
|
||
|
ColourAllocated fore, ColourAllocated back) {
|
||
|
|
||
|
CGContextSaveGState( gc );
|
||
|
CGContextClipToRect( gc, PRectangleToCGRect( rc ) );
|
||
|
DrawTextNoClip( rc, font_, ybase, s, len, fore, back );
|
||
|
CGContextRestoreGState( gc );
|
||
|
}
|
||
|
|
||
|
void SurfaceImpl::DrawTextTransparent(PRectangle rc, Font &font_, int ybase, const char *s, int len, ColourAllocated fore) {
|
||
|
textLayout->setText (reinterpret_cast<const UInt8*>(s), len, *reinterpret_cast<QuartzTextStyle*>(font_.GetID()));
|
||
|
|
||
|
// The Quartz RGB fill color influences the ATSUI color
|
||
|
FillColour(fore);
|
||
|
// Draw 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 Sort();
|
||
|
void RegisterImage(int type, const char *xpm_data);
|
||
|
void ClearRegisteredImages();
|
||
|
void SetDoubleClickAction(CallBackAction action, void *data) {
|
||
|
doubleClickAction = action;
|
||
|
doubleClickActionData = data;
|
||
|
}
|
||
|
|
||
|
int IconWidth();
|
||
|
void ShowHideScrollbar();
|
||
|
#ifdef DB_TABLE_ROW_HEIGHT
|
||
|
void SetRowHeight(DataBrowserItemID itemID);
|
||
|
#endif
|
||
|
|
||
|
void DrawRow(DataBrowserItemID item,
|
||
|
DataBrowserPropertyID property,
|
||
|
DataBrowserItemState itemState,
|
||
|
const Rect *theRect);
|
||
|
|
||
|
void SetList(const char* list, char separator, char typesep);
|
||
|
};
|
||
|
|
||
|
ListBox *ListBox::Allocate() {
|
||
|
ListBoxImpl *lb = new ListBoxImpl();
|
||
|
return lb;
|
||
|
}
|
||
|
|
||
|
void ListBoxImpl::Create(Window &/*parent*/, int /*ctrlID*/, Scintilla::Point /*pt*/,
|
||
|
int lineHeight_, bool unicodeMode_) {
|
||
|
lineHeight = lineHeight_;
|
||
|
unicodeMode = unicodeMode_;
|
||
|
maxWidth = 2000;
|
||
|
|
||
|
WindowClass windowClass = kHelpWindowClass;
|
||
|
WindowAttributes attributes = kWindowNoAttributes;
|
||
|
Rect contentBounds;
|
||
|
WindowRef outWindow;
|
||
|
|
||
|
contentBounds.top = contentBounds.left = 0;
|
||
|
contentBounds.right = 100;
|
||
|
contentBounds.bottom = lineHeight * desiredVisibleRows;
|
||
|
|
||
|
CreateNewWindow(windowClass, attributes, &contentBounds, &outWindow);
|
||
|
|
||
|
InstallStandardEventHandler(GetWindowEventTarget(outWindow));
|
||
|
|
||
|
ControlRef root;
|
||
|
CreateRootControl(outWindow, &root);
|
||
|
|
||
|
CreateDataBrowserControl(outWindow, &contentBounds, kDataBrowserListView, &lb);
|
||
|
|
||
|
#ifdef DB_TABLE_ROW_HEIGHT
|
||
|
// 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::Sort() {
|
||
|
// TODO: Implement this
|
||
|
fprintf(stderr, "ListBox::Sort\n");
|
||
|
}
|
||
|
|
||
|
void ListBoxImpl::RegisterImage(int type, const char *xpm_data) {
|
||
|
xset.Add(type, xpm_data);
|
||
|
}
|
||
|
|
||
|
void ListBoxImpl::ClearRegisteredImages() {
|
||
|
xset.Clear();
|
||
|
}
|
||
|
|
||
|
Menu::Menu() : 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
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// TODO: Consider if I should be using GetCurrentEventTime instead of gettimeoday
|
||
|
ElapsedTime::ElapsedTime() {
|
||
|
struct timeval curTime;
|
||
|
int retVal;
|
||
|
retVal = gettimeofday( &curTime, NULL );
|
||
|
|
||
|
bigBit = curTime.tv_sec;
|
||
|
littleBit = curTime.tv_usec;
|
||
|
}
|
||
|
|
||
|
double ElapsedTime::Duration(bool reset) {
|
||
|
struct timeval curTime;
|
||
|
int retVal;
|
||
|
retVal = gettimeofday( &curTime, NULL );
|
||
|
long endBigBit = curTime.tv_sec;
|
||
|
long endLittleBit = curTime.tv_usec;
|
||
|
double result = 1000000.0 * (endBigBit - bigBit);
|
||
|
result += endLittleBit - littleBit;
|
||
|
result /= 1000000.0;
|
||
|
if (reset) {
|
||
|
bigBit = endBigBit;
|
||
|
littleBit = endLittleBit;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|