From c6689b39ad4b2af85e942933599ccbba1ef9990d Mon Sep 17 00:00:00 2001 From: ejones Date: Fri, 30 Jan 2004 07:00:05 +0000 Subject: [PATCH] 2003/05/08 13:38:40 darcs-hash:20040130070005-dddfd-7a608be1ae96f76ee7e1ffd9b0b17f6bed59125f.gz --- src/org/xwt/Main.java | 5 +- src/org/xwt/Platform.java | 13 +- src/org/xwt/plat/Carbon.cc | 1279 +++++++++++++++++++++++++++++++++++++++++- src/org/xwt/plat/Carbon.java | 136 ++++- src/org/xwt/plat/Win32.java | 8 + 5 files changed, 1391 insertions(+), 50 deletions(-) diff --git a/src/org/xwt/Main.java b/src/org/xwt/Main.java index 4eabbce..7713361 100644 --- a/src/org/xwt/Main.java +++ b/src/org/xwt/Main.java @@ -130,8 +130,9 @@ public class Main extends Applet { }); // gcj-win32 exit()'s when the original thread dies, so we have to deadlock ourselves - if (Log.on) Log.log(Main.class, "main thread blocking on new semaphore"); - new org.xwt.util.Semaphore().block(); + //if (Log.on) Log.log(Main.class, "main thread blocking on new semaphore"); + //new org.xwt.util.Semaphore().block(); + Platform.running(); } catch (Throwable e) { e.printStackTrace(); diff --git a/src/org/xwt/Platform.java b/src/org/xwt/Platform.java index 5bb1687..e2c148c 100644 --- a/src/org/xwt/Platform.java +++ b/src/org/xwt/Platform.java @@ -57,8 +57,8 @@ public class Platform { String os_version = System.getProperty("os.version", ""); String platform_class = null; - //if (os_name.startsWith("Mac OS X")) platform_class = "MacOSX"; - if (vendor.startsWith("Free Software Foundation")) { + if (os_name.startsWith("Darwin")) platform_class = "Carbon"; + else if (vendor.startsWith("Free Software Foundation")) { if (os_name.startsWith("Window")) platform_class = "Win32"; else platform_class = "X11"; } else if (version.startsWith("1.1") && vendor.startsWith("Netscape")) platform_class = "Netscape"; @@ -175,6 +175,9 @@ public class Platform { public Object get() { return o; } }; } + + /** Called once XWT is initialized and the application is running. */ + protected void _running() {} /** quits XWT */ protected void _exit() { @@ -351,6 +354,12 @@ public class Platform { platform._newBrowserWindow(url); } + /** Called once XWT is initialized and the application is running. */ + public static void running() { + Log.log(Platform.class, "XWT is running"); + platform._running(); + } + /** quits XWT */ public static void exit() { Log.log(Platform.class, "exiting via Platform.exit()"); diff --git a/src/org/xwt/plat/Carbon.cc b/src/org/xwt/plat/Carbon.cc index 326392a..92b2dfc 100644 --- a/src/org/xwt/plat/Carbon.cc +++ b/src/org/xwt/plat/Carbon.cc @@ -6,116 +6,1333 @@ #include #include #include +#include #include #include #include +// 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 +#include +#include +#include +#include +#include +#include +#include + +#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( *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( kEventParamMouseWheelAxis, typeMouseWheelAxis, &inAxis ); + assert( noErr == result ); + + SInt32 inDelta; + result = inEvent.GetParameter( 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( 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* 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* fonts = (JArray*)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(); +} diff --git a/src/org/xwt/plat/Carbon.java b/src/org/xwt/plat/Carbon.java index a0d4e01..e7a6125 100644 --- a/src/org/xwt/plat/Carbon.java +++ b/src/org/xwt/plat/Carbon.java @@ -15,31 +15,118 @@ import org.xwt.*; /** Platform implementation for Carbon UI on a POSIX-compliant OS (ie Mac OS X) */ public class Carbon extends POSIX { + /** hashtable of all OS X fonts; key is an XWT font name, value is WrappedRawData which stores an ATSFontRef. + * Initialized by natInit(). */ + static Hashtable nativeFontCache = new Hashtable(); + + /** Cache of ATSUStyle objects; key is an XWT font spec, value is WrappedRawData which stores an ATSUStyle. + * According to an Apple technote, caching the style bjects can really increase performance. */ + static Hashtable atsuStyleCache = new Hashtable(); + + /** List of all XWT font specs. Initialized by init(). */ + static String[] fontList = null; + // General Methods /////////////////////////////////////////////////////// protected String _getAltKeyName() { return "option"; } - protected String[] _listFonts() { throw new Error("FIXME"); } + protected String[] _listFonts() { return fontList; } protected Picture _createPicture(int[] data, int w, int h) { return new CarbonPicture(data, w, h); } protected DoubleBuffer _createDoubleBuffer(int w, int h, Surface owner) { return new CarbonDoubleBuffer(w, h); } protected Surface _createSurface(Box b, boolean framed) { return new CarbonSurface(b, framed); } - protected boolean _needsAutoClick() { throw new Error("FIXME"); } + protected boolean _needsAutoClick() { return false; } protected native int _getScreenWidth(); protected native int _getScreenHeight(); protected native String _getClipBoard(); protected native void _setClipBoard(String s); - protected native int _stringWidth(String font, String text); + static String defaultFontName = "lucida_grande"; + protected String _getDefaultFont() { return defaultFontName + "13"; } + protected native int _stringWidth(String fontSpec, String text); protected native int _getMaxAscent(String font); protected native int _getMaxDescent(String font); - protected boolean _needsAutoDoubleClick() { throw new Error("FIXME"); } + protected boolean _needsAutoDoubleClick() { return false; } + + /** Returns the ATSUStyle associated with the given XWT font spec. + * This method first checks its internal cache before creating the + * ATSUStyle object from scratch. */ + protected RawData _getATSUStyle( String fontSpec ) { + WrappedRawData ret = null; + ret = (WrappedRawData) atsuStyleCache.get( fontSpec ); + if (ret != null) return ret.wrapee; + + Platform.ParsedFont pf = new Platform.ParsedFont( fontSpec ); + + // Find the font + if (pf.name.equals("serif")) pf.name = "lucida_grande"; + else if (pf.name.equals("sansserif")) pf.name = "helvetica"; + else if (pf.name.equals("monospace")) pf.name = "courier"; + else if (pf.name.equals("dialog")) pf.name = "lucida_grande"; + else if (pf.name.equals("tty")) pf.name = "courier"; + + // Find the ATSFontRef + WrappedRawData fontRef = (WrappedRawData) nativeFontCache.get( pf.name ); + // If we couldn't find the font, use the default font + if ( fontRef == null ) fontRef = (WrappedRawData) nativeFontCache.get( defaultFontName ); + if ( fontRef == null ) throw new Error( "Default font cannot be found" ); + + // Create the ATSUStyle object + ret = new WrappedRawData( _createATSUStyle( fontRef.wrapee, pf.size, pf.bold, pf.italic, pf.underline ) ); + + // Map this font spec to the ATSFontRef to optimize future requests + atsuStyleCache.put( fontSpec, ret ); + + return ret.wrapee; + } + + /** Creates an ATSUStyle object with the specified attributes. */ + protected native RawData _createATSUStyle( RawData fontRef, int fontSize, boolean isBold, boolean isItalic, boolean isUnderline ); + + /** Called once XWT is initialized and the application is running. On Mac OS X this calls + * RunApplicationEventLoop(). */ + protected native void _running(); + + /** dumps a list of Mac OS X font strings. TODO: Will this be sufficient? */ + //private native String[] listNativeFonts(); + /** translates a font string into an ATSUFontRef? TODO: Will this be sufficient? */ + //public static native gnu.gcj.RawData fontStringToStruct(String s); public Carbon() { } - public void init() { throw new Error("FIXME"); } + public void init() { + natInit(); + + // nativeFontCache contains font NAMES. Each font exists as an outline font + // which can be any size, plus can have real or simulated bold or italic + fontList = new String[nativeFontCache.size()*4]; + Enumeration e = nativeFontCache.keys(); + for(int i=0; e.hasMoreElements(); i+=4) { + String fontName = (String)e.nextElement() + "0"; + + fontList[i] = fontName; + fontList[i+1] = fontName + "i"; + fontList[i+2] = fontName + "b"; + fontList[i+3] = fontName + "bi"; + } + + // Make sure that the default font exists + if ( _getATSUStyle( _getDefaultFont() ) == null ) throw new Error( "Default font does not exist" ); + } + private native void natInit(); + + /** so we can put ATSUStyles and ATSFontRefs into Hashtables */ + private static class WrappedRawData { + public RawData wrapee = null; + public WrappedRawData(RawData r) { wrapee = r; } + } // CarbonSurface ///////////////////////////////////////////////////// /** Implements a Surface as an Carbon Window */ public static class CarbonSurface extends Surface { + + /** The WindowRef that implements this Surface. */ + gnu.gcj.RawData window = null; + /** The CGContextRef. TODO: How do we get this??? */ + gnu.gcj.RawData gc = null; public native void setInvisible(boolean i); public native void _setMaximized(boolean m); @@ -48,15 +135,15 @@ public class Carbon extends POSIX { public native void setTitleBarText(String s); public native void setSize(int w, int h); public native void setLocation(int x, int y); - public native void natInit(); + public native void natInit(boolean framed); public native void toFront(); public native void toBack(); public native void syncCursor(); public native void _dispose(); - public native void setLimits(int minw, int minh, int maxw, int maxh); + //public native void setLimits(int minw, int minh, int maxw, int maxh); public native void blit(DoubleBuffer s, int sx, int sy, int dx, int dy, int dx2, int dy2); - public CarbonSurface(Box root, boolean framed) { super(root); throw new Error("FIXME"); } + public CarbonSurface(Box root, boolean framed) { super(root); natInit( framed ); } } @@ -65,36 +152,55 @@ public class Carbon extends POSIX { /** Implements a Picture */ public static class CarbonPicture extends Picture { - int width; int height; int[] data = null; + + /** A CGImageRef of the picture. */ + RawData image = null; + public int getWidth() { return width; } public int getHeight() { return height; } + public native void natInit(); + public native void finalize(); + public CarbonPicture(int[] data, int w, int h) { this.data = data; this.width = w; this.height = h; - throw new Error("FIXME"); + natInit(); } } /** A Carbon DoubleBuffer */ public static class CarbonDoubleBuffer extends DoubleBuffer { - int width; int height; + + /** A pointer to the raw bitmap data. */ + RawData bitmapData; + /** A CGBitmapContextRef. */ + RawData gc; + /** A CGImageRef which represents the CGBitmapContext. */ + RawData image; + public int getWidth() { return width; } public int getHeight() { return height; } - public CarbonDoubleBuffer(int w, int h) { this.width = w; this.height = h; throw new Error("FIXME"); } - public void setClip(int x, int y, int x2, int y2) { throw new Error("FIXME"); } - public void drawPicture(Picture source, int x, int y) { throw new Error("FIXME"); } - public void drawPicture(Picture source, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2) { throw new Error("FIXME"); } + public CarbonDoubleBuffer(int w, int h) { + this.width = w; + this.height = h; + natInit(); + } + + public native void setClip(int x, int y, int x2, int y2); + public native void drawPicture(Picture source, int x, int y); + public native void drawPicture(Picture source, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2); public native void fillRect(int x, int y, int x2, int y2, int color); public native void drawString(String font, String text, int x, int y, int color); + public native void natInit(); public native void finalize(); } diff --git a/src/org/xwt/plat/Win32.java b/src/org/xwt/plat/Win32.java index a6551df..36652fd 100644 --- a/src/org/xwt/plat/Win32.java +++ b/src/org/xwt/plat/Win32.java @@ -84,6 +84,14 @@ public class Win32 extends GCJ { int maxDescent; } + /** Called once XWT is initialized and the application is running. On Win32, we need to block the main thread + * on a semaphore because if the main thread exits, the whole application quits. */ + protected void _running() { + // gcj-win32 exit()'s when the original thread dies, so we have to deadlock ourselves + if (Log.on) Log.log(Main.class, "main thread blocking on new semaphore"); + new org.xwt.util.Semaphore().block(); + } + /** takes a parsed font and finds the closest platform-specific font */ static native Win32Font mapFont(Platform.ParsedFont pf); -- 1.7.10.4