#include <org/xwt/plat/Carbon$CarbonSurface.h>
#include <org/xwt/plat/Carbon$CarbonPicture.h>
#include <org/xwt/plat/Carbon$CarbonDoubleBuffer.h>
+#include <org/xwt/plat/Carbon$WrappedRawData.h>
#include <java/lang/System.h>
#include <java/lang/Error.h>
#include <java/io/PrintStream.h>
+// Apple's version of GCC automatically defines this symbol.
+// Since XWT is built with a "stock" version of GCC, we need to define
+// this manually to get the system header files to allow us to include them
+#define __APPLE_CPP__ 1
+
+// Standard GCC doesn't support precompiled headers,
+// meaning that it is WAY faster to just include the individual headers
+#include <CoreFoundation/CFString.h>
+#include <HIToolbox/MacWindows.h>
+#include <HIToolbox/MacApplication.h>
+#include <HIToolbox/Scrap.h>
+#include <HIToolbox/Appearance.h>
+#include <HIToolbox/CarbonEvents.h>
+#include <HIToolbox/HIView.h>
+#include <QD/ATSUnicode.h>
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) < (b) ? (b) : (a))
+
+#define FORMAT_RECT( x ) x.left, x.top, x.right, x.bottom
+#define PRINT_RECT( x ) fprintf( stderr, #x " = (%d, %d) (%d, %d)\n", x.left, x.top, x.right, x.bottom )
+#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 );
+
+
+static const EventTypeSpec CarbonSurfaceEventInfo[] = {
+ { kEventClassWindow, kEventWindowUpdate },
+ { kEventClassWindow, kEventWindowBoundsChanged },
+ { kEventClassWindow, kEventWindowActivated },
+ { kEventClassWindow, kEventWindowDeactivated },
+ { kEventClassWindow, kEventWindowClose },
+ { kEventClassWindow, kEventWindowZoomed },
+ { kEventClassWindow, kEventWindowExpanded },
+ { kEventClassWindow, kEventWindowCollapsed },
+
+
+ { kEventClassKeyboard, kEventRawKeyDown },
+ { kEventClassKeyboard, kEventRawKeyRepeat },
+ { kEventClassKeyboard, kEventRawKeyUp },
+ { kEventClassKeyboard, kEventRawKeyModifiersChanged },
+
+
+ { kEventClassMouse, kEventMouseDown },
+ { kEventClassMouse, kEventMouseUp },
+ { kEventClassMouse, kEventMouseMoved },
+ { kEventClassMouse, kEventMouseDragged },
+ { kEventClassMouse, kEventMouseExited },
+
+
+};
+
+static inline UniChar GetCharacterWithoutModifiers( EventRef rawKeyboardEvent )
+{
+ UInt32 keyCode;
+ // Get the key code from the raw key event
+ GetEventParameter( rawKeyboardEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof( keyCode ), NULL, &keyCode );
+
+ // Get the current keyboard layout
+ // TODO: If this is a performance sink, we need to cache these values
+ SInt16 lastKeyLayoutID = GetScriptVariable( /*currentKeyScript*/ GetScriptManagerVariable(smKeyScript), smScriptKeys);
+ Handle uchrHandle = GetResource('uchr', lastKeyLayoutID);
+
+ // Translate the key press ignoring ctrl and option
+ UInt32 ignoredDeadKeys = 0;
+ UInt32 ignoredActualLength = 0;
+ UniChar unicodeKey = 0;
+
+ // We actually want the current shift key value
+ UInt32 modifiers = (GetCurrentEventKeyModifiers() & shiftKey) >> 8;
+ OSStatus err = UCKeyTranslate( reinterpret_cast<UCKeyboardLayout*>( *uchrHandle ), keyCode, kUCKeyActionDown,
+ /* modifierKeyState */ modifiers, LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask, &ignoredDeadKeys,
+ /* buffer length */ 1,
+ /* actual length */ &ignoredActualLength,
+ /* string */ &unicodeKey );
+ assert( err == noErr );
+
+ return unicodeKey;
+}
+
+static CFStringRef JavaToCFString(java::lang::String* s) {
+ int len = min(1024, JvGetStringUTFLength(s));
+
+ char* buffer = (char*) malloc( len );
+ JvGetStringUTFRegion(s, 0, s->length(), buffer);
+
+ // Create a CFString from the UTF8 string
+ CFStringRef string = CFStringCreateWithBytes( NULL, (UInt8*) buffer, len, kCFStringEncodingUTF8, false );
+ assert( string != NULL );
+
+ free( buffer );
+ buffer = NULL;
+
+ return string;
+}
+
+static jstring CFToJavaString( CFStringRef s ) {
+ // TODO: I can probably use CFStringCreateExternalRepresentation
+ // First determine the size of the buffer required
+ const CFRange entireString = CFRangeMake( 0, CFStringGetLength( s ) );
+ CFIndex requiredSize = 0;
+ CFIndex charsConverted = CFStringGetBytes( s, entireString, kCFStringEncodingUTF8, 0, false, NULL, 0, &requiredSize );
+ assert( charsConverted == CFStringGetLength( s ) );
+
+ // Allocate the buffer, plus an additional byte for the null byte
+ UInt8* buffer = (UInt8*) malloc( requiredSize + 1 );
+ assert( buffer != NULL );
+
+ // Convert the string
+ charsConverted = CFStringGetBytes( s, entireString, kCFStringEncodingUTF8, 0, false, buffer, requiredSize, NULL );
+ assert( charsConverted == CFStringGetLength( s ) );
+
+ buffer[requiredSize] = '\0';
+
+ jstring string = JvNewStringUTF( (char*) buffer );
+ assert( string != NULL );
+
+ free( buffer );
+ buffer = NULL;
+
+ return string;
+}
+
+static jstring UniCharToXWTString( UniChar character )
+{
+ switch ( character ) {
+ case '\t': return JvNewStringLatin1("tab");
+ case kEscapeCharCode: return JvNewStringLatin1("escape");
+ case '\n':
+ case kEnterCharCode:
+ case kReturnCharCode:
+ return JvNewStringLatin1("enter");
+ case kBackspaceCharCode: return JvNewStringLatin1("back_space");
+ // escape == clear
+ //case kClearCharCode: return JvNewStringLatin1("clear");
+ //case VK_PAUSE: return JvNewStringLatin1("pause");
+ case kPageUpCharCode: return JvNewStringLatin1("page_up");
+ case kPageDownCharCode: return JvNewStringLatin1("page_down");
+ case kEndCharCode: return JvNewStringLatin1("end");
+ case kHomeCharCode: return JvNewStringLatin1("home");
+ case kLeftArrowCharCode: return JvNewStringLatin1("left");
+ case kUpArrowCharCode: return JvNewStringLatin1("up");
+ case kRightArrowCharCode: return JvNewStringLatin1("right");
+ case kDownArrowCharCode: return JvNewStringLatin1("down");
+ // TODO: Is help always the insert key in the mac world? on my keyboard it is
+ case kHelpCharCode: return JvNewStringLatin1("insert");
+ case kDeleteCharCode: return JvNewStringLatin1("delete");
+ // TODO: How can I determine if a numpad key was pressed?
+ //case VK_NUMPAD0: return JvNewStringLatin1("numpad0");
+ //case VK_NUMPAD1: return JvNewStringLatin1("numpad1");
+ //case VK_NUMPAD2: return JvNewStringLatin1("numpad2");
+ //case VK_NUMPAD3: return JvNewStringLatin1("numpad3");
+ //case VK_NUMPAD4: return JvNewStringLatin1("numpad4");
+ //case VK_NUMPAD5: return JvNewStringLatin1("numpad5");
+ //case VK_NUMPAD6: return JvNewStringLatin1("numpad6");
+ //case VK_NUMPAD7: return JvNewStringLatin1("numpad7");
+ //case VK_NUMPAD8: return JvNewStringLatin1("numpad8");
+ //case VK_NUMPAD9: return JvNewStringLatin1("numpad9");
+ // TODO: How do I get the function key?
+ case kFunctionKeyCharCode: return JvNewStringLatin1("f1");
+ //case VK_F2: return JvNewStringLatin1("f2");
+ //case VK_F3: return JvNewStringLatin1("f3");
+ //case VK_F4: return JvNewStringLatin1("f4");
+ //case VK_F5: return JvNewStringLatin1("f5");
+ //case VK_F6: return JvNewStringLatin1("f6");
+ //case VK_F7: return JvNewStringLatin1("f7");
+ //case VK_F8: return JvNewStringLatin1("f8");
+ //case VK_F9: return JvNewStringLatin1("f9");
+ //case VK_F10: return JvNewStringLatin1("f10");
+ //case VK_F11: return JvNewStringLatin1("f11");
+ //case VK_F12: return JvNewStringLatin1("f12");
+
+ default:
+ // Convert the character into a CFString, then into a java string
+ CFStringRef string = CFStringCreateWithCharactersNoCopy( NULL, &character, 1, kCFAllocatorNull );
+ assert( string != NULL );
+
+ jstring str = CFToJavaString( string );
+
+ CFRelease( string );
+ string = NULL;
+
+ return str;
+ }
+
+ // We should never get here
+ assert( false );
+ return NULL;
+}
+
+static void ModifierKeyTest( org::xwt::plat::Carbon$CarbonSurface* surface, UInt32 changedModifiers, UInt32 currentModifiers, UInt32 keyMask, const char* string )
+{
+ // If the modifier key changed
+ if ( changedModifiers & keyMask ) {
+ jstring str = JvNewStringLatin1( string );
+
+ // And it is currently down, dispatch a keypressed event
+ if ( currentModifiers & keyMask ) surface->KeyPressed( str );
+ // otherwise dispatch a key released event
+ else surface->KeyReleased( str );
+ }
+}
+
+// TODO: Do I have to make the event handlers "pascal", or use "NewEventHandlerUPP"?
+pascal static OSStatus CarbonSurfaceEventHandler( EventHandlerCallRef handler, EventRef event, void* data ) {
+ UInt32 eventClass = GetEventClass( event );
+ UInt32 eventKind = GetEventKind( event );
+ OSStatus result = eventNotHandledErr;
+ org::xwt::plat::Carbon$CarbonSurface* surface = (org::xwt::plat::Carbon$CarbonSurface*)data;
+
+ //JvSynchronize dummy( surface );
+
+ switch(eventClass) {
+ case kEventClassWindow:
+ switch(eventKind)
+ {
+ case kEventWindowUpdate:
+ {
+ //fprintf( stderr, "window update " );
+
+ // Create the graphics context
+ result = CreateCGContextForPort(GetWindowPort( (WindowRef) surface->window), (CGContextRef*) &surface->gc);
+ assert( result == noErr && surface->gc != NULL );
+
+ // Quartz uses the bottom left as its origin. Translate to use the top left.
+ Rect bounds;
+ GetWindowBounds( (WindowRef) surface->window, kWindowContentRgn, &bounds );
+ assert( result == noErr );
+ CGContextTranslateCTM( (CGContextRef) surface->gc, 0, bounds.bottom - bounds.top );
+ CGContextScaleCTM( (CGContextRef) surface->gc, 1, -1 );
+
+ // Convert the context to a CGRect with local coordinates
+ CGRect contentBounds = CGRectMake( 0, 0, bounds.right - bounds.left, bounds.bottom - bounds.top );
+
+ //PRINT_CGRECT( contentBounds );
+
+ // Get the invalid region
+ Rect globalInvalidRect;
+ result = GetWindowBounds( (WindowRef) surface->window, kWindowUpdateRgn, &globalInvalidRect );
+ assert( result == noErr );
+
+ CGRect invalidRect = CGRectMake( globalInvalidRect.left - bounds.left, globalInvalidRect.top - bounds.top, globalInvalidRect.right - globalInvalidRect.left, globalInvalidRect.bottom - globalInvalidRect.top );
+
+ //invalidRect = CGRectIntersection( invalidRect, contentBounds );
+
+ //PRINT_CGRECT( invalidRect );
+
+ BeginUpdate( (WindowRef) surface->window );
+
+ // Erase the region with white
+ // TODO: Draw the window background instead
+ //result = SetThemeWindowBackground( (WindowRef) surface->window, ThemeBrush inBrush, true );
+ CGContextSetRGBFillColor( (CGContextRef) surface->gc, 1.0, 1.0, 1.0, 1.0 );
+ CGContextFillRect( (CGContextRef) surface->gc, invalidRect );
+
+ //CGContextSetRGBFillColor( (CGContextRef) surface->gc, red, green, blue, 0.5 );
+ //CGContextFillRect( (CGContextRef) surface->gc, CGRectMake( 0, 0, (bounds.right - bounds.left), (bounds.bottom - bounds.top) ) );
+ // Add the invalid region to XWT's dirty regions
+ surface->Dirty( (jint) invalidRect.origin.x, (jint) invalidRect.origin.y, (jint) invalidRect.size.width, (jint) invalidRect.size.height );
+ // Then tell it to actually do the drawing
+ surface->blitDirtyScreenRegions();
+
+ EndUpdate( (WindowRef) surface->window );
+
+ // Free the context
+ CGContextFlush( (CGContextRef) surface->gc );
+ CGContextRelease( (CGContextRef) surface->gc );
+ surface->gc = NULL;
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowBoundsChanged:
+ {
+ // We can't use the event parameter: it is the window bounds, not the content area bounds
+ HIRect currentBounds;
+ result = GetEventParameter( event, kEventParamCurrentBounds, typeHIRect, NULL, sizeof( currentBounds ), NULL, ¤tBounds );
+ assert( result == noErr );
+
+ // Get the event attributes to determine if size and/or origin have changed
+ UInt32 attributes;
+ result = GetEventParameter( event, kEventParamAttributes, typeUInt32, NULL, sizeof( attributes ), NULL, &attributes );
+ assert( result == noErr );
+
+ if ( attributes & kWindowBoundsChangeSizeChanged ) {
+ // If maximize is set, unset it
+ if ( attributes & kWindowBoundsChangeUserResize && surface->maximized ) {
+ //fprintf( stderr, "maximized = 0\n" );
+ surface->Maximized( false );
+ }
+
+ //fprintf( stderr, "size changed\n" );
+ surface->SizeChange( (jint) currentBounds.size.width, (jint) currentBounds.size.height );
+
+
+ }
+
+ if ( attributes & kWindowBoundsChangeOriginChanged ) {
+ //fprintf( stderr, "origin changed\n" );
+ surface->PosChange( (jint) currentBounds.origin.x, (jint) currentBounds.origin.y );
+ }
+
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowActivated:
+ case kEventWindowDeactivated:
+ {
+ //fprintf( stderr, "focus = %d\n", (eventKind == kEventWindowActivated) );
+ surface->Focused( (eventKind == kEventWindowActivated) );
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowZoomed:
+ {
+ // Toggle maximized whenever we recieve this event
+ //fprintf( stderr, "maximized: %d\n", ! surface->maximized );
+ surface->Maximized( ! surface->maximized );
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowCollapsed:
+ {
+ //fprintf( stderr, "minimized true\n" );
+ surface->Minimized( true );
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowExpanded:
+ {
+ //fprintf( stderr, "minimized false\n" );
+ surface->Minimized( false );
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowClose:
+ {
+ //fprintf( stderr, "close\n" );
+ surface->Close();
+ result = noErr;
+ break;
+ }
+ default:
+ throw new java::lang::Error(JvNewStringLatin1("Unhandled Window Event"));
+ }
+ break;
+
+ case kEventClassMouse:
+ {
+ HIPoint where;
+ result = GetEventParameter( event, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(where), NULL, &where );
+ assert( result == noErr );
+
+ // Create a hit test event to ask the standard window event handler where this press is
+ EventRef hitTest;
+ result = CreateEvent( NULL, kEventClassWindow, kEventWindowHitTest, 0, kEventAttributeNone, &hitTest );
+ assert( result == noErr );
+
+ result = SetEventParameter( hitTest, kEventParamMouseLocation, typeHIPoint, sizeof(where), &where );
+ assert( result == noErr );
+
+ result = SetEventParameter( hitTest, kEventParamDirectObject, typeWindowRef, sizeof(surface->window), &surface->window );
+ assert( result == noErr );
+
+ result = SendEventToEventTarget( hitTest, GetWindowEventTarget( (WindowRef) surface->window ) );
+ assert( result == noErr );
+
+ WindowDefPartCode part;
+ result = GetEventParameter( hitTest, kEventParamWindowDefPart, typeWindowDefPartCode, NULL, sizeof(part), NULL, &part );
+ assert( result == noErr );
+
+ ReleaseEvent( hitTest );
+ hitTest = NULL;
+
+ // ignore the event if it is not in the content region.
+ if ( part != wInContent )
+ {
+ return eventNotHandledErr;
+ }
+
+ // TODO: Can I get the content region's bounds as an HIRect?
+ Rect bounds;
+ GetWindowBounds( (WindowRef) surface->window, kWindowContentRgn, &bounds );
+ assert( result == noErr );
+ // Convert the context to a CGRect with local coordinates
+ CGRect contentBounds = CGRectMake( bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top );
+
+ // Now convert the point to content area relative coordinates:
+ where.x -= contentBounds.origin.x;
+ where.y -= contentBounds.origin.y;
+
+ switch ( eventKind )
+ {
+ case kEventMouseDown:
+ {
+ //fprintf( stderr, "mouse down (%f, %f)\n", where.x, where.y );
+ EventMouseButton mouseButton;
+ result = GetEventParameter( event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), NULL, &mouseButton );
+ assert( result == noErr );
+
+ surface->Press( mouseButton );
+ result = eventNotHandledErr;
+ break;
+ }
+
+ case kEventMouseUp:
+ {
+ //fprintf( stderr, "mouse up (%f, %f)\n", where.x, where.y );
+ EventMouseButton mouseButton;
+ result = GetEventParameter( event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), NULL, &mouseButton );
+ assert( result == noErr );
+
+ UInt32 clickCount;
+ result = GetEventParameter( event, kEventParamClickCount, typeUInt32, NULL, sizeof(clickCount), NULL, &clickCount );
+ assert( result == noErr );
+
+ surface->Release( mouseButton );
+
+ // deliver click/double click events
+ if ( clickCount == 1 ) {
+ //fprintf( stderr, "click (%f, %f)\n", where.x, where.y );
+ surface->Click( mouseButton );
+
+ //result = ActivateWindow( (WindowRef) surface->window, true );
+ //assert( result == noErr );
+
+ //ProcessSerialNumber psn = { 0, kCurrentProcess };
+ //result = SetFrontProcess( &psn );
+ //assert( result == noErr );
+ } else if ( clickCount == 2 ) {
+ //fprintf( stderr, "double click (%f, %f)\n", where.x, where.y );
+ surface->DoubleClick( mouseButton );
+ }
+
+ result = noErr;
+ break;
+ }
+
+ case kEventMouseMoved:
+ case kEventMouseDragged:
+ {
+ surface->Move( (jint) where.x, (jint) where.y );
+ result = noErr;
+ //fprintf( stderr, "mousemoved (%f, %f)\n", where.x, where.y );
+ break;
+ }
+
+ /*case kEventMouseExited:
+ {
+ // Move the mouse to (-1, -1) when it exits the window
+ surface->Move( -1, -1 );
+ result = noErr;
+ fprintf( stderr, "mouse exited\n" );
+ break;
+ }*/
+
+
+ /*case kEventMouseWheelMoved:
+ {
+ EventMouseWheelAxis inAxis;
+ result = inEvent.GetParameter<EventMouseWheelAxis>( kEventParamMouseWheelAxis, typeMouseWheelAxis, &inAxis );
+ assert( noErr == result );
+
+ SInt32 inDelta;
+ result = inEvent.GetParameter<SInt32>( kEventParamMouseWheelDelta, typeSInt32, &inDelta );
+ assert( noErr == result );
+
+ result = MouseWheelMoved( inAxis, inDelta, inKeyModifiers );
+ break;
+ }
+ */
+
+
+ // some other kind of Mouse event: This is (in my opinion) an error
+ default:
+ assert( false );
+ break;
+ }
+ break;
+ }
+
+
+ case kEventClassKeyboard:
+ switch (eventKind)
+ {
+ case kEventRawKeyDown:
+ case kEventRawKeyRepeat:
+ {
+ // TODO: This breaks international input
+ UniChar character = GetCharacterWithoutModifiers( event );
+ surface->KeyPressed( UniCharToXWTString( character ) );
+
+ result = noErr;
+ break;
+ }
+
+ case kEventRawKeyUp:
+ {
+ UniChar character = GetCharacterWithoutModifiers( event );
+ surface->KeyReleased( UniCharToXWTString( character ) );
+
+ result = noErr;
+ break;
+ }
+
+ case kEventRawKeyModifiersChanged:
+ {
+ static UInt32 previousModifiers = 0;
+ UInt32 currentModifiers = GetCurrentEventKeyModifiers();
+ UInt32 changedModifiers = previousModifiers ^ currentModifiers;
+
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, shiftKey, "shift" );
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, alphaLock, "caps_lock" );
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, controlKey, "control" );
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, optionKey, "alt" );
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, kEventKeyModifierNumLockMask, "num_lock" );
+
+ previousModifiers = currentModifiers;
+ result = noErr;
+ break;
+ }
+
+ default:
+ assert( false );
+ break;
+ }
+ break;
+
+ default:
+ throw new java::lang::Error(JvNewStringLatin1("Unhandled Window Event"));
+ }
+
+ return result;
+}
+
+/** Creates a next layout from string and style. If unicodeBuffer is not null, the caller is responsable for releasing it. */
+static ATSUTextLayout CreateTextLayout( CFStringRef string, ATSUStyle style, CFStringRef* unicodeBuffer ) {
+ assert( string != NULL && unicodeBuffer != NULL && *unicodeBuffer == NULL && style != NULL );
+
+ // Create layout
+ ATSUTextLayout layout;
+ OSStatus result = ATSUCreateTextLayout( &layout );
+ assert( result == noErr && layout != NULL );
+
+ // Try to get the unicode buffer directly
+ CFIndex unicodeLength = CFStringGetLength( string );
+ assert( unicodeLength != 0 );
+ const UniChar* unicodeString = CFStringGetCharactersPtr( string );
+ if ( unicodeString == NULL )
+ {
+ // That failed. Allocate a buffer using CFAllocator so the CFString can own it
+ UniChar* buffer = (UniChar*) CFAllocatorAllocate( NULL, unicodeLength * sizeof( UniChar ), 0 );
+ assert( buffer != NULL );
+
+ // Get a copy of the text
+ CFStringGetCharacters( string, CFRangeMake( 0, unicodeLength ), buffer );
+
+ // Create a CFString which wraps and takes ownership of the buffer
+ *unicodeBuffer = CFStringCreateWithCharactersNoCopy( NULL, buffer, unicodeLength, NULL );
+ assert( *unicodeBuffer != NULL );
+
+ unicodeString = buffer;
+ }
+
+ assert( unicodeString != NULL );
+ //UniChar* unicode_string = malloc( sizeof( UniChar ) * unicode_length );
+ //CFStringGetCharacters( string, CFRangeMake( 0, unicode_length ), unicode_string );
+ ATSUSetTextPointerLocation( layout, unicodeString, kATSUFromTextBeginning, kATSUToTextEnd, unicodeLength );
+
+ // Set the style
+ result = ATSUSetRunStyle( layout, style, kATSUFromTextBeginning, kATSUToTextEnd );
+ assert( result == noErr );
+
+ // Using this setting makes rendering match TextEdit's (except for the antialiasing rules)
+ ATSLineLayoutOptions rendering = kATSLineFractDisable; //kATSLineUseDeviceMetrics;
+ ATSUAttributeTag tag = kATSULineLayoutOptionsTag;
+ ByteCount size = sizeof( rendering );
+ ATSUAttributeValuePtr value = &rendering;
+ result = ATSUSetLayoutControls( layout, 1, &tag, &size, &value );
+ assert( noErr == result );
+
+ // Allow ATSUI to use its default font fallbacks for Unicode. This means that the font may not
+ // match exactly, but it will display the characters if it can.
+ result = ATSUSetTransientFontMatching( layout, true );
+ assert( noErr == result );
+
+ return layout;
+}
+
+static void SetARGBFillColor( CGContextRef gc, jint argb )
+{
+ uint8_t* colour = (uint8_t*) &argb;
+ float alpha = colour[0] / 255.0;
+ float red = colour[1] / 255.0;
+ float green = colour[2] / 255.0;
+ float blue = colour[3] / 255.0;
+
+ CGContextSetRGBFillColor( gc, red, green, blue, alpha );
+}
// CarbonDoubleBuffer //////////////////////////////////////////////////////////////////////
+void org::xwt::plat::Carbon$CarbonDoubleBuffer::setClip(jint x1, jint y1, jint x2, jint y2) {
+ // Restore the previous graphics state, which should be the initial state as saved in
+ // natInit(). This sets the clipping region to the entire buffer.
+ CGContextRestoreGState( (CGContextRef) gc );
+ // We then immediately save the state again, so the we can reset the clipping area again
+ CGContextSaveGState( (CGContextRef) gc );
+
+ // Set the clipping area to the rectangle
+ CGContextClipToRect( (CGContextRef) gc, CGRectMake( x1, y1, x2 - x1, y2 - y1 ) );
+}
+
+void org::xwt::plat::Carbon$CarbonDoubleBuffer::drawPicture(org::xwt::Picture* s, jint x, jint y) {
+ org::xwt::plat::Carbon$CarbonPicture* source = (org::xwt::plat::Carbon$CarbonPicture*)s;
+
+ CGRect destination = CGRectMake( x, y, source->width, source->height );
+ HIViewDrawCGImage( (CGContextRef) gc, &destination, (CGImageRef) source->image );
+}
+
void org::xwt::plat::Carbon$CarbonDoubleBuffer::drawPicture(org::xwt::Picture* s,
jint dx1, jint dy1, jint dx2, jint dy2, jint sx1, jint sy1, jint sx2, jint sy2) {
org::xwt::plat::Carbon$CarbonPicture* source = (org::xwt::plat::Carbon$CarbonPicture*)s;
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+
+ //fprintf( stderr, "drawing stretched picture\n" );
+ //CGContextSetRGBFillColor( (CGContextRef) gc, 1.0, 0.0, 0.0, 1.0 );
+ //CGContextFillRect( (CGContextRef) gc, CGRectMake( dx1, dy1, dx2 - dx1, dy2 - dy1 ) );
+ //return;
+
+ // Some fancy clipping work is required here: draw only inside of the destination points
+ CGContextSaveGState( (CGContextRef) gc );
+ CGContextClipToRect( (CGContextRef) gc, CGRectMake( dx1, dy1, dx2 - dx1, dy2 - dy1 ) );
+
+ // Some fancy scaling work is required here as well:
+ // We want to copy a rectangle from the source image to a destination rectangle.
+ // We are clipped to the destination rectangle, so now we must scale the source image
+ // and line it up correctly.
+ float destinationRectWidth = dx2 - dx1;
+ float destinationRectHeight = dy2 - dy1;
+ float sourceRectWidth = sx2 - sx1;
+ float sourceRectHeight = sy2 - sy1;
+ float horizontalScalingFactor = destinationRectWidth / sourceRectWidth;
+ float verticalScalingFactor = destinationRectHeight / sourceRectHeight;
+
+ // TODO: Work out the math for doing this. For now, no scaling is possible.
+ CGRect destinationRect = CGRectMake( dx1 - sx1 * horizontalScalingFactor, dy1 - sy1 * verticalScalingFactor,
+ source->width * horizontalScalingFactor, source->height * verticalScalingFactor );
+ HIViewDrawCGImage( (CGContextRef) gc, &destinationRect, (CGImageRef) source->image );
+
+ // Undo the clipping fun
+ CGContextRestoreGState( (CGContextRef) gc );
+}
+
+/*void CarbonPictureRelease( void* info, const void* data, size_t size )
+{
+ // TODO: This should release a java reference, which should be
+ // obtained when this is created.
+ // It'll work without it, but that's only by "chance".
+}*/
+
+static CGImageRef CreateCGImageFromData( void* data, int width, int height, CGImageAlphaInfo alphaFormat )
+{
+ // I'm assuming that data is in RGBA format
+ const size_t NUM_COMPONENTS = 4;
+ const size_t BITS_PER_COMPONENT = 8;
+ const size_t BITS_PER_PIXEL = BITS_PER_COMPONENT * NUM_COMPONENTS;
+ const size_t BYTES_PER_PIXEL = BITS_PER_PIXEL / 8;
+ const size_t BYTES_PER_ROW = BYTES_PER_PIXEL * width;
+
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ assert( colorSpace != NULL );
+
+ CGDataProviderRef dataProvider = CGDataProviderCreateWithData( NULL, data, width * height * BYTES_PER_PIXEL, NULL /*CarbonPictureRelease*/ );
+ assert( dataProvider != NULL );
+
+ CGImageRef image = CGImageCreate ( width, height, BITS_PER_COMPONENT, BITS_PER_PIXEL, BYTES_PER_ROW,
+ colorSpace, alphaFormat, dataProvider, NULL, 1, kCGRenderingIntentDefault );
+ assert( image != NULL );
+
+ CGDataProviderRelease( dataProvider );
+ dataProvider = NULL;
+
+ CGColorSpaceRelease( colorSpace );
+ colorSpace = NULL;
+
+ return image;
+}
+
+void org::xwt::plat::Carbon$CarbonDoubleBuffer::natInit() {
+ // FIRST: We create a CGBitmapContextRef so we can draw on this buffer
+ const int BYTES_PER_PIXEL = 4; // RGBA
+ const int BITS_PER_COMPONENT = 8;
+
+ // Create a new bitmap context, along with the RAM for the bitmap itself
+ const int bitmapBytesPerRow = (width * BYTES_PER_PIXEL);
+ const int bitmapByteCount = (bitmapBytesPerRow * height);
+
+ //fprintf( stderr, "alloced %ld bytes for %d x %d double buffer\n", bitmapByteCount, width, height );
+
+ // create an RGB color space
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ assert( colorSpace != NULL );
+
+ // create the bitmap
+ bitmapData = (gnu::gcj::RawData*) malloc( bitmapByteCount );
+ assert( bitmapData != NULL );
+
+ // create the context
+ // TODO: Use kCGImageAlphaPremultipliedLast for better performance
+ gc = (gnu::gcj::RawData*) CGBitmapContextCreate( bitmapData, width, height, BITS_PER_COMPONENT, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst );
+ assert( gc != NULL );
+
+ // the context retains the color space, so we can release it
+ // OPTIMIZATION: We could pass this colorSpace into CreateCGImageFromData
+ CGColorSpaceRelease( colorSpace );
+ colorSpace = NULL;
+
+ // Clear the double buffer to transparent
+ CGContextClearRect( (CGContextRef) gc, CGRectMake( 0, 0, width, height ) );
+ //CGContextSetRGBFillColor( (CGContextRef) gc, 1.0, 1.0, 1.0, 1.0 );
+ //CGContextFillRect( (CGContextRef) gc, CGRectMake( 0, 0, width, height ) );
+
+ // Shift the coordinate origin to the top left corner (default = bottom right)
+ CGContextTranslateCTM( (CGContextRef) gc, 0, height );
+ CGContextScaleCTM( (CGContextRef) gc, 1, -1 );
+
+
+ // SECOND: We create a CGImageRef that wraps this buffer, so we can copy it to a Surface
+ image = (gnu::gcj::RawData*) CreateCGImageFromData( bitmapData, width, height, kCGImageAlphaPremultipliedFirst );
+ assert( image != NULL );
+
+ // THIRD: Save the current graphics state so we can set and reset the clipping state
+ // We need to do this because the only way to make the clipping region larger is to
+ // restore a previous graphics state. See setClip.
+ CGContextSaveGState( (CGContextRef) gc );
}
void org::xwt::plat::Carbon$CarbonDoubleBuffer::finalize() {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ CGContextRelease( (CGContextRef) gc );
+ CGImageRelease( (CGImageRef) image );
+ free( bitmapData );
+ bitmapData = NULL;
}
-void org::xwt::plat::Carbon$CarbonSurface::blit(org::xwt::DoubleBuffer* db, jint sx, jint sy, jint dx, jint dy, jint dx2, jint dy2) {
+void org::xwt::plat::Carbon$CarbonSurface::blit(org::xwt::DoubleBuffer* db, jint sx, jint sy, jint dx1, jint dy1, jint dx2, jint dy2) {
org::xwt::plat::Carbon$CarbonDoubleBuffer *xdb = (org::xwt::plat::Carbon$CarbonDoubleBuffer*)db;
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ assert( xdb != NULL && xdb->gc != NULL && xdb->image != NULL );
+
+ // This method is synchronized to prevent crappy threading fun
+ //JvSynchronize dummy(this);
+
+ /*fprintf( stderr, "reading image data...\n" );
+ uint32_t* data = (uint32_t*) xdb->bitmapData;
+ for ( int i = 0; i < xdb->width * xdb->height; ++ i )
+ {
+ uint32_t temp = data[i];
+ ++ temp;
+ }
+ fprintf( stderr, "DONE\n" );*/
+
+
+ // If we do not have a graphics context we simply mark this area as dirty and return.
+ // We'll get a WindowPaint event later on.
+ if ( gc == NULL )
+ {
+ Rect area;
+ area.left = dx1;
+ area.top = dy1;
+ area.right = dx2;
+ area.bottom = dy2;
+ // Make the rectangle where we would blit this buffer as invalid
+ OSStatus err = InvalWindowRect( (WindowRef) window, &area );
+ //OSStatus err = QDAddRectToDirtyRegion( GetWindowPort( (WindowRef) window ), &area );
+ assert( err == noErr );
+
+ //fprintf( stderr, "blit: but marking invalid " );
+ //PRINT_RECT( area );
+ return;
+ }
+ assert( gc != NULL );
+
+ //fprintf( stderr, "blit\n" );
+
+ // Make sure that the CGImage is up to date
+ CGContextFlush( (CGContextRef) xdb->gc );
+
+ // Some fancy clipping work is required here: draw only inside of the destination rectangle
+ CGContextSaveGState( (CGContextRef) gc );
+ CGContextClipToRect( (CGContextRef) gc, CGRectMake( dx1, dy1, dx2 - dx1, dy2 - dy1 ) );
+
+ // Draw the image on the surface
+ CGRect destination = CGRectMake( dx1 - sx, dy1 - sy, xdb->width, xdb->height );
+ HIViewDrawCGImage( (CGContextRef) gc, &destination, (CGImageRef) xdb->image );
+
+ // Undo the clipping fun
+ CGContextRestoreGState( (CGContextRef)gc );
}
void org::xwt::plat::Carbon$CarbonDoubleBuffer::fillRect (jint x, jint y, jint x2, jint y2, jint argb) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ // Set the Fill color to match
+ SetARGBFillColor( (CGContextRef) gc, argb );
+ CGContextFillRect( (CGContextRef) gc, CGRectMake( x, y, x2 - x, y2 - y ) );
}
void org::xwt::plat::Carbon$CarbonDoubleBuffer::drawString(::java::lang::String* font, ::java::lang::String* text, jint x, jint y, jint argb) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
-}
+ ATSUStyle style = (ATSUStyle) ((org::xwt::plat::Carbon*)org::xwt::plat::Carbon::platform)->_getATSUStyle( font );
+ CFStringRef string = JavaToCFString( text );
+
+ CFStringRef unicodeBuffer = NULL;
+ ATSUTextLayout layout = CreateTextLayout( string, style, &unicodeBuffer );
+
+ // Associate the graphics context with the text layout object
+ ATSUAttributeTag tag = kATSUCGContextTag;
+ ByteCount size = sizeof( gc );
+ ATSUAttributeValuePtr value = &gc;
+ OSStatus result = ATSUSetLayoutControls( layout, 1, &tag, &size, &value );
+ assert( result == noErr );
+
+ // The Quartz RGB fill color influences the ATSUI color
+ SetARGBFillColor( (CGContextRef) gc, argb );
+ // I've flipped the context to "standard" coordinates, but draw text needs it to be flipped back
+ CGContextSaveGState( (CGContextRef) gc );
+ CGContextScaleCTM( (CGContextRef) gc, 1.0, -1.0 );
+ y = -y;
+
+ // Draw text
+ result = ATSUDrawText( layout, kATSUFromTextBeginning, kATSUToTextEnd, X2Fix( x ), X2Fix( y ) );
+ assert( result == noErr );
+
+ CGContextRestoreGState( (CGContextRef) gc );
+
+ result = ATSUDisposeTextLayout( layout );
+ assert( result == noErr );
+ layout = NULL;
+
+ if ( unicodeBuffer != NULL ) {
+ CFRelease( unicodeBuffer );
+ unicodeBuffer = NULL;
+ }
+
+ CFRelease( string );
+ string = NULL;
+}
// CarbonSurface //////////////////////////////////////////////////////////////////////
void org::xwt::plat::Carbon$CarbonSurface::setIcon(org::xwt::Picture* pic) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ org::xwt::plat::Carbon$CarbonPicture *picture = (org::xwt::plat::Carbon$CarbonPicture*)pic;
+
+ // TODO: This sets the dock icon. Maybe I should set the document icon (the icon beside the window title)?
+ // or set the "window preview" that is shown when a window is collapsed?
+ // TODO: This doesn't seem to work. Maybe it is a size problem?
+ OSStatus result = SetApplicationDockTileImage( (CGImageRef) picture->image );
+ assert( result == noErr );
}
void org::xwt::plat::Carbon$CarbonSurface::setTitleBarText(java::lang::String* s) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
-}
+ CFStringRef string = JavaToCFString( s );
+ OSStatus result = SetWindowTitleWithCFString( (WindowRef) window, string );
+ assert( result == noErr );
-void org::xwt::plat::Carbon$CarbonSurface::setLimits(jint minw, jint minh, jint maxw, jint maxh) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ CFRelease( string );
+ string = NULL;
}
void org::xwt::plat::Carbon$CarbonSurface::setSize (jint width, jint height) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ Rect bounds;
+ OSStatus result = GetWindowBounds( (WindowRef) window, kWindowContentRgn, &bounds );
+ assert( result == noErr );
+
+ bounds.right = bounds.left + width;
+ bounds.bottom = bounds.top + height;
+
+ result = SetWindowBounds( (WindowRef) window, kWindowContentRgn, &bounds );
+ assert( result == noErr );
+ result = ConstrainWindowToScreen( (WindowRef) window, kWindowContentRgn, kWindowConstrainStandardOptions, NULL, NULL );
+ assert( result == noErr );
+
+ fprintf( stderr, "set size (%d, %d) (%d, %d)\n", FORMAT_RECT( bounds ) );
+ // TODO: Shouldn't this deliver a "size changed" event automatically?
+ //SizeChange( width, height );
}
void org::xwt::plat::Carbon$CarbonSurface::setLocation (jint x, jint y) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ Rect bounds;
+ OSStatus result = GetWindowBounds( (WindowRef) window, kWindowStructureRgn, &bounds );
+ assert( result == noErr );
+
+ int temp = bounds.right - bounds.left;
+ bounds.left = x;
+ bounds.right = x + temp;
+ temp = bounds.bottom - bounds.top;
+ bounds.top = y;
+ bounds.bottom = y + temp;
+
+ result = SetWindowBounds( (WindowRef) window, kWindowStructureRgn, &bounds );
+ assert( result == noErr );
+ result = ConstrainWindowToScreen( (WindowRef) window, kWindowStructureRgn, kWindowConstrainStandardOptions, NULL, NULL );
+ assert( result == noErr );
+
+ fprintf( stderr, "set location (%d, %d) (%d, %d)\n", FORMAT_RECT( bounds ) );
+}
+
+void org::xwt::plat::Carbon$CarbonSurface::natInit (jboolean framed) {
+ // Create a standard window. Maybe I should be using the Appearance Manager ones?
+ OSStatus result = noErr;
+ Rect bounds;
+ bounds.left = 0;
+ bounds.top = 0;
+ bounds.right = 100;
+ bounds.bottom = 100;
+ if ( framed ) {
+ OSStatus result = CreateNewWindow( kDocumentWindowClass,
+ kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowLiveResizeAttribute,
+ &bounds,
+ (WindowRef*) &window
+ );
+ }
+ else {
+ OSStatus result = CreateNewWindow( kPlainWindowClass,
+ kWindowStandardHandlerAttribute | kWindowLiveResizeAttribute,
+ &bounds,
+ (WindowRef*) &window
+ );
+ }
+ assert( result == noErr && window != NULL );
+
+ // Install the event handler
+ result = InstallEventHandler( GetWindowEventTarget( (WindowRef) window ),
+ CarbonSurfaceEventHandler,
+ GetEventTypeCount( CarbonSurfaceEventInfo ), CarbonSurfaceEventInfo,
+ this,
+ NULL );
+ assert( result == noErr );
+
+ // Make sure that this window is NOT visible
+ //HideWindow( (WindowRef) window );
}
+
void org::xwt::plat::Carbon$CarbonSurface::toFront() {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ BringToFront( (WindowRef) window );
+ //OSStatus result = ActivateWindow( (WindowRef) window, true );
+ //assert( result == noErr );
+
+ // Using the default "current process" or actually fetching the current process doesn't work
+ ProcessSerialNumber currentProcess = { 0, kCurrentProcess };
+ OSStatus result = GetCurrentProcess( ¤tProcess );
+ assert( result == noErr );
+ result = SetFrontProcessWithOptions( ¤tProcess, kSetFrontProcessFrontWindowOnly );
+ assert( result == noErr );
}
+
void org::xwt::plat::Carbon$CarbonSurface::toBack() {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ SendBehind( (WindowRef) window, NULL );
+}
+
+void org::xwt::plat::Carbon$CarbonSurface::syncCursor() {
+ ThemeCursor curs;
+ if (cursor->equals(JvNewStringLatin1("crosshair"))) curs = kThemeCrossCursor;
+ //else if (cursor->equals(east)) curs = kThemeArrowCursor;
+ else if (cursor->equals(JvNewStringLatin1("hand"))) curs = kThemePointingHandCursor;
+ //else if (cursor->equals(move)) curs = kThemeArrowCursor;
+ //else if (cursor->equals(north)) curs = kThemeArrowCursor;
+ //else if (cursor->equals(northeast)) curs = kThemeArrowCursor;
+ //else if (cursor->equals(northwest)) curs = kThemeArrowCursor;
+ //else if (cursor->equals(south)) curs = kThemeArrowCursor;
+ //else if (cursor->equals(southeast)) curs = kThemeArrowCursor;
+ //else if (cursor->equals(southwest)) curs = kThemeArrowCursor;
+ else if (cursor->equals(JvNewStringLatin1("text"))) curs = kThemeIBeamCursor;
+ //else if (cursor->equals(west)) curs = kThemeArrowCursor;
+ else if (cursor->equals(JvNewStringLatin1("wait_string"))) curs = kThemeSpinningCursor;
+ else curs = kThemeArrowCursor;
+
+ // TODO: This should PROBABLY be activated/deactivated when over the window using mouse tracking regions
+ SetThemeCursor( curs );
}
void org::xwt::plat::Carbon$CarbonSurface::_dispose() {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ ReleaseWindow( (WindowRef) window );
+ window = NULL;
}
void org::xwt::plat::Carbon$CarbonSurface::setInvisible(jboolean i) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ //fprintf( stderr, "invisible = %d\n", i );
+ if ( i ) {
+ HideWindow( (WindowRef) window );
+ } else {
+ ShowWindow( (WindowRef) window );
+ }
}
void org::xwt::plat::Carbon$CarbonSurface::_setMaximized(jboolean b) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ Point ideal;
+ // TODO: Probably should use the actual screen size here
+ ideal.h = CGDisplayPixelsWide( kCGDirectMainDisplay );
+ ideal.v = CGDisplayPixelsHigh( kCGDirectMainDisplay );
+ //fprintf( stderr, "maximized = %d\n", b );
+ OSStatus result = ZoomWindowIdeal( (WindowRef) window, ( b ? inZoomOut : inZoomIn ), &ideal );
+ // TODO: It seems to work, so why does it return an error? Theory: XWT is not in a bundle.
+ assert( result == noErr );
}
void org::xwt::plat::Carbon$CarbonSurface::_setMinimized(jboolean b) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ OSStatus result = CollapseWindow( (WindowRef) window, b );
+ // TODO: It seems to work, so why does it return an error? Theory: XWT is not in a bundle.
+ //assert( result == noErr );
}
+void org::xwt::plat::Carbon$CarbonPicture::natInit() {
+ assert( data->length == width * height );
+ image = (gnu::gcj::RawData*) CreateCGImageFromData( elements(data), width, height, kCGImageAlphaFirst );
+ assert( image != NULL );
+}
-
+void org::xwt::plat::Carbon$CarbonPicture::finalize() {
+ CGImageRelease( (CGImage*) image );
+ image = NULL;
+}
// Carbon ///////////////////////////////////////////////////////////////////
jint org::xwt::plat::Carbon::_getScreenWidth() {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ //fprintf( stderr, "screen width = %d\n", CGDisplayPixelsWide( kCGDirectMainDisplay ) );
+ return CGDisplayPixelsWide( kCGDirectMainDisplay );
}
+
jint org::xwt::plat::Carbon::_getScreenHeight() {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ //fprintf( stderr, "screen height = %d\n", CGDisplayPixelsHigh( kCGDirectMainDisplay ) );
+ return CGDisplayPixelsHigh( kCGDirectMainDisplay );
}
jstring org::xwt::plat::Carbon::_getClipBoard() {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ // Get the clipboard reference
+ ScrapRef scrap = NULL;
+ OSStatus result = GetCurrentScrap( &scrap );
+ assert( result == noErr );
+
+ ScrapFlavorFlags flavorFlags;
+ result = GetScrapFlavorFlags ( scrap, kScrapFlavorTypeUnicode, &flavorFlags );
+
+ if ( result == noErr ) {
+ // No error, we have unicode data in a Scrap. Find out how many bytes of data it is.
+ Size bytes = 0;
+ result = GetScrapFlavorSize( scrap, kScrapFlavorTypeUnicode, &bytes );
+ assert( result == noErr );
+ const Size numUniChars = bytes / sizeof( UniChar );
+
+ // Allocate a buffer for the text using Core Foundation
+ UniChar* buffer = reinterpret_cast<UniChar*>( CFAllocatorAllocate( NULL, bytes, 0 ) );
+ assert( buffer != NULL );
+
+ // Get a copy of the text
+ Size nextBytes = bytes;
+ result = GetScrapFlavorData( scrap, kScrapFlavorTypeUnicode, &nextBytes, buffer );
+ assert( result == noErr );
+
+ // Create a CFString which wraps and takes ownership of the buffer
+ CFStringRef string = CFStringCreateWithCharactersNoCopy( NULL, buffer, numUniChars, NULL );
+ assert( string != NULL );
+ buffer = NULL; // string now owns this buffer
+
+ jstring ret = CFToJavaString( string );
+
+ // Default allocator releases both the CFString and the UniChar buffer (text)
+ CFRelease( string );
+ string = NULL;
+
+ return ret;
+ } else {
+ return JvNewStringLatin1("");
+ }
}
void org::xwt::plat::Carbon::_setClipBoard(jstring s) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ CFStringRef string = JavaToCFString( s );
+
+ OSStatus result = ClearCurrentScrap();
+ assert( result == noErr );
+
+ ScrapRef scrap = NULL;
+ result = GetCurrentScrap( &scrap );
+ assert( result == noErr );
+
+ CFIndex numUniChars = CFStringGetLength( string );
+
+ // Try to get a pointer to the unicode data first, then do the copy if needed
+ const UniChar* buffer = CFStringGetCharactersPtr( string );
+ if ( buffer != NULL )
+ {
+ result = PutScrapFlavor ( scrap, kScrapFlavorTypeUnicode, 0, sizeof( UniChar ) * numUniChars, buffer );
+ assert( result == noErr );
+ }
+ else
+ {
+ UniChar* buffer = (UniChar*) malloc( sizeof( UniChar ) * numUniChars );
+ assert( buffer != NULL );
+ CFStringGetCharacters( string, CFRangeMake( 0, numUniChars ), buffer );
+
+ result = PutScrapFlavor ( scrap, kScrapFlavorTypeUnicode, 0, sizeof( UniChar ) * numUniChars, buffer );
+ assert( result == noErr );
+
+ // Done with the UniChar* buffer
+ free( buffer );
+ buffer = NULL;
+ }
+
+ // Done with the CFString
+ CFRelease( string );
+ string = NULL;
}
+/*
JArray<java::lang::String*>* org::xwt::plat::Carbon::listNativeFonts() {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ ATSFontFamilyIterator iterator;
+ OSStatus result = ATSFontFamilyIteratorCreate( kATSFontContextUnspecified, NULL, NULL, kATSOptionFlagsDefault, &iterator );
+ if ( result != noErr ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
+
+ // Iterate over fonts twice: The first time just to count the fonts so we can create the array
+ // The second time gets the font names
+ ATSFontFamilyRef font = NULL;
+ int i = -1;
+ while ( result == noErr )
+ {
+ i ++;
+ result = ATSFontFamilyIteratorNext( iterator, &font );
+ }
+ if ( result != kATSIterationCompleted ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
+
+ result = ATSFontFamilyIteratorReset( kATSFontContextUnspecified, NULL, NULL, kATSOptionFlagsDefault, &iterator );
+ if ( result != noErr ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
+
+ JArray<jstring>* fonts = (JArray<jstring>*)JvNewObjectArray( i, &(::java::lang::String::class$), NULL);
+
+ ATSFontFamilyRef font = NULL;
+ result = ATSFontFamilyIteratorNext( iterator, &font );
+ while( result == noErr )
+ {
+ CFStringRef name = NULL;
+ result = ATSFontFamilyGetName( font, kATSOptionFlagsDefault, &name );
+ if ( result != noErr || name == NULL ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
+
+ //const char* cstring = CFStringGetCStringPtr( name, kCFStringEncodingMacRoman );
+ //if ( cstring != NULL ) fprintf( stderr, "%s\n", cstring );
+
+ jstring xwtName = CFToJavaString( name );
+
+ //
+ xwtName = xwtName->replace(' ', '_')->toLowerCase()->concat( "0" );
+ CFRelease( name );
+ name = NULL;
+
+ result = ATSFontFamilyIteratorNext( iterator, &font );
+ i ++;
+ }
+ if ( result != kATSIterationCompleted ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
+
+ result = ATSFontFamilyIteratorRelease ( &iterator );
+ if ( result != noErr ) throw new java::lang::Error(JvNewStringLatin1("ASSERT"));
+
+ return fonts;
}
+*/
+
-gnu::gcj::RawData* org::xwt::plat::Carbon::fontStringToStruct(jstring s) {
+/*gnu::gcj::RawData* org::xwt::plat::Carbon::fontStringToStruct(jstring s) {
throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+}*/
+
+// HACK: WARNING: Undocumented Hack! This may break in the future
+// For now, however, it allows a non-bundled app to create Windows! Yay!
+extern "C" { OSErr CPSEnableForegroundOperation(ProcessSerialNumber *psn); }
+
+void org::xwt::plat::Carbon::natInit() {
+ // HACK: WARNING: Undocumented Hack! This may break in the future
+ // For now, however, it allows a non-bundled app to create Windows! Yay!
+ ProcessSerialNumber currentProcess = { 0, kCurrentProcess }; // Using the default "current process" doesn't work
+ OSStatus result = GetCurrentProcess( ¤tProcess );
+ assert( result == noErr );
+ result = CPSEnableForegroundOperation( ¤tProcess );
+ assert( result == noErr );
+
+ // Initiaize the native font hashtable
+ // Get a copy of all available fonts
+ ItemCount numFonts;
+ result = ATSUFontCount( &numFonts );
+ assert( result == noErr );
+
+ ATSFontRef* fonts = (ATSFontRef*) malloc( sizeof(ATSFontRef) * numFonts );
+ assert( fonts != NULL );
+
+ ItemCount currentNumFonts;
+ result = ATSUGetFontIDs( fonts, numFonts, ¤tNumFonts );
+ assert( result == noErr );
+ numFonts = min( currentNumFonts, numFonts ); // Just to be safe, maybe the number of fonts changed (unlikely!)
+
+ const ByteCount MAX_NAME_LENGTH = 100;
+ char familyName[MAX_NAME_LENGTH+1];
+ for ( ItemCount i = 0; i < numFonts; i ++ ) {
+ ByteCount actualBytes = 0;
+ result = ATSUFindFontName( fonts[i], kFontFamilyName, (FontPlatformCode) kFontMacintoshPlatform, kFontNoScriptCode, kFontNoLanguageCode,
+ MAX_NAME_LENGTH, familyName, &actualBytes, NULL );
+
+ // If we were able to get a macintosh string for the font family name, use it to make a jstring
+ if ( result == noErr && actualBytes > 0 )
+ {
+ familyName[actualBytes] = '\0';
+
+ jstring xwtName = JvNewStringLatin1( familyName, actualBytes );
+
+ // Convert the font family name to an XWT font name
+ xwtName = xwtName->replace(' ', '_')->toLowerCase();
+ // Map the first font we find to this family name. The first font is the "Regular" font.
+ if ( nativeFontCache->get( xwtName ) == NULL )
+ nativeFontCache->put( (java::lang::Object*) xwtName, new org::xwt::plat::Carbon$WrappedRawData( (::gnu::gcj::RawData*) fonts[i] ) );
+ }
+ else
+ {
+ //fprintf( stderr, "FONT HAS NO MAC NAME\n" );
+ }
+ }
+
+ free( fonts );
+ fonts = NULL;
+}
+
+jint org::xwt::plat::Carbon::_stringWidth(::java::lang::String* font, ::java::lang::String* text) {
+ // If the string doesn't have any characters, return zero immediately
+ if ( text->length() == 0 ) return 0;
+
+ ATSUStyle style = (ATSUStyle) _getATSUStyle( font );
+ CFStringRef string = JavaToCFString( text );
+
+ CFStringRef unicodeBuffer = NULL;
+ ATSUTextLayout layout = CreateTextLayout( string, style, &unicodeBuffer );
+
+ 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
+ OSStatus result = ATSUGetGlyphBounds( layout, 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &glyphBounds, &actualNumberOfBounds );
+ assert( result == noErr && actualNumberOfBounds == 1 );
+
+ ATSUDisposeTextLayout( layout );
+ layout = NULL;
+
+ if ( unicodeBuffer != NULL ) {
+ CFRelease( unicodeBuffer );
+ unicodeBuffer = NULL;
+ }
+
+ CFRelease( string );
+ string = NULL;
+
+ return Fix2Long( glyphBounds.upperRight.x - glyphBounds.upperLeft.x );
}
jint org::xwt::plat::Carbon::_getMaxAscent(::java::lang::String* font) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ ATSUStyle style = (ATSUStyle) _getATSUStyle( font );
+
+ ByteCount actualSize = 0;
+ ATSUTextMeasurement ascent;
+ OSStatus result = ATSUGetAttribute( style, kATSUAscentTag, sizeof( ascent ), &ascent, &actualSize );
+ assert( result == kATSUNotSetErr );
+
+ //fprintf( stderr, "ascent = %ld\n", Fix2Long( ascent ) );
+
+ return Fix2Long( ascent );
}
jint org::xwt::plat::Carbon::_getMaxDescent(::java::lang::String* font) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+ ATSUStyle style = (ATSUStyle) _getATSUStyle( font );
+
+ ByteCount actualSize = 0;
+ ATSUTextMeasurement descent;
+ OSStatus result = ATSUGetAttribute( style, kATSUDescentTag, sizeof( descent ), &descent, &actualSize );
+ assert( result == kATSUNotSetErr );
+
+ //fprintf( stderr, "descent = %ld\n", Fix2Long( descent ) );
+
+ return Fix2Long( descent );
}
-jint org::xwt::plat::Carbon::_stringWidth(::java::lang::String* font, ::java::lang::String* text) {
- throw new java::lang::Error(JvNewStringLatin1("FIXME"));
+
+gnu::gcj::RawData* org::xwt::plat::Carbon::_createATSUStyle( gnu::gcj::RawData* fontRef, jint fontSize, jboolean isBold, jboolean isItalic, jboolean isUnderline ) {
+ ATSUStyle style;
+ OSStatus result = ATSUCreateStyle( &style );
+ assert( result == noErr && style != NULL );
+
+ // Font
+ ATSFontRef font = (ATSFontRef) fontRef;
+ Fixed size = X2Fix( fontSize );
+ Boolean bold = isBold;
+ Boolean italic = isItalic;
+ Boolean underline = isUnderline;
+
+ const ATSUAttributeTag tags[] = { kATSUFontTag, kATSUSizeTag, kATSUQDBoldfaceTag, kATSUQDItalicTag, kATSUQDUnderlineTag };
+ ByteCount sizes[] = { sizeof( font ), sizeof( size ), sizeof( bold ), sizeof( italic ), sizeof( underline ) };
+ ATSUAttributeValuePtr values[] = { &font, &size, &bold, &italic, &underline };
+
+ result = ATSUSetAttributes( style, sizeof( tags ) / sizeof( *tags ), tags, sizes, values );
+ assert( result == noErr );
+
+ // TODO: Why do I have to turn this off manually? This shouldn't this be the default?
+ ATSUFontFeatureType featureType = kLigaturesType;
+ ATSUFontFeatureSelector selector = kCommonLigaturesOffSelector;
+ result = ATSUSetFontFeatures( style, 1, &featureType, &selector );
+ assert( result == noErr );
+
+ return (gnu::gcj::RawData*) style;
}
+
+/** Called once XWT is initialized and the application is running. */
+void org::xwt::plat::Carbon::_running() {
+ // TODO: This is going to be a bit magical:
+ // Let's see what happens when we mix multithreading and OS X applications.
+ // Theory: Bad stuff may happen if events are delivered and other stuff is going on at
+ // the same time. However, I'm going to pray that XWT's Java stuff locks things for me,
+ // So by the time I call into the Java code, i'm the only code path active?
+ RunApplicationEventLoop();
+
+ org::xwt::plat::Carbon::exit();
+}