1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [LGPL]
2 // see below for copyright information on the second portion of this file
5 #include <org/xwt/plat/Carbon.h>
6 #include <org/xwt/plat/Carbon$CarbonSurface.h>
7 #include <org/xwt/plat/Carbon$CarbonPicture.h>
8 #include <org/xwt/plat/Carbon$CarbonDoubleBuffer.h>
9 #include <org/xwt/plat/Carbon$WrappedRawData.h>
10 #include <java/lang/System.h>
11 #include <java/lang/Error.h>
12 #include <java/io/PrintStream.h>
14 // Apple's version of GCC automatically defines this symbol.
15 // Since XWT is built with a "stock" version of GCC, we need to define
16 // this manually to get the system header files to allow us to include them
17 #define __APPLE_CPP__ 1
19 // Standard GCC doesn't support precompiled headers,
20 // meaning that it is WAY faster to just include the individual headers
21 #include <CoreFoundation/CFString.h>
22 #include <HIToolbox/MacWindows.h>
23 #include <HIToolbox/MacApplication.h>
24 #include <HIToolbox/Scrap.h>
25 #include <HIToolbox/Appearance.h>
26 #include <HIToolbox/CarbonEvents.h>
27 #include <HIToolbox/HIView.h>
28 #include <QD/ATSUnicode.h>
30 #define min(a, b) ((a) < (b) ? (a) : (b))
31 #define max(a, b) ((a) < (b) ? (b) : (a))
33 #define FORMAT_RECT( x ) x.left, x.top, x.right, x.bottom
34 #define PRINT_RECT( x ) fprintf( stderr, #x " = (%d, %d) (%d, %d)\n", x.left, x.top, x.right, x.bottom )
35 #define PRINT_CGRECT( rect ) fprintf( stderr, #rect " = %f x %f @ (%f, %f)\n", rect.size.width, rect.size.height, rect.origin.x, rect.origin.y );
38 static const EventTypeSpec CarbonSurfaceEventInfo[] = {
39 { kEventClassWindow, kEventWindowUpdate },
40 { kEventClassWindow, kEventWindowBoundsChanged },
41 { kEventClassWindow, kEventWindowActivated },
42 { kEventClassWindow, kEventWindowDeactivated },
43 { kEventClassWindow, kEventWindowClose },
44 { kEventClassWindow, kEventWindowZoomed },
45 { kEventClassWindow, kEventWindowExpanded },
46 { kEventClassWindow, kEventWindowCollapsed },
49 { kEventClassKeyboard, kEventRawKeyDown },
50 { kEventClassKeyboard, kEventRawKeyRepeat },
51 { kEventClassKeyboard, kEventRawKeyUp },
52 { kEventClassKeyboard, kEventRawKeyModifiersChanged },
55 { kEventClassMouse, kEventMouseDown },
56 { kEventClassMouse, kEventMouseUp },
57 { kEventClassMouse, kEventMouseMoved },
58 { kEventClassMouse, kEventMouseDragged },
59 { kEventClassMouse, kEventMouseExited },
64 static inline UniChar GetCharacterWithoutModifiers( EventRef rawKeyboardEvent )
67 // Get the key code from the raw key event
68 GetEventParameter( rawKeyboardEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof( keyCode ), NULL, &keyCode );
70 // Get the current keyboard layout
71 // TODO: If this is a performance sink, we need to cache these values
72 SInt16 lastKeyLayoutID = GetScriptVariable( /*currentKeyScript*/ GetScriptManagerVariable(smKeyScript), smScriptKeys);
73 Handle uchrHandle = GetResource('uchr', lastKeyLayoutID);
75 // Translate the key press ignoring ctrl and option
76 UInt32 ignoredDeadKeys = 0;
77 UInt32 ignoredActualLength = 0;
78 UniChar unicodeKey = 0;
80 // We actually want the current shift key value
81 UInt32 modifiers = (GetCurrentEventKeyModifiers() & shiftKey) >> 8;
82 OSStatus err = UCKeyTranslate( reinterpret_cast<UCKeyboardLayout*>( *uchrHandle ), keyCode, kUCKeyActionDown,
83 /* modifierKeyState */ modifiers, LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask, &ignoredDeadKeys,
84 /* buffer length */ 1,
85 /* actual length */ &ignoredActualLength,
86 /* string */ &unicodeKey );
87 assert( err == noErr );
92 static CFStringRef JavaToCFString(java::lang::String* s) {
93 int len = min(1024, JvGetStringUTFLength(s));
95 char* buffer = (char*) malloc( len );
96 JvGetStringUTFRegion(s, 0, s->length(), buffer);
98 // Create a CFString from the UTF8 string
99 CFStringRef string = CFStringCreateWithBytes( NULL, (UInt8*) buffer, len, kCFStringEncodingUTF8, false );
100 assert( string != NULL );
108 static jstring CFToJavaString( CFStringRef s ) {
109 // TODO: I can probably use CFStringCreateExternalRepresentation
110 // First determine the size of the buffer required
111 const CFRange entireString = CFRangeMake( 0, CFStringGetLength( s ) );
112 CFIndex requiredSize = 0;
113 CFIndex charsConverted = CFStringGetBytes( s, entireString, kCFStringEncodingUTF8, 0, false, NULL, 0, &requiredSize );
114 assert( charsConverted == CFStringGetLength( s ) );
116 // Allocate the buffer, plus an additional byte for the null byte
117 UInt8* buffer = (UInt8*) malloc( requiredSize + 1 );
118 assert( buffer != NULL );
120 // Convert the string
121 charsConverted = CFStringGetBytes( s, entireString, kCFStringEncodingUTF8, 0, false, buffer, requiredSize, NULL );
122 assert( charsConverted == CFStringGetLength( s ) );
124 buffer[requiredSize] = '\0';
126 jstring string = JvNewStringUTF( (char*) buffer );
127 assert( string != NULL );
135 static jstring UniCharToXWTString( UniChar character )
137 switch ( character ) {
138 case '\t': return JvNewStringLatin1("tab");
139 case kEscapeCharCode: return JvNewStringLatin1("escape");
142 case kReturnCharCode:
143 return JvNewStringLatin1("enter");
144 case kBackspaceCharCode: return JvNewStringLatin1("back_space");
146 //case kClearCharCode: return JvNewStringLatin1("clear");
147 //case VK_PAUSE: return JvNewStringLatin1("pause");
148 case kPageUpCharCode: return JvNewStringLatin1("page_up");
149 case kPageDownCharCode: return JvNewStringLatin1("page_down");
150 case kEndCharCode: return JvNewStringLatin1("end");
151 case kHomeCharCode: return JvNewStringLatin1("home");
152 case kLeftArrowCharCode: return JvNewStringLatin1("left");
153 case kUpArrowCharCode: return JvNewStringLatin1("up");
154 case kRightArrowCharCode: return JvNewStringLatin1("right");
155 case kDownArrowCharCode: return JvNewStringLatin1("down");
156 // TODO: Is help always the insert key in the mac world? on my keyboard it is
157 case kHelpCharCode: return JvNewStringLatin1("insert");
158 case kDeleteCharCode: return JvNewStringLatin1("delete");
159 // TODO: How can I determine if a numpad key was pressed?
160 //case VK_NUMPAD0: return JvNewStringLatin1("numpad0");
161 //case VK_NUMPAD1: return JvNewStringLatin1("numpad1");
162 //case VK_NUMPAD2: return JvNewStringLatin1("numpad2");
163 //case VK_NUMPAD3: return JvNewStringLatin1("numpad3");
164 //case VK_NUMPAD4: return JvNewStringLatin1("numpad4");
165 //case VK_NUMPAD5: return JvNewStringLatin1("numpad5");
166 //case VK_NUMPAD6: return JvNewStringLatin1("numpad6");
167 //case VK_NUMPAD7: return JvNewStringLatin1("numpad7");
168 //case VK_NUMPAD8: return JvNewStringLatin1("numpad8");
169 //case VK_NUMPAD9: return JvNewStringLatin1("numpad9");
170 // TODO: How do I get the function key?
171 case kFunctionKeyCharCode: return JvNewStringLatin1("f1");
172 //case VK_F2: return JvNewStringLatin1("f2");
173 //case VK_F3: return JvNewStringLatin1("f3");
174 //case VK_F4: return JvNewStringLatin1("f4");
175 //case VK_F5: return JvNewStringLatin1("f5");
176 //case VK_F6: return JvNewStringLatin1("f6");
177 //case VK_F7: return JvNewStringLatin1("f7");
178 //case VK_F8: return JvNewStringLatin1("f8");
179 //case VK_F9: return JvNewStringLatin1("f9");
180 //case VK_F10: return JvNewStringLatin1("f10");
181 //case VK_F11: return JvNewStringLatin1("f11");
182 //case VK_F12: return JvNewStringLatin1("f12");
185 // Convert the character into a CFString, then into a java string
186 CFStringRef string = CFStringCreateWithCharactersNoCopy( NULL, &character, 1, kCFAllocatorNull );
187 assert( string != NULL );
189 jstring str = CFToJavaString( string );
197 // We should never get here
202 static void ModifierKeyTest( org::xwt::plat::Carbon$CarbonSurface* surface, UInt32 changedModifiers, UInt32 currentModifiers, UInt32 keyMask, const char* string )
204 // If the modifier key changed
205 if ( changedModifiers & keyMask ) {
206 jstring str = JvNewStringLatin1( string );
208 // And it is currently down, dispatch a keypressed event
209 if ( currentModifiers & keyMask ) surface->KeyPressed( str );
210 // otherwise dispatch a key released event
211 else surface->KeyReleased( str );
215 // TODO: Do I have to make the event handlers "pascal", or use "NewEventHandlerUPP"?
216 pascal static OSStatus CarbonSurfaceEventHandler( EventHandlerCallRef handler, EventRef event, void* data ) {
217 UInt32 eventClass = GetEventClass( event );
218 UInt32 eventKind = GetEventKind( event );
219 OSStatus result = eventNotHandledErr;
220 org::xwt::plat::Carbon$CarbonSurface* surface = (org::xwt::plat::Carbon$CarbonSurface*)data;
222 //JvSynchronize dummy( surface );
225 case kEventClassWindow:
228 case kEventWindowUpdate:
230 //fprintf( stderr, "window update " );
232 // Create the graphics context
233 result = CreateCGContextForPort(GetWindowPort( (WindowRef) surface->window), (CGContextRef*) &surface->gc);
234 assert( result == noErr && surface->gc != NULL );
236 // Quartz uses the bottom left as its origin. Translate to use the top left.
238 GetWindowBounds( (WindowRef) surface->window, kWindowContentRgn, &bounds );
239 assert( result == noErr );
240 CGContextTranslateCTM( (CGContextRef) surface->gc, 0, bounds.bottom - bounds.top );
241 CGContextScaleCTM( (CGContextRef) surface->gc, 1, -1 );
243 // Convert the context to a CGRect with local coordinates
244 CGRect contentBounds = CGRectMake( 0, 0, bounds.right - bounds.left, bounds.bottom - bounds.top );
246 //PRINT_CGRECT( contentBounds );
248 // Get the invalid region
249 Rect globalInvalidRect;
250 result = GetWindowBounds( (WindowRef) surface->window, kWindowUpdateRgn, &globalInvalidRect );
251 assert( result == noErr );
253 CGRect invalidRect = CGRectMake( globalInvalidRect.left - bounds.left, globalInvalidRect.top - bounds.top, globalInvalidRect.right - globalInvalidRect.left, globalInvalidRect.bottom - globalInvalidRect.top );
255 //invalidRect = CGRectIntersection( invalidRect, contentBounds );
257 //PRINT_CGRECT( invalidRect );
259 BeginUpdate( (WindowRef) surface->window );
261 // Erase the region with white
262 // TODO: Draw the window background instead
263 //result = SetThemeWindowBackground( (WindowRef) surface->window, ThemeBrush inBrush, true );
264 CGContextSetRGBFillColor( (CGContextRef) surface->gc, 1.0, 1.0, 1.0, 1.0 );
265 CGContextFillRect( (CGContextRef) surface->gc, invalidRect );
267 //CGContextSetRGBFillColor( (CGContextRef) surface->gc, red, green, blue, 0.5 );
268 //CGContextFillRect( (CGContextRef) surface->gc, CGRectMake( 0, 0, (bounds.right - bounds.left), (bounds.bottom - bounds.top) ) );
269 // Add the invalid region to XWT's dirty regions
270 surface->Dirty( (jint) invalidRect.origin.x, (jint) invalidRect.origin.y, (jint) invalidRect.size.width, (jint) invalidRect.size.height );
271 // Then tell it to actually do the drawing
272 surface->blitDirtyScreenRegions();
274 EndUpdate( (WindowRef) surface->window );
277 CGContextFlush( (CGContextRef) surface->gc );
278 CGContextRelease( (CGContextRef) surface->gc );
284 case kEventWindowBoundsChanged:
286 // We can't use the event parameter: it is the window bounds, not the content area bounds
287 HIRect currentBounds;
288 result = GetEventParameter( event, kEventParamCurrentBounds, typeHIRect, NULL, sizeof( currentBounds ), NULL, ¤tBounds );
289 assert( result == noErr );
291 // Get the event attributes to determine if size and/or origin have changed
293 result = GetEventParameter( event, kEventParamAttributes, typeUInt32, NULL, sizeof( attributes ), NULL, &attributes );
294 assert( result == noErr );
296 if ( attributes & kWindowBoundsChangeSizeChanged ) {
297 // If maximize is set, unset it
298 if ( attributes & kWindowBoundsChangeUserResize && surface->maximized ) {
299 //fprintf( stderr, "maximized = 0\n" );
300 surface->Maximized( false );
303 //fprintf( stderr, "size changed\n" );
304 surface->SizeChange( (jint) currentBounds.size.width, (jint) currentBounds.size.height );
309 if ( attributes & kWindowBoundsChangeOriginChanged ) {
310 //fprintf( stderr, "origin changed\n" );
311 surface->PosChange( (jint) currentBounds.origin.x, (jint) currentBounds.origin.y );
318 case kEventWindowActivated:
319 case kEventWindowDeactivated:
321 //fprintf( stderr, "focus = %d\n", (eventKind == kEventWindowActivated) );
322 surface->Focused( (eventKind == kEventWindowActivated) );
327 case kEventWindowZoomed:
329 // Toggle maximized whenever we recieve this event
330 //fprintf( stderr, "maximized: %d\n", ! surface->maximized );
331 surface->Maximized( ! surface->maximized );
336 case kEventWindowCollapsed:
338 //fprintf( stderr, "minimized true\n" );
339 surface->Minimized( true );
344 case kEventWindowExpanded:
346 //fprintf( stderr, "minimized false\n" );
347 surface->Minimized( false );
352 case kEventWindowClose:
354 //fprintf( stderr, "close\n" );
360 throw new java::lang::Error(JvNewStringLatin1("Unhandled Window Event"));
364 case kEventClassMouse:
367 result = GetEventParameter( event, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(where), NULL, &where );
368 assert( result == noErr );
370 // Create a hit test event to ask the standard window event handler where this press is
372 result = CreateEvent( NULL, kEventClassWindow, kEventWindowHitTest, 0, kEventAttributeNone, &hitTest );
373 assert( result == noErr );
375 result = SetEventParameter( hitTest, kEventParamMouseLocation, typeHIPoint, sizeof(where), &where );
376 assert( result == noErr );
378 result = SetEventParameter( hitTest, kEventParamDirectObject, typeWindowRef, sizeof(surface->window), &surface->window );
379 assert( result == noErr );
381 result = SendEventToEventTarget( hitTest, GetWindowEventTarget( (WindowRef) surface->window ) );
382 assert( result == noErr );
384 WindowDefPartCode part;
385 result = GetEventParameter( hitTest, kEventParamWindowDefPart, typeWindowDefPartCode, NULL, sizeof(part), NULL, &part );
386 assert( result == noErr );
388 ReleaseEvent( hitTest );
391 // ignore the event if it is not in the content region.
392 if ( part != wInContent )
394 return eventNotHandledErr;
397 // TODO: Can I get the content region's bounds as an HIRect?
399 GetWindowBounds( (WindowRef) surface->window, kWindowContentRgn, &bounds );
400 assert( result == noErr );
401 // Convert the context to a CGRect with local coordinates
402 CGRect contentBounds = CGRectMake( bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top );
404 // Now convert the point to content area relative coordinates:
405 where.x -= contentBounds.origin.x;
406 where.y -= contentBounds.origin.y;
410 case kEventMouseDown:
412 //fprintf( stderr, "mouse down (%f, %f)\n", where.x, where.y );
413 EventMouseButton mouseButton;
414 result = GetEventParameter( event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), NULL, &mouseButton );
415 assert( result == noErr );
417 surface->Press( mouseButton );
418 result = eventNotHandledErr;
424 //fprintf( stderr, "mouse up (%f, %f)\n", where.x, where.y );
425 EventMouseButton mouseButton;
426 result = GetEventParameter( event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), NULL, &mouseButton );
427 assert( result == noErr );
430 result = GetEventParameter( event, kEventParamClickCount, typeUInt32, NULL, sizeof(clickCount), NULL, &clickCount );
431 assert( result == noErr );
433 surface->Release( mouseButton );
435 // deliver click/double click events
436 if ( clickCount == 1 ) {
437 //fprintf( stderr, "click (%f, %f)\n", where.x, where.y );
438 surface->Click( mouseButton );
440 //result = ActivateWindow( (WindowRef) surface->window, true );
441 //assert( result == noErr );
443 //ProcessSerialNumber psn = { 0, kCurrentProcess };
444 //result = SetFrontProcess( &psn );
445 //assert( result == noErr );
446 } else if ( clickCount == 2 ) {
447 //fprintf( stderr, "double click (%f, %f)\n", where.x, where.y );
448 surface->DoubleClick( mouseButton );
455 case kEventMouseMoved:
456 case kEventMouseDragged:
458 surface->Move( (jint) where.x, (jint) where.y );
460 //fprintf( stderr, "mousemoved (%f, %f)\n", where.x, where.y );
464 /*case kEventMouseExited:
466 // Move the mouse to (-1, -1) when it exits the window
467 surface->Move( -1, -1 );
469 fprintf( stderr, "mouse exited\n" );
474 /*case kEventMouseWheelMoved:
476 EventMouseWheelAxis inAxis;
477 result = inEvent.GetParameter<EventMouseWheelAxis>( kEventParamMouseWheelAxis, typeMouseWheelAxis, &inAxis );
478 assert( noErr == result );
481 result = inEvent.GetParameter<SInt32>( kEventParamMouseWheelDelta, typeSInt32, &inDelta );
482 assert( noErr == result );
484 result = MouseWheelMoved( inAxis, inDelta, inKeyModifiers );
490 // some other kind of Mouse event: This is (in my opinion) an error
499 case kEventClassKeyboard:
502 case kEventRawKeyDown:
503 case kEventRawKeyRepeat:
505 // TODO: This breaks international input
506 UniChar character = GetCharacterWithoutModifiers( event );
507 surface->KeyPressed( UniCharToXWTString( character ) );
515 UniChar character = GetCharacterWithoutModifiers( event );
516 surface->KeyReleased( UniCharToXWTString( character ) );
522 case kEventRawKeyModifiersChanged:
524 static UInt32 previousModifiers = 0;
525 UInt32 currentModifiers = GetCurrentEventKeyModifiers();
526 UInt32 changedModifiers = previousModifiers ^ currentModifiers;
528 ModifierKeyTest( surface, changedModifiers, currentModifiers, shiftKey, "shift" );
529 ModifierKeyTest( surface, changedModifiers, currentModifiers, alphaLock, "caps_lock" );
530 ModifierKeyTest( surface, changedModifiers, currentModifiers, controlKey, "control" );
531 ModifierKeyTest( surface, changedModifiers, currentModifiers, optionKey, "alt" );
532 ModifierKeyTest( surface, changedModifiers, currentModifiers, kEventKeyModifierNumLockMask, "num_lock" );
534 previousModifiers = currentModifiers;
546 throw new java::lang::Error(JvNewStringLatin1("Unhandled Window Event"));
552 /** Creates a next layout from string and style. If unicodeBuffer is not null, the caller is responsable for releasing it. */
553 static ATSUTextLayout CreateTextLayout( CFStringRef string, ATSUStyle style, CFStringRef* unicodeBuffer ) {
554 assert( string != NULL && unicodeBuffer != NULL && *unicodeBuffer == NULL && style != NULL );
557 ATSUTextLayout layout;
558 OSStatus result = ATSUCreateTextLayout( &layout );
559 assert( result == noErr && layout != NULL );
561 // Try to get the unicode buffer directly
562 CFIndex unicodeLength = CFStringGetLength( string );
563 assert( unicodeLength != 0 );
564 const UniChar* unicodeString = CFStringGetCharactersPtr( string );
565 if ( unicodeString == NULL )
567 // That failed. Allocate a buffer using CFAllocator so the CFString can own it
568 UniChar* buffer = (UniChar*) CFAllocatorAllocate( NULL, unicodeLength * sizeof( UniChar ), 0 );
569 assert( buffer != NULL );
571 // Get a copy of the text
572 CFStringGetCharacters( string, CFRangeMake( 0, unicodeLength ), buffer );
574 // Create a CFString which wraps and takes ownership of the buffer
575 *unicodeBuffer = CFStringCreateWithCharactersNoCopy( NULL, buffer, unicodeLength, NULL );
576 assert( *unicodeBuffer != NULL );
578 unicodeString = buffer;
581 assert( unicodeString != NULL );
582 //UniChar* unicode_string = malloc( sizeof( UniChar ) * unicode_length );
583 //CFStringGetCharacters( string, CFRangeMake( 0, unicode_length ), unicode_string );
584 ATSUSetTextPointerLocation( layout, unicodeString, kATSUFromTextBeginning, kATSUToTextEnd, unicodeLength );
587 result = ATSUSetRunStyle( layout, style, kATSUFromTextBeginning, kATSUToTextEnd );
588 assert( result == noErr );
590 // Using this setting makes rendering match TextEdit's (except for the antialiasing rules)
591 ATSLineLayoutOptions rendering = kATSLineFractDisable; //kATSLineUseDeviceMetrics;
592 ATSUAttributeTag tag = kATSULineLayoutOptionsTag;
593 ByteCount size = sizeof( rendering );
594 ATSUAttributeValuePtr value = &rendering;
595 result = ATSUSetLayoutControls( layout, 1, &tag, &size, &value );
596 assert( noErr == result );
598 // Allow ATSUI to use its default font fallbacks for Unicode. This means that the font may not
599 // match exactly, but it will display the characters if it can.
600 result = ATSUSetTransientFontMatching( layout, true );
601 assert( noErr == result );
606 static void SetARGBFillColor( CGContextRef gc, jint argb )
608 uint8_t* colour = (uint8_t*) &argb;
609 float alpha = colour[0] / 255.0;
610 float red = colour[1] / 255.0;
611 float green = colour[2] / 255.0;
612 float blue = colour[3] / 255.0;
614 CGContextSetRGBFillColor( gc, red, green, blue, alpha );
617 // CarbonDoubleBuffer //////////////////////////////////////////////////////////////////////
619 void org::xwt::plat::Carbon$CarbonDoubleBuffer::setClip(jint x1, jint y1, jint x2, jint y2) {
620 // Restore the previous graphics state, which should be the initial state as saved in
621 // natInit(). This sets the clipping region to the entire buffer.
622 CGContextRestoreGState( (CGContextRef) gc );
623 // We then immediately save the state again, so the we can reset the clipping area again
624 CGContextSaveGState( (CGContextRef) gc );
626 // Set the clipping area to the rectangle
627 CGContextClipToRect( (CGContextRef) gc, CGRectMake( x1, y1, x2 - x1, y2 - y1 ) );
630 void org::xwt::plat::Carbon$CarbonDoubleBuffer::drawPicture(org::xwt::Picture* s, jint x, jint y) {
631 org::xwt::plat::Carbon$CarbonPicture* source = (org::xwt::plat::Carbon$CarbonPicture*)s;
633 CGRect destination = CGRectMake( x, y, source->width, source->height );
634 HIViewDrawCGImage( (CGContextRef) gc, &destination, (CGImageRef) source->image );
637 void org::xwt::plat::Carbon$CarbonDoubleBuffer::drawPicture(org::xwt::Picture* s,
638 jint dx1, jint dy1, jint dx2, jint dy2, jint sx1, jint sy1, jint sx2, jint sy2) {
639 org::xwt::plat::Carbon$CarbonPicture* source = (org::xwt::plat::Carbon$CarbonPicture*)s;
641 //fprintf( stderr, "drawing stretched picture\n" );
642 //CGContextSetRGBFillColor( (CGContextRef) gc, 1.0, 0.0, 0.0, 1.0 );
643 //CGContextFillRect( (CGContextRef) gc, CGRectMake( dx1, dy1, dx2 - dx1, dy2 - dy1 ) );
646 // Some fancy clipping work is required here: draw only inside of the destination points
647 CGContextSaveGState( (CGContextRef) gc );
648 CGContextClipToRect( (CGContextRef) gc, CGRectMake( dx1, dy1, dx2 - dx1, dy2 - dy1 ) );
650 // Some fancy scaling work is required here as well:
651 // We want to copy a rectangle from the source image to a destination rectangle.
652 // We are clipped to the destination rectangle, so now we must scale the source image
653 // and line it up correctly.
654 float destinationRectWidth = dx2 - dx1;
655 float destinationRectHeight = dy2 - dy1;
656 float sourceRectWidth = sx2 - sx1;
657 float sourceRectHeight = sy2 - sy1;
658 float horizontalScalingFactor = destinationRectWidth / sourceRectWidth;
659 float verticalScalingFactor = destinationRectHeight / sourceRectHeight;
661 // TODO: Work out the math for doing this. For now, no scaling is possible.
662 CGRect destinationRect = CGRectMake( dx1 - sx1 * horizontalScalingFactor, dy1 - sy1 * verticalScalingFactor,
663 source->width * horizontalScalingFactor, source->height * verticalScalingFactor );
664 HIViewDrawCGImage( (CGContextRef) gc, &destinationRect, (CGImageRef) source->image );
666 // Undo the clipping fun
667 CGContextRestoreGState( (CGContextRef) gc );
670 /*void CarbonPictureRelease( void* info, const void* data, size_t size )
672 // TODO: This should release a java reference, which should be
673 // obtained when this is created.
674 // It'll work without it, but that's only by "chance".
677 static CGImageRef CreateCGImageFromData( void* data, int width, int height, CGImageAlphaInfo alphaFormat )
679 // I'm assuming that data is in RGBA format
680 const size_t NUM_COMPONENTS = 4;
681 const size_t BITS_PER_COMPONENT = 8;
682 const size_t BITS_PER_PIXEL = BITS_PER_COMPONENT * NUM_COMPONENTS;
683 const size_t BYTES_PER_PIXEL = BITS_PER_PIXEL / 8;
684 const size_t BYTES_PER_ROW = BYTES_PER_PIXEL * width;
686 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
687 assert( colorSpace != NULL );
689 CGDataProviderRef dataProvider = CGDataProviderCreateWithData( NULL, data, width * height * BYTES_PER_PIXEL, NULL /*CarbonPictureRelease*/ );
690 assert( dataProvider != NULL );
692 CGImageRef image = CGImageCreate ( width, height, BITS_PER_COMPONENT, BITS_PER_PIXEL, BYTES_PER_ROW,
693 colorSpace, alphaFormat, dataProvider, NULL, 1, kCGRenderingIntentDefault );
694 assert( image != NULL );
696 CGDataProviderRelease( dataProvider );
699 CGColorSpaceRelease( colorSpace );
705 void org::xwt::plat::Carbon$CarbonDoubleBuffer::natInit() {
706 // FIRST: We create a CGBitmapContextRef so we can draw on this buffer
707 const int BYTES_PER_PIXEL = 4; // RGBA
708 const int BITS_PER_COMPONENT = 8;
710 // Create a new bitmap context, along with the RAM for the bitmap itself
711 const int bitmapBytesPerRow = (width * BYTES_PER_PIXEL);
712 const int bitmapByteCount = (bitmapBytesPerRow * height);
714 //fprintf( stderr, "alloced %ld bytes for %d x %d double buffer\n", bitmapByteCount, width, height );
716 // create an RGB color space
717 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
718 assert( colorSpace != NULL );
721 bitmapData = (gnu::gcj::RawData*) malloc( bitmapByteCount );
722 assert( bitmapData != NULL );
724 // create the context
725 // TODO: Use kCGImageAlphaPremultipliedLast for better performance
726 gc = (gnu::gcj::RawData*) CGBitmapContextCreate( bitmapData, width, height, BITS_PER_COMPONENT, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst );
727 assert( gc != NULL );
729 // the context retains the color space, so we can release it
730 // OPTIMIZATION: We could pass this colorSpace into CreateCGImageFromData
731 CGColorSpaceRelease( colorSpace );
734 // Clear the double buffer to transparent
735 CGContextClearRect( (CGContextRef) gc, CGRectMake( 0, 0, width, height ) );
736 //CGContextSetRGBFillColor( (CGContextRef) gc, 1.0, 1.0, 1.0, 1.0 );
737 //CGContextFillRect( (CGContextRef) gc, CGRectMake( 0, 0, width, height ) );
739 // Shift the coordinate origin to the top left corner (default = bottom right)
740 CGContextTranslateCTM( (CGContextRef) gc, 0, height );
741 CGContextScaleCTM( (CGContextRef) gc, 1, -1 );
744 // SECOND: We create a CGImageRef that wraps this buffer, so we can copy it to a Surface
745 image = (gnu::gcj::RawData*) CreateCGImageFromData( bitmapData, width, height, kCGImageAlphaPremultipliedFirst );
746 assert( image != NULL );
748 // THIRD: Save the current graphics state so we can set and reset the clipping state
749 // We need to do this because the only way to make the clipping region larger is to
750 // restore a previous graphics state. See setClip.
751 CGContextSaveGState( (CGContextRef) gc );
754 void org::xwt::plat::Carbon$CarbonDoubleBuffer::finalize() {
755 CGContextRelease( (CGContextRef) gc );
756 CGImageRelease( (CGImageRef) image );
761 void org::xwt::plat::Carbon$CarbonSurface::blit(org::xwt::DoubleBuffer* db, jint sx, jint sy, jint dx1, jint dy1, jint dx2, jint dy2) {
762 org::xwt::plat::Carbon$CarbonDoubleBuffer *xdb = (org::xwt::plat::Carbon$CarbonDoubleBuffer*)db;
763 assert( xdb != NULL && xdb->gc != NULL && xdb->image != NULL );
765 // This method is synchronized to prevent crappy threading fun
766 //JvSynchronize dummy(this);
768 /*fprintf( stderr, "reading image data...\n" );
769 uint32_t* data = (uint32_t*) xdb->bitmapData;
770 for ( int i = 0; i < xdb->width * xdb->height; ++ i )
772 uint32_t temp = data[i];
775 fprintf( stderr, "DONE\n" );*/
778 // If we do not have a graphics context we simply mark this area as dirty and return.
779 // We'll get a WindowPaint event later on.
787 // Make the rectangle where we would blit this buffer as invalid
788 OSStatus err = InvalWindowRect( (WindowRef) window, &area );
789 //OSStatus err = QDAddRectToDirtyRegion( GetWindowPort( (WindowRef) window ), &area );
790 assert( err == noErr );
792 //fprintf( stderr, "blit: but marking invalid " );
793 //PRINT_RECT( area );
796 assert( gc != NULL );
798 //fprintf( stderr, "blit\n" );
800 // Make sure that the CGImage is up to date
801 CGContextFlush( (CGContextRef) xdb->gc );
803 // Some fancy clipping work is required here: draw only inside of the destination rectangle
804 CGContextSaveGState( (CGContextRef) gc );
805 CGContextClipToRect( (CGContextRef) gc, CGRectMake( dx1, dy1, dx2 - dx1, dy2 - dy1 ) );
807 // Draw the image on the surface
808 CGRect destination = CGRectMake( dx1 - sx, dy1 - sy, xdb->width, xdb->height );
809 HIViewDrawCGImage( (CGContextRef) gc, &destination, (CGImageRef) xdb->image );
811 // Undo the clipping fun
812 CGContextRestoreGState( (CGContextRef)gc );
815 void org::xwt::plat::Carbon$CarbonDoubleBuffer::fillRect (jint x, jint y, jint x2, jint y2, jint argb) {
816 // Set the Fill color to match
817 SetARGBFillColor( (CGContextRef) gc, argb );
818 CGContextFillRect( (CGContextRef) gc, CGRectMake( x, y, x2 - x, y2 - y ) );
821 void org::xwt::plat::Carbon$CarbonDoubleBuffer::drawString(::java::lang::String* font, ::java::lang::String* text, jint x, jint y, jint argb) {
822 ATSUStyle style = (ATSUStyle) ((org::xwt::plat::Carbon*)org::xwt::plat::Carbon::platform)->_getATSUStyle( font );
823 CFStringRef string = JavaToCFString( text );
825 CFStringRef unicodeBuffer = NULL;
826 ATSUTextLayout layout = CreateTextLayout( string, style, &unicodeBuffer );
828 // Associate the graphics context with the text layout object
829 ATSUAttributeTag tag = kATSUCGContextTag;
830 ByteCount size = sizeof( gc );
831 ATSUAttributeValuePtr value = &gc;
832 OSStatus result = ATSUSetLayoutControls( layout, 1, &tag, &size, &value );
833 assert( result == noErr );
835 // The Quartz RGB fill color influences the ATSUI color
836 SetARGBFillColor( (CGContextRef) gc, argb );
838 // I've flipped the context to "standard" coordinates, but draw text needs it to be flipped back
839 CGContextSaveGState( (CGContextRef) gc );
840 CGContextScaleCTM( (CGContextRef) gc, 1.0, -1.0 );
844 result = ATSUDrawText( layout, kATSUFromTextBeginning, kATSUToTextEnd, X2Fix( x ), X2Fix( y ) );
845 assert( result == noErr );
847 CGContextRestoreGState( (CGContextRef) gc );
849 result = ATSUDisposeTextLayout( layout );
850 assert( result == noErr );
853 if ( unicodeBuffer != NULL ) {
854 CFRelease( unicodeBuffer );
855 unicodeBuffer = NULL;
862 // CarbonSurface //////////////////////////////////////////////////////////////////////
864 void org::xwt::plat::Carbon$CarbonSurface::setIcon(org::xwt::Picture* pic) {
865 org::xwt::plat::Carbon$CarbonPicture *picture = (org::xwt::plat::Carbon$CarbonPicture*)pic;
867 // TODO: This sets the dock icon. Maybe I should set the document icon (the icon beside the window title)?
868 // or set the "window preview" that is shown when a window is collapsed?
869 // TODO: This doesn't seem to work. Maybe it is a size problem?
870 OSStatus result = SetApplicationDockTileImage( (CGImageRef) picture->image );
871 assert( result == noErr );
874 void org::xwt::plat::Carbon$CarbonSurface::setTitleBarText(java::lang::String* s) {
875 CFStringRef string = JavaToCFString( s );
876 OSStatus result = SetWindowTitleWithCFString( (WindowRef) window, string );
877 assert( result == noErr );
883 void org::xwt::plat::Carbon$CarbonSurface::setSize (jint width, jint height) {
885 OSStatus result = GetWindowBounds( (WindowRef) window, kWindowContentRgn, &bounds );
886 assert( result == noErr );
888 bounds.right = bounds.left + width;
889 bounds.bottom = bounds.top + height;
891 result = SetWindowBounds( (WindowRef) window, kWindowContentRgn, &bounds );
892 assert( result == noErr );
893 result = ConstrainWindowToScreen( (WindowRef) window, kWindowContentRgn, kWindowConstrainStandardOptions, NULL, NULL );
894 assert( result == noErr );
896 fprintf( stderr, "set size (%d, %d) (%d, %d)\n", FORMAT_RECT( bounds ) );
897 // TODO: Shouldn't this deliver a "size changed" event automatically?
898 //SizeChange( width, height );
901 void org::xwt::plat::Carbon$CarbonSurface::setLocation (jint x, jint y) {
903 OSStatus result = GetWindowBounds( (WindowRef) window, kWindowStructureRgn, &bounds );
904 assert( result == noErr );
906 int temp = bounds.right - bounds.left;
908 bounds.right = x + temp;
909 temp = bounds.bottom - bounds.top;
911 bounds.bottom = y + temp;
913 result = SetWindowBounds( (WindowRef) window, kWindowStructureRgn, &bounds );
914 assert( result == noErr );
915 result = ConstrainWindowToScreen( (WindowRef) window, kWindowStructureRgn, kWindowConstrainStandardOptions, NULL, NULL );
916 assert( result == noErr );
918 fprintf( stderr, "set location (%d, %d) (%d, %d)\n", FORMAT_RECT( bounds ) );
921 void org::xwt::plat::Carbon$CarbonSurface::natInit (jboolean framed) {
922 // Create a standard window. Maybe I should be using the Appearance Manager ones?
923 OSStatus result = noErr;
930 OSStatus result = CreateNewWindow( kDocumentWindowClass,
931 kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowLiveResizeAttribute,
937 OSStatus result = CreateNewWindow( kPlainWindowClass,
938 kWindowStandardHandlerAttribute | kWindowLiveResizeAttribute,
943 assert( result == noErr && window != NULL );
945 // Install the event handler
946 result = InstallEventHandler( GetWindowEventTarget( (WindowRef) window ),
947 CarbonSurfaceEventHandler,
948 GetEventTypeCount( CarbonSurfaceEventInfo ), CarbonSurfaceEventInfo,
951 assert( result == noErr );
953 // Make sure that this window is NOT visible
954 //HideWindow( (WindowRef) window );
957 void org::xwt::plat::Carbon$CarbonSurface::toFront() {
958 BringToFront( (WindowRef) window );
959 //OSStatus result = ActivateWindow( (WindowRef) window, true );
960 //assert( result == noErr );
962 // Using the default "current process" or actually fetching the current process doesn't work
963 ProcessSerialNumber currentProcess = { 0, kCurrentProcess };
964 OSStatus result = GetCurrentProcess( ¤tProcess );
965 assert( result == noErr );
966 result = SetFrontProcessWithOptions( ¤tProcess, kSetFrontProcessFrontWindowOnly );
967 assert( result == noErr );
970 void org::xwt::plat::Carbon$CarbonSurface::toBack() {
971 SendBehind( (WindowRef) window, NULL );
974 void org::xwt::plat::Carbon$CarbonSurface::syncCursor() {
976 if (cursor->equals(JvNewStringLatin1("crosshair"))) curs = kThemeCrossCursor;
977 //else if (cursor->equals(east)) curs = kThemeArrowCursor;
978 else if (cursor->equals(JvNewStringLatin1("hand"))) curs = kThemePointingHandCursor;
979 //else if (cursor->equals(move)) curs = kThemeArrowCursor;
980 //else if (cursor->equals(north)) curs = kThemeArrowCursor;
981 //else if (cursor->equals(northeast)) curs = kThemeArrowCursor;
982 //else if (cursor->equals(northwest)) curs = kThemeArrowCursor;
983 //else if (cursor->equals(south)) curs = kThemeArrowCursor;
984 //else if (cursor->equals(southeast)) curs = kThemeArrowCursor;
985 //else if (cursor->equals(southwest)) curs = kThemeArrowCursor;
986 else if (cursor->equals(JvNewStringLatin1("text"))) curs = kThemeIBeamCursor;
987 //else if (cursor->equals(west)) curs = kThemeArrowCursor;
988 else if (cursor->equals(JvNewStringLatin1("wait_string"))) curs = kThemeSpinningCursor;
989 else curs = kThemeArrowCursor;
991 // TODO: This should PROBABLY be activated/deactivated when over the window using mouse tracking regions
992 SetThemeCursor( curs );
995 void org::xwt::plat::Carbon$CarbonSurface::_dispose() {
996 ReleaseWindow( (WindowRef) window );
1000 void org::xwt::plat::Carbon$CarbonSurface::setInvisible(jboolean i) {
1001 //fprintf( stderr, "invisible = %d\n", i );
1003 HideWindow( (WindowRef) window );
1005 ShowWindow( (WindowRef) window );
1009 void org::xwt::plat::Carbon$CarbonSurface::_setMaximized(jboolean b) {
1011 // TODO: Probably should use the actual screen size here
1012 ideal.h = CGDisplayPixelsWide( kCGDirectMainDisplay );
1013 ideal.v = CGDisplayPixelsHigh( kCGDirectMainDisplay );
1014 //fprintf( stderr, "maximized = %d\n", b );
1015 OSStatus result = ZoomWindowIdeal( (WindowRef) window, ( b ? inZoomOut : inZoomIn ), &ideal );
1016 // TODO: It seems to work, so why does it return an error? Theory: XWT is not in a bundle.
1017 assert( result == noErr );
1020 void org::xwt::plat::Carbon$CarbonSurface::_setMinimized(jboolean b) {
1021 OSStatus result = CollapseWindow( (WindowRef) window, b );
1022 // TODO: It seems to work, so why does it return an error? Theory: XWT is not in a bundle.
1023 //assert( result == noErr );
1026 void org::xwt::plat::Carbon$CarbonPicture::natInit() {
1027 assert( data->length == width * height );
1028 image = (gnu::gcj::RawData*) CreateCGImageFromData( elements(data), width, height, kCGImageAlphaFirst );
1029 assert( image != NULL );
1032 void org::xwt::plat::Carbon$CarbonPicture::finalize() {
1033 CGImageRelease( (CGImage*) image );
1037 // Carbon ///////////////////////////////////////////////////////////////////
1039 jint org::xwt::plat::Carbon::_getScreenWidth() {
1040 //fprintf( stderr, "screen width = %d\n", CGDisplayPixelsWide( kCGDirectMainDisplay ) );
1041 return CGDisplayPixelsWide( kCGDirectMainDisplay );
1044 jint org::xwt::plat::Carbon::_getScreenHeight() {
1045 //fprintf( stderr, "screen height = %d\n", CGDisplayPixelsHigh( kCGDirectMainDisplay ) );
1046 return CGDisplayPixelsHigh( kCGDirectMainDisplay );
1049 jstring org::xwt::plat::Carbon::_getClipBoard() {
1050 // Get the clipboard reference
1051 ScrapRef scrap = NULL;
1052 OSStatus result = GetCurrentScrap( &scrap );
1053 assert( result == noErr );
1055 ScrapFlavorFlags flavorFlags;
1056 result = GetScrapFlavorFlags ( scrap, kScrapFlavorTypeUnicode, &flavorFlags );
1058 if ( result == noErr ) {
1059 // No error, we have unicode data in a Scrap. Find out how many bytes of data it is.
1061 result = GetScrapFlavorSize( scrap, kScrapFlavorTypeUnicode, &bytes );
1062 assert( result == noErr );
1063 const Size numUniChars = bytes / sizeof( UniChar );
1065 // Allocate a buffer for the text using Core Foundation
1066 UniChar* buffer = reinterpret_cast<UniChar*>( CFAllocatorAllocate( NULL, bytes, 0 ) );
1067 assert( buffer != NULL );
1069 // Get a copy of the text
1070 Size nextBytes = bytes;
1071 result = GetScrapFlavorData( scrap, kScrapFlavorTypeUnicode, &nextBytes, buffer );
1072 assert( result == noErr );
1074 // Create a CFString which wraps and takes ownership of the buffer
1075 CFStringRef string = CFStringCreateWithCharactersNoCopy( NULL, buffer, numUniChars, NULL );
1076 assert( string != NULL );
1077 buffer = NULL; // string now owns this buffer
1079 jstring ret = CFToJavaString( string );
1081 // Default allocator releases both the CFString and the UniChar buffer (text)
1082 CFRelease( string );
1087 return JvNewStringLatin1("");
1091 void org::xwt::plat::Carbon::_setClipBoard(jstring s) {
1092 CFStringRef string = JavaToCFString( s );
1094 OSStatus result = ClearCurrentScrap();
1095 assert( result == noErr );
1097 ScrapRef scrap = NULL;
1098 result = GetCurrentScrap( &scrap );
1099 assert( result == noErr );
1101 CFIndex numUniChars = CFStringGetLength( string );
1103 // Try to get a pointer to the unicode data first, then do the copy if needed
1104 const UniChar* buffer = CFStringGetCharactersPtr( string );
1105 if ( buffer != NULL )
1107 result = PutScrapFlavor ( scrap, kScrapFlavorTypeUnicode, 0, sizeof( UniChar ) * numUniChars, buffer );
1108 assert( result == noErr );
1112 UniChar* buffer = (UniChar*) malloc( sizeof( UniChar ) * numUniChars );
1113 assert( buffer != NULL );
1114 CFStringGetCharacters( string, CFRangeMake( 0, numUniChars ), buffer );
1116 result = PutScrapFlavor ( scrap, kScrapFlavorTypeUnicode, 0, sizeof( UniChar ) * numUniChars, buffer );
1117 assert( result == noErr );
1119 // Done with the UniChar* buffer
1124 // Done with the CFString
1125 CFRelease( string );
1130 JArray<java::lang::String*>* org::xwt::plat::Carbon::listNativeFonts() {
1131 ATSFontFamilyIterator iterator;
1132 OSStatus result = ATSFontFamilyIteratorCreate( kATSFontContextUnspecified, NULL, NULL, kATSOptionFlagsDefault, &iterator );
1133 if ( result != noErr ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
1135 // Iterate over fonts twice: The first time just to count the fonts so we can create the array
1136 // The second time gets the font names
1137 ATSFontFamilyRef font = NULL;
1139 while ( result == noErr )
1142 result = ATSFontFamilyIteratorNext( iterator, &font );
1144 if ( result != kATSIterationCompleted ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
1146 result = ATSFontFamilyIteratorReset( kATSFontContextUnspecified, NULL, NULL, kATSOptionFlagsDefault, &iterator );
1147 if ( result != noErr ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
1149 JArray<jstring>* fonts = (JArray<jstring>*)JvNewObjectArray( i, &(::java::lang::String::class$), NULL);
1151 ATSFontFamilyRef font = NULL;
1152 result = ATSFontFamilyIteratorNext( iterator, &font );
1153 while( result == noErr )
1155 CFStringRef name = NULL;
1156 result = ATSFontFamilyGetName( font, kATSOptionFlagsDefault, &name );
1157 if ( result != noErr || name == NULL ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
1159 //const char* cstring = CFStringGetCStringPtr( name, kCFStringEncodingMacRoman );
1160 //if ( cstring != NULL ) fprintf( stderr, "%s\n", cstring );
1162 jstring xwtName = CFToJavaString( name );
1165 xwtName = xwtName->replace(' ', '_')->toLowerCase()->concat( "0" );
1169 result = ATSFontFamilyIteratorNext( iterator, &font );
1172 if ( result != kATSIterationCompleted ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
1174 result = ATSFontFamilyIteratorRelease ( &iterator );
1175 if ( result != noErr ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
1182 /*gnu::gcj::RawData* org::xwt::plat::Carbon::fontStringToStruct(jstring s) {
1183 throw new java::lang::Error(JvNewStringLatin1("FIXME"));
1186 // HACK: WARNING: Undocumented Hack! This may break in the future
1187 // For now, however, it allows a non-bundled app to create Windows! Yay!
1188 extern "C" { OSErr CPSEnableForegroundOperation(ProcessSerialNumber *psn); }
1190 void org::xwt::plat::Carbon::natInit() {
1191 // HACK: WARNING: Undocumented Hack! This may break in the future
1192 // For now, however, it allows a non-bundled app to create Windows! Yay!
1193 ProcessSerialNumber currentProcess = { 0, kCurrentProcess }; // Using the default "current process" doesn't work
1194 OSStatus result = GetCurrentProcess( ¤tProcess );
1195 assert( result == noErr );
1196 result = CPSEnableForegroundOperation( ¤tProcess );
1197 assert( result == noErr );
1199 // Initiaize the native font hashtable
1200 // Get a copy of all available fonts
1202 result = ATSUFontCount( &numFonts );
1203 assert( result == noErr );
1205 ATSFontRef* fonts = (ATSFontRef*) malloc( sizeof(ATSFontRef) * numFonts );
1206 assert( fonts != NULL );
1208 ItemCount currentNumFonts;
1209 result = ATSUGetFontIDs( fonts, numFonts, ¤tNumFonts );
1210 assert( result == noErr );
1211 numFonts = min( currentNumFonts, numFonts ); // Just to be safe, maybe the number of fonts changed (unlikely!)
1213 const ByteCount MAX_NAME_LENGTH = 100;
1214 char familyName[MAX_NAME_LENGTH+1];
1215 for ( ItemCount i = 0; i < numFonts; i ++ ) {
1216 ByteCount actualBytes = 0;
1217 result = ATSUFindFontName( fonts[i], kFontFamilyName, (FontPlatformCode) kFontMacintoshPlatform, kFontNoScriptCode, kFontNoLanguageCode,
1218 MAX_NAME_LENGTH, familyName, &actualBytes, NULL );
1220 // If we were able to get a macintosh string for the font family name, use it to make a jstring
1221 if ( result == noErr && actualBytes > 0 )
1223 familyName[actualBytes] = '\0';
1225 jstring xwtName = JvNewStringLatin1( familyName, actualBytes );
1227 // Convert the font family name to an XWT font name
1228 xwtName = xwtName->replace(' ', '_')->toLowerCase();
1229 // Map the first font we find to this family name. The first font is the "Regular" font.
1230 if ( nativeFontCache->get( xwtName ) == NULL )
1231 nativeFontCache->put( (java::lang::Object*) xwtName, new org::xwt::plat::Carbon$WrappedRawData( (::gnu::gcj::RawData*) fonts[i] ) );
1235 //fprintf( stderr, "FONT HAS NO MAC NAME\n" );
1243 jint org::xwt::plat::Carbon::_stringWidth(::java::lang::String* font, ::java::lang::String* text) {
1244 // If the string doesn't have any characters, return zero immediately
1245 if ( text->length() == 0 ) return 0;
1247 ATSUStyle style = (ATSUStyle) _getATSUStyle( font );
1248 CFStringRef string = JavaToCFString( text );
1250 CFStringRef unicodeBuffer = NULL;
1251 ATSUTextLayout layout = CreateTextLayout( string, style, &unicodeBuffer );
1253 unsigned long actualNumberOfBounds = 0;
1254 ATSTrapezoid glyphBounds;
1256 // We get a single bound, since the text should only require one. If it requires more, there is an issue
1257 OSStatus result = ATSUGetGlyphBounds( layout, 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &glyphBounds, &actualNumberOfBounds );
1258 assert( result == noErr && actualNumberOfBounds == 1 );
1260 ATSUDisposeTextLayout( layout );
1263 if ( unicodeBuffer != NULL ) {
1264 CFRelease( unicodeBuffer );
1265 unicodeBuffer = NULL;
1268 CFRelease( string );
1271 return Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x );
1274 jint org::xwt::plat::Carbon::_getMaxAscent(::java::lang::String* font) {
1275 ATSUStyle style = (ATSUStyle) _getATSUStyle( font );
1277 ByteCount actualSize = 0;
1278 ATSUTextMeasurement ascent;
1279 OSStatus result = ATSUGetAttribute( style, kATSUAscentTag, sizeof( ascent ), &ascent, &actualSize );
1280 assert( result == kATSUNotSetErr );
1282 //fprintf( stderr, "ascent = %ld\n", Fix2Long( ascent ) );
1284 return Fix2Long( ascent );
1286 jint org::xwt::plat::Carbon::_getMaxDescent(::java::lang::String* font) {
1287 ATSUStyle style = (ATSUStyle) _getATSUStyle( font );
1289 ByteCount actualSize = 0;
1290 ATSUTextMeasurement descent;
1291 OSStatus result = ATSUGetAttribute( style, kATSUDescentTag, sizeof( descent ), &descent, &actualSize );
1292 assert( result == kATSUNotSetErr );
1294 //fprintf( stderr, "descent = %ld\n", Fix2Long( descent ) );
1296 return Fix2Long( descent );
1299 gnu::gcj::RawData* org::xwt::plat::Carbon::_createATSUStyle( gnu::gcj::RawData* fontRef, jint fontSize, jboolean isBold, jboolean isItalic, jboolean isUnderline ) {
1301 OSStatus result = ATSUCreateStyle( &style );
1302 assert( result == noErr && style != NULL );
1305 ATSFontRef font = (ATSFontRef) fontRef;
1306 Fixed size = X2Fix( fontSize );
1307 Boolean bold = isBold;
1308 Boolean italic = isItalic;
1309 Boolean underline = isUnderline;
1311 const ATSUAttributeTag tags[] = { kATSUFontTag, kATSUSizeTag, kATSUQDBoldfaceTag, kATSUQDItalicTag, kATSUQDUnderlineTag };
1312 ByteCount sizes[] = { sizeof( font ), sizeof( size ), sizeof( bold ), sizeof( italic ), sizeof( underline ) };
1313 ATSUAttributeValuePtr values[] = { &font, &size, &bold, &italic, &underline };
1315 result = ATSUSetAttributes( style, sizeof( tags ) / sizeof( *tags ), tags, sizes, values );
1316 assert( result == noErr );
1318 // TODO: Why do I have to turn this off manually? This shouldn't this be the default?
1319 ATSUFontFeatureType featureType = kLigaturesType;
1320 ATSUFontFeatureSelector selector = kCommonLigaturesOffSelector;
1321 result = ATSUSetFontFeatures( style, 1, &featureType, &selector );
1322 assert( result == noErr );
1324 return (gnu::gcj::RawData*) style;
1328 /** Called once XWT is initialized and the application is running. */
1329 void org::xwt::plat::Carbon::_running() {
1330 // TODO: This is going to be a bit magical:
1331 // Let's see what happens when we mix multithreading and OS X applications.
1332 // Theory: Bad stuff may happen if events are delivered and other stuff is going on at
1333 // the same time. However, I'm going to pray that XWT's Java stuff locks things for me,
1334 // So by the time I call into the Java code, i'm the only code path active?
1335 RunApplicationEventLoop();
1337 org::xwt::plat::Carbon::exit();