diff -ur fltk-1.3.0r9619.org/configure.in fltk-1.3.0r9619/configure.in --- fltk-1.3.0r9619.org/configure.in 2012-04-22 04:45:09.000000000 +0200 +++ fltk-1.3.0r9619/configure.in 2012-06-18 13:47:33.290447462 +0200 @@ -865,6 +865,8 @@ Darwin*) # MacOS X uses Cocoa for graphics. LIBS="$LIBS -framework Cocoa" + # And some Carbon for keyboard handling + LIBS="$LIBS -framework Carbon" if test x$have_pthread = xyes; then AC_DEFINE(HAVE_PTHREAD) diff -ur fltk-1.3.0r9619.org/src/Fl_cocoa.mm fltk-1.3.0r9619/src/Fl_cocoa.mm --- fltk-1.3.0r9619.org/src/Fl_cocoa.mm 2012-06-16 10:49:52.000000000 +0200 +++ fltk-1.3.0r9619/src/Fl_cocoa.mm 2012-06-18 13:47:42.944910782 +0200 @@ -53,6 +53,7 @@ #include #import +#import #ifndef NSINTEGER_DEFINED // appears with 10.5 in NSObjCRuntime.h #if defined(__LP64__) && __LP64__ @@ -114,6 +115,8 @@ extern Fl_Window* fl_xmousewin; #endif +bool use_simple_keyboard = false; + enum { FLTKTimerEvent = 1, FLTKDataReadyEvent }; @@ -130,6 +133,39 @@ { } +// Undocumented voodoo. Taken from Mozilla. +#define ENABLE_ROMAN_KYBDS_ONLY -23 + +void fl_update_focus(void) +{ + Fl_Widget *focus; + + focus = Fl::grab(); + if (!focus) + focus = Fl::focus(); + if (!focus) + return; + + if (focus->simple_keyboard()) + use_simple_keyboard = true; + else + use_simple_keyboard = false; + + // Force a "Roman" or "ASCII" keyboard, which both the Mozilla and + // Safari people seem to think implies turning off advanced IME stuff + // (see nsTSMManager::SyncKeyScript in Mozilla and enableSecureTextInput + // in Safari/Webcore). Should be good enough for us then... +#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); + TSMSetDocumentProperty(TSMGetActiveDocument(), + kTSMDocumentEnabledInputSourcesPropertyTag, + sizeof(CFArrayRef), &inputSources); + CFRelease(inputSources); +#else + KeyScript(use_simple_keyboard ? ENABLE_ROMAN_KYBDS_ONLY : smKeyEnableKybds); +#endif +} + /* * Mac keyboard lookup table */ @@ -908,6 +944,25 @@ } @end +static const char* cocoaDead2FLTK(const char *in) +{ + if (strcmp(in, "\140") == 0) // GRAVE ACCENT + return "\314\200"; // COMBINING GRAVE ACCENT + if (strcmp(in, "\302\264") == 0) // ACUTE ACCENT + return "\314\201"; // COMBINING ACUTE ACCENT + if (strcmp(in, "\136") == 0) // CIRCUMFLEX ACCENT + return "\314\202"; // COMBINING CIRCUMFLEX ACCENT + if (strcmp(in, "\176") == 0) // TILDE + return "\314\203"; // COMBINING TILDE + if (strcmp(in, "\302\250") == 0) // DIAERESIS + return "\314\210"; // COMBINING DIAERESIS + // FIXME: OS X dead key behaviour isn't documented and I don't have + // any more keyboards to test with... + + // hope that OS X gave us something proper to begin with + return in; +} + /* Handle cocoa keyboard events Events during a character composition sequence: @@ -1648,6 +1703,7 @@ - (void)rightMouseDragged:(NSEvent *)theEvent; - (void)otherMouseDragged:(NSEvent *)theEvent; - (void)scrollWheel:(NSEvent *)theEvent; ++ (NSString *)keyTranslate:(UInt16)keyCode withModifierFlags:(UInt32)modifierFlags; - (BOOL)handleKeyDown:(NSEvent *)theEvent; - (void)keyDown:(NSEvent *)theEvent; - (void)keyUp:(NSEvent *)theEvent; @@ -1726,6 +1782,130 @@ - (void)scrollWheel:(NSEvent *)theEvent { cocoaMouseWheelHandler(theEvent); } ++ (NSString *)keyTranslate:(UInt16)keyCode withModifierFlags:(UInt32)modifierFlags { + const UCKeyboardLayout *layout; + OSStatus err; + + layout = NULL; + +#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + TISInputSourceRef keyboard; + CFDataRef uchr; + + keyboard = TISCopyCurrentKeyboardInputSource(); + uchr = (CFDataRef)TISGetInputSourceProperty(keyboard, + kTISPropertyUnicodeKeyLayoutData); + if (uchr == NULL) + return nil; + + layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); +#else + KeyboardLayoutRef old_layout; + int kind; + + err = KLGetCurrentKeyboardLayout(&old_layout); + if (err != noErr) + return nil; + + err = KLGetKeyboardLayoutProperty(old_layout, kKLKind, + (const void**)&kind); + if (err != noErr) + return nil; + + // Old, crufty layout format? + if (kind == kKLKCHRKind) { + void *kchr_layout; + + UInt32 chars, state; + char buf[3]; + + unichar result[16]; + ByteCount in_len, out_len; + + err = KLGetKeyboardLayoutProperty(old_layout, kKLKCHRData, + (const void**)&kchr_layout); + if (err != noErr) + return nil; + + state = 0; + + keyCode &= 0x7f; + modifierFlags &= 0xff00; + + chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state); + + buf[0] = (chars >> 16) & 0xff; + buf[1] = chars & 0xff; + buf[2] = '\0'; + + if (buf[0] == '\0') { + buf[0] = buf[1]; + buf[1] = '\0'; + } + + // The data is now in some layout specific encoding. Need to convert + // this to unicode. + + ScriptCode script; + TextEncoding encoding; + TECObjectRef converter; + + script = (ScriptCode)GetScriptManagerVariable(smKeyScript); + + err = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare, + kTextRegionDontCare, NULL, + &encoding); + if (err != noErr) + return nil; + + err = TECCreateConverter(&converter, encoding, kTextEncodingUnicodeV4_0); + if (err != noErr) + return nil; + + in_len = strlen(buf); + out_len = sizeof(result); + + err = TECConvertText(converter, (ConstTextPtr)buf, in_len, &in_len, + (TextPtr)result, out_len, &out_len); + + TECDisposeConverter(converter); + + if (err != noErr) + return nil; + + return [NSString stringWithCharacters:result + length:(out_len / sizeof(unichar))]; + } + + if ((kind != kKLKCHRuchrKind) && (kind != kKLuchrKind)) + return nil; + + err = KLGetKeyboardLayoutProperty(old_layout, kKLuchrData, + (const void**)&layout); + if (err != noErr) + return nil; +#endif + + if (layout == NULL) + return nil; + + UInt32 dead_state; + UniCharCount max_len, actual_len; + UniChar string[255]; + + dead_state = 0; + max_len = sizeof(string)/sizeof(*string); + + modifierFlags = (modifierFlags >> 8) & 0xff; + + err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags, + LMGetKbdType(), 0, &dead_state, max_len, &actual_len, + string); + if (err != noErr) + return nil; + + return [NSString stringWithCharacters:string length:actual_len]; +} - (BOOL)handleKeyDown:(NSEvent *)theEvent { //NSLog(@"handleKeyDown"); fl_lock_function(); @@ -1752,14 +1932,47 @@ break; } } - if (!no_text_key && !(Fl::e_state & FL_META) ) { - // Don't send cmd- to interpretKeyEvents because it beeps. + if (!no_text_key) { + // The simple keyboard model will ignore insertText, so we need to grab + // the symbol directly from the event. Note that we still use setMarkedText. + if (use_simple_keyboard) { + NSString *simple_chars; + UInt32 modifiers; + + // We want a "normal" symbol out of the event, which basically means + // we only respect the shift and alt/altgr modifiers. Cocoa can help + // us if we only wanted shift, but as we also want alt/altgr, we'll + // have to do some lookup ourselves. This matches our behaviour on + // other platforms. + + modifiers = 0; + if ([theEvent modifierFlags] & NSAlphaShiftKeyMask) + modifiers |= alphaLock; + if ([theEvent modifierFlags] & NSShiftKeyMask) + modifiers |= shiftKey; + if ([theEvent modifierFlags] & NSAlternateKeyMask) + modifiers |= optionKey; + + simple_chars = [FLView keyTranslate:[theEvent keyCode] + withModifierFlags:modifiers]; + if (simple_chars == nil) { + // Something went wrong. Fall back to what Cocoa gave us... + simple_chars = [theEvent charactersIgnoringModifiers]; + } + + [FLView prepareEtext:simple_chars]; + } + // Then we can let the OS have a stab at it and see if it thinks it // should result in some text - NSText *edit = [[theEvent window] fieldEditor:YES forObject:nil]; - in_key_event = true; - [edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; - in_key_event = false; + + // Don't send cmd- to interpretKeyEvents because it beeps. + if (!(Fl::e_state & FL_META)) { + NSText *edit = [[theEvent window] fieldEditor:YES forObject:nil]; + in_key_event = true; + [edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; + in_key_event = false; + } } //NSLog(@"to text=%@ l=%d", [NSString stringWithUTF8String:Fl::e_text], Fl::e_length); int handled = Fl::handle(FL_KEYDOWN, window); @@ -1937,21 +2150,30 @@ //NSLog(@"insertText: received=%@",received); if (!in_key_event) fl_lock_function(); + + // Simple keyboard widgets do not want these side channel inputs. + if (use_simple_keyboard) + goto end; + [FLView prepareEtext:received]; + // We can get called outside of key events (e.g. from the character - // palette). Transform such actions to FL_PASTE events. + // palette). We need to fake our own key event at that point. if (!in_key_event) { Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; - Fl::handle(FL_PASTE, target); + Fl::e_keysym = Fl::e_original_keysym = 0; + Fl::handle(FL_KEYDOWN, target); // for some reason, the window does not redraw until the next mouse move or button push // sending a 'redraw()' or 'awake()' does not solve the issue! Fl::flush(); } + +end: if (!in_key_event) fl_unlock_function(); } - (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection { - NSString *received; + NSString *received, *current, *aggregate; if (newSelection.location == 0) { [self unmarkText]; return; @@ -1962,11 +2184,47 @@ received = (NSString*)aString; } //NSLog(@"setMarkedText: %@ %d %d",received,newSelection.location,newSelection.length); + + fl_lock_function(); + + // Simple keyboard widgets generally do not want these side channel + // inputs, but we have no other way of getting dead keys so we make + // an exception in that case. + if (use_simple_keyboard) { + if (in_key_event && (Fl::e_length == 0)) { + [FLView prepareEtext:received]; + + Fl::e_text = (char*)cocoaDead2FLTK(Fl::e_text); + Fl::e_length = strlen(Fl::e_text); + } + goto end; + } + // This code creates the OS X behaviour of seeing dead keys as things // are being composed. + // + // Note: The concatenation thing is because of how OS X deals with + // invalid sequences. At that point it will spit out one call + // to insertText with the now aborted sequence, and one new + // call to setMarkedText with the new sequence. Since we want + // both to be visible, we need to concatenate. next_compose_length = newSelection.location; - [FLView prepareEtext:received]; - //NSLog(@"Fl::e_text=%@ Fl::e_length=%d next_compose_length=%d", received, Fl::e_length, next_compose_length); + current = [NSString stringWithUTF8String:Fl::e_text]; + aggregate = [current stringByAppendingString:received]; + + [FLView prepareEtext:aggregate]; + //NSLog(@"Fl::e_text=%@ Fl::e_length=%d next_compose_length=%d", aggregate, Fl::e_length, next_compose_length); + + // We can get called outside of key events (e.g. from the character + // palette). We need to fake our own key event at that point. + if (!in_key_event) { + Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; + Fl::e_keysym = Fl::e_original_keysym = 0; + Fl::handle(FL_KEYDOWN, target); + } + +end: + fl_unlock_function(); } - (void)unmarkText {