Use Mac OS-defined character composition method. It allows to handle composed characters of many more languages than before (e.g., Polish, Greek).

git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@7759 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
This commit is contained in:
Manolo Gouy 2010-10-26 21:01:17 +00:00
parent cac2ff3339
commit 901bc31ec3
3 changed files with 83 additions and 268 deletions

View File

@ -37,10 +37,10 @@
This is the FLTK text input widget. It displays a single line
of text and lets the user edit it. Normally it is drawn with an
inset box and a white background. The text may contain any
characters (even 0), and will correctly display anything, using
^X notation for unprintable control characters and \\nnn notation
for unprintable characters with the high bit set. It assumes the
font can draw any characters in the ISO-8859-1 character set.</P>
characters (even 0), and will correctly display any UTF text, using
^X notation for unprintable control characters. It assumes the
font can draw any characters of the used scripts, which is true
for standard fonts under MSWindows and Mac OS X.</P>
<CENTER><TABLE border=1 WIDTH=90% summary="Fl_Input keyboard and mouse bindings.">
@ -114,7 +114,10 @@
also be able to type "dead key" prefix characters. On X you will
actually be able to see what dead key you typed, and if you then move
the cursor without completing the sequence the accent will remain
inserted.</TD></TR>
inserted.
<p>Under Mac OS X, character composition is done as for any Mac application
using Mac OS-defined keystroke series.</TD></TR>
</TABLE></CENTER>
<!-- NEW PAGE -->

View File

@ -875,141 +875,33 @@ static void cocoaMouseHandler(NSEvent *theEvent)
}
/*
* keycode_function for post-10.5 systems, allows more sophisticated decoding of keys
*/
/*
static int keycodeToUnicode(
char * uniChars, int maxChars,
EventKind eKind,
UInt32 keycode, UInt32 modifiers,
UInt32 * deadKeyStatePtr,
unsigned char, // not used in this function
unsigned short) // not used in this function
{
// first get the keyboard mapping in a post 10.2 way
Ptr resource;
TextEncoding encoding;
static TextEncoding lastEncoding = kTextEncodingMacRoman;
int len = 0;
KeyboardLayoutRef currentLayout = NULL;
static KeyboardLayoutRef lastLayout = NULL;
SInt32 currentLayoutId = 0;
static SInt32 lastLayoutId;
int hasLayoutChanged = false;
static Ptr uchr = NULL;
static Ptr KCHR = NULL;
// ScriptCode currentKeyScript;
KLGetCurrentKeyboardLayout(&currentLayout);
if (currentLayout) {
KLGetKeyboardLayoutProperty(currentLayout, kKLIdentifier, (const void**)&currentLayoutId);
if ( (lastLayout != currentLayout) || (lastLayoutId != currentLayoutId) ) {
lastLayout = currentLayout;
lastLayoutId = currentLayoutId;
uchr = NULL;
KCHR = NULL;
if ((KLGetKeyboardLayoutProperty(currentLayout, kKLuchrData, (const void**)&uchr) == noErr) && (uchr != NULL)) {
// done
} else if ((KLGetKeyboardLayoutProperty(currentLayout, kKLKCHRData, (const void**)&KCHR) == noErr) && (KCHR != NULL)) {
// done
}
// FIXME No Layout property found. Now we have a problem.
}
}
if (hasLayoutChanged) {
// deadKeyStateUp = deadKeyStateDown = 0;
if (KCHR != NULL) {
// FIXME this must not happen
} else if (uchr == NULL) {
KCHR = (Ptr) GetScriptManagerVariable(smKCHRCache);
}
}
if (uchr != NULL) {
// this is what I expect
resource = uchr;
} else {
resource = KCHR;
encoding = lastEncoding;
// this is actually not supported by the following code and will likely crash
}
// now apply that keyboard mapping to our keycode
int action;
//OptionBits options = 0;
// not used yet: OptionBits options = kUCKeyTranslateNoDeadKeysMask;
unsigned long keyboardType;
keycode &= 0xFF;
modifiers = (modifiers >> 8) & 0xFF;
keyboardType = LMGetKbdType();
OSStatus status;
UniCharCount actuallength;
UniChar utext[10];
switch(eKind) {
case kEventRawKeyDown: action = kUCKeyActionDown; break;
case kEventRawKeyUp: action = kUCKeyActionUp; break;
case kEventRawKeyRepeat: action = kUCKeyActionAutoKey; break;
default: return 0;
}
UInt32 deadKeyState = *deadKeyStatePtr;
if ((action==kUCKeyActionUp)&&(*deadKeyStatePtr)) {
deadKeyStatePtr = &deadKeyState;
}
status = UCKeyTranslate((const UCKeyboardLayout *) uchr,
keycode, action, modifiers, keyboardType,
0, deadKeyStatePtr,
10, &actuallength, utext);
if (noErr != status) {
fprintf(stderr,"UCKeyTranslate failed: %d\n", (int) status);
actuallength = 0;
}
// convert the list of unicode chars into utf8
// FIXME no bounds check (see maxchars)
unsigned i;
for (i=0; i<actuallength; ++i) {
len += fl_utf8encode(utext[i], uniChars+len);
}
uniChars[len] = 0;
return len;
}
*/
/*
* keycode_function for pre-10.5 systems, this is the "historic" fltk Mac key handling
*/
static int keycode_wrap_old(char * buffer,
int, EventKind, UInt32, // not used in this function
UInt32, UInt32 *, // not used in this function
unsigned char key,
unsigned short sym)
static void calc_e_text(CFStringRef s, char *buffer, size_t len, unsigned sym)
{
if ( (sym >= FL_KP && sym <= FL_KP_Last) || !(sym & 0xff00) ||
sym == FL_Tab || sym == FL_Enter) {
buffer[0] = key;
return 1;
} else {
buffer[0] = 0;
return 0;
int i, no_text_key = false;
static unsigned notext[] = { // keys that don't emit text
FL_BackSpace, FL_Print, FL_Scroll_Lock, FL_Pause,
FL_Insert, FL_Home, FL_Page_Up, FL_Delete, FL_End, FL_Page_Down,
FL_Left, FL_Up, FL_Right, FL_Down,
FL_Menu, FL_Num_Lock, FL_Help
};
int count = sizeof(notext)/sizeof(int);
if (sym > FL_F && sym <= FL_F_Last) no_text_key = true;
else for (i=0; i < count; i++) {
if(notext[i] == sym) {
no_text_key = true;
break;
}
}
} /* keycode_wrap_old */
/*
* Stub pointer to select appropriate keycode_function per operating system version. This function pointer
* is initialised in fl_open_display, based on the runtime identification of the host OS version. This is
* intended to allow us to utilise 10.5 services dynamically to improve Unicode handling, whilst still
* allowing code to run satisfactorily on older systems.
*/
static int (*keycode_function)(char*, int, EventKind, UInt32, UInt32, UInt32*, unsigned char, unsigned short) = keycode_wrap_old;
if ( no_text_key) {
buffer[0] = 0;
} else {
CFStringGetCString(s, buffer, len, kCFStringEncodingUTF8);
}
}
// EXPERIMENTAL!
// this gets called by CJK character palette input
OSStatus carbonTextHandler( EventHandlerCallRef nextHandler, EventRef event, void *unused )
{
@ -1047,44 +939,6 @@ OSStatus carbonTextHandler( EventHandlerCallRef nextHandler, EventRef event, voi
return noErr;
}
static void processCompositionSequence(CFStringRef s, Fl_Window *window)
// composed character sequences are sent here
// they contain 2 unichars, the first comes from the deadkey and can be non ascii (e.g., diaeresis),
// the second is the character to be modified (e.g., e to be accented)
{
// unicodes: non-ascii unicode chars produced by deadkeys
// asciis: corresponding ascii chars expected by Fl::compose()
// diaeresis acute-accent circumflex tilde ring-above
static UniChar unicodes[] = {0xA8, 0xB4, 0x2C6, 0x2DC, 0x2DA };
static char asciis[] = { ':', '\'', '^', '~', '*' };
if (CFStringGetLength(s) == 0) return;
char text[10];
char buffer[10];
UniChar unis[2];
CFStringGetCharacters(s, CFRangeMake(0, 2), unis);
for(unsigned int i = 0; i < sizeof(asciis)/sizeof(char); i++) {
if (unis[0] == unicodes[i]) {
// replace the non-ascii unicode by the corresponding ascii value
unis[0] = (UniChar)asciis[i];
break;
}
}
CFStringRef smod = CFStringCreateWithCharacters(kCFAllocatorDefault, unis, 2);
CFStringGetCString(smod, buffer, sizeof(buffer), kCFStringEncodingUTF8);
CFRelease(smod);
Fl::e_keysym = 0;
Fl::e_state = 0;
Fl::e_length = 1;
Fl::e_text = text;
Fl::e_text[0] = buffer[0];
Fl::handle(FL_KEYBOARD, window);
Fl::e_keysym = 0;
Fl::e_state = 0;
Fl::e_length = 1;
Fl::e_text[0] = buffer[1];
Fl::handle(FL_KEYBOARD, window);
}
/*
* handle cocoa keyboard events
@ -1106,36 +960,41 @@ OSStatus cocoaKeyboardHandler(NSEvent *theEvent)
// get the key code only for key events
UInt32 keyCode = 0, maskedKeyCode = 0;
unichar key = 0;
unsigned char keychar = 0;
unsigned short sym = 0;
keyCode = [theEvent keyCode];
NSString *s = [theEvent characters];
static BOOL compose = NO;
static NSText *edit;
static int countevents;
static CFMutableStringRef sequence; // will contain the two characters of the composition sequence
if ( (mods & NSShiftKeyMask) && (mods & NSCommandKeyMask) ) {
s = [s uppercaseString]; // US keyboards return lowercase letter in s if cmd-shift-key is hit
}
if (compose) { // we are in a composition sequence
// the only benefit of sending events to the NSText object edit is that the deadkey becomes visible
// at its keyUp event; without this, the deadkey remains invisible
if ([s length] == 0) { // occurs if 2 deadkeys are typed successively by error
compose = NO;
[edit setString:@""];
CFRelease(sequence);
fl_unlock_function();
return noErr;
}
[edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
mods_to_e_state( mods ); // process modifier keys
Fl::e_keysym = Fl::e_original_keysym = macKeyLookUp[keyCode];
if([theEvent type] == NSKeyDown) { // keydown of the 2nd key of the compose sequence
[edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
}
else { // keyup of the dead key: there's text in s: display it temporarily
CFStringGetCString((CFStringRef)s, buffer, sizeof(buffer), kCFStringEncodingUTF8);
Fl::e_length = strlen(buffer);
Fl::e_text = buffer;
Fl::handle(FL_KEYBOARD, window);
}
countevents++;
UniChar deadkey;
CFStringGetCharacters((CFStringRef)s, CFRangeMake(0, 1), &deadkey);
CFStringAppendCharacters(sequence, &deadkey, 1);// done for keyUp of deadkey and keyDown of next key
if (countevents >= 3) { // end of composition sequence
processCompositionSequence( sequence, window );
CFRelease(sequence);
// the composed char is now in s, handle it
CFStringGetCString((CFStringRef)s, buffer, sizeof(buffer), kCFStringEncodingUTF8);
Fl::e_length = strlen(buffer);
Fl::e_text = buffer;
Fl::compose_state = 1; // signals the end of a composition sequence
Fl::handle(FL_KEYBOARD, window);
[edit setString:@""]; // clear the content of the edit object
compose=NO; // character composition is now complete
}
@ -1144,23 +1003,13 @@ OSStatus cocoaKeyboardHandler(NSEvent *theEvent)
}
if ([s length] == 0) { // this is a dead key that must be combined with the next key to be pressed
while (window->parent()) window = window->window();
Fl::e_keysym = FL_Control_R; // first simulate pressing of the compose key (FL_Control_R)
Fl::e_text = (char*)"";
Fl::e_length = 0;
Fl::handle(FL_KEYBOARD, window);
compose=YES;
// then send remaining events to an object of type NSText that helps handle character composition sequences
// send remaining keydown events to an object of type NSText that handles character composition sequences
edit = [[theEvent window] fieldEditor:YES forObject:nil];
[edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
[edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
countevents = 1;
sequence = CFStringCreateMutable(NULL, 2);
fl_unlock_function();
return noErr;
} else {
char buff[10];
CFStringGetCString((CFStringRef)s, buff, sizeof(buff), kCFStringEncodingUnicode);
key = *(unichar*)buff;
keychar = buff[0];
}
// extended keyboards can also send sequences on key-up to generate Kanji etc. codes.
// Some observed prefixes are 0x81 to 0x83, followed by an 8 bit keycode.
@ -1187,15 +1036,10 @@ OSStatus cocoaKeyboardHandler(NSEvent *theEvent)
sendEvent = FL_KEYUP;
Fl::e_state &= 0xbfffffff; // clear the deadkey flag
}
mods_to_e_state( mods ); // we process modifier keys at the same time
// if the user pressed alt/option, event_key should have the keycap,
// but event_text should generate the international symbol
mods_to_e_state( mods ); // process modifier keys
sym = macKeyLookUp[maskedKeyCode];
if ( isalpha(key) )
sym = tolower(key);
else if ( Fl::e_state&FL_CTRL && key<32 && sym<0xff00)
sym = key+96;
else if ( Fl::e_state&FL_ALT && sym<0xff00) { // find the keycap of this key
if (sym < 0xff00) { // a "simple" key
// find the result of this key without modifier
NSString *sim = [theEvent charactersIgnoringModifiers];
UniChar one;
CFStringGetCharacters((CFStringRef)sim, CFRangeMake(0, 1), &one);
@ -1206,16 +1050,11 @@ OSStatus cocoaKeyboardHandler(NSEvent *theEvent)
Fl::e_keysym = Fl::e_original_keysym = sym;
// Handle FL_KP_Enter on regular keyboards and on Powerbooks
if ( maskedKeyCode==0x4c || maskedKeyCode==0x34) key=0x0d;
static UInt32 deadKeyState = 0; // must be cleared when losing focus
int l = (*keycode_function)(buffer, 31, kind, keyCode, mods, &deadKeyState, keychar, sym);
if (l > 0) {
CFStringGetCString((CFStringRef)s, buffer, sizeof(buffer), kCFStringEncodingUTF8);
}
if ( maskedKeyCode==0x4c || maskedKeyCode==0x34) s = @"\r";
calc_e_text((CFStringRef)s, buffer, sizeof(buffer), sym);
Fl::e_length = strlen(buffer);
Fl::e_text = buffer;
buffer[Fl::e_length] = 0; // just in case...
}
}
break;
default:
fl_unlock_function();
@ -1232,7 +1071,6 @@ OSStatus cocoaKeyboardHandler(NSEvent *theEvent)
}
/*
* Open callback function to call...
*/
@ -1557,18 +1395,7 @@ void fl_open_display() {
CFRelease(execUrl);
}
keycode_function = keycode_wrap_old; // under Cocoa we always use this one
/* // imm: keycode handler stub setting - use Gestalt to determine the running system version,
* // then set the keycode_function pointer accordingly
* if (MACsystemVersion >= 0x1050) { // 10.5.0 or later
* keycode_function = keycodeToUnicode;
* }
* else {
* keycode_function = keycode_wrap_old; // pre-10.5 mechanism
* }
*/
if ( !bundle )
{
// Earlier versions of this code tried to use weak linking, however it

View File

@ -37,14 +37,7 @@
//#define OLD_DEAD_KEY_CODE
#ifdef __APPLE__
static const char* const compose_pairs =
" ! % # $ y=| & : c a <<~ - r _ * +-2 3 ' u p . , 1 o >>141234? "//00A0 ...
"`A'A^A~A:A*AAE,C`E'E^E:E`I'I^I:I-D~N`O'O^O~O:Ox O/`U'U^U:U'YTHss" //00C0 ...
"`a'a^a~a:a*aae,c`e'e^e:e`i'i^i:i-d~n`o'o^o~o:o-:o/`u'u^u:u'yth:y";//00E0 ...
#else
#ifndef __APPLE__
static const char* const compose_pairs =
"=E _'f _\"..+ ++^ %%^S< OE ^Z ^''^^\"\"^-*- --~ TM^s> oe ^z:Y"
@ -85,6 +78,26 @@ static char dead_keys[] = {
int Fl::compose_state = 0;
#endif
#ifdef __APPLE__
// under Mac OS X character composition is handled by the OS
int Fl::compose(int& del) {
if(Fl::e_length == 0 || Fl::e_keysym == FL_Enter || Fl::e_keysym == FL_KP_Enter ||
Fl::e_keysym == FL_Tab || Fl::e_keysym == FL_Escape || Fl::e_state&FL_META || Fl::e_state&FL_CTRL) {
del = 0;
return 0;
}
if(Fl::compose_state) {
del = 1;
Fl::compose_state = 0;
}
else {
del = 0;
}
return 1;
}
#else
/** Any text editing widget should call this for each FL_KEYBOARD event.
Use of this function is very simple.
@ -114,11 +127,7 @@ int Fl::compose(int& del) {
//
// OSX users sometimes need to hold down ALT for keys, so we only check
// for META on OSX...
#ifdef __APPLE__
if ((e_state & FL_META) && !(ascii & 128)) return 0;
#else
if ((e_state & (FL_ALT|FL_META)) && !(ascii & 128)) return 0;
#endif // __APPLE__
if (compose_state == 1) { // after the compose key
if ( // do not get distracted by any modifier keys
@ -134,15 +143,9 @@ int Fl::compose(int& del) {
) return 0;
if (ascii == ' ') { // space turns into nbsp
#ifdef __APPLE__
int len = fl_utf8encode(0xCA, e_text);
e_text[len] = '\0';
e_length = len;
#else
int len = fl_utf8encode(0xA0, e_text);
e_text[len] = '\0';
e_length = len;
#endif
compose_state = 0;
return 1;
} else if (ascii < ' ' || ascii == 127) {
@ -171,19 +174,6 @@ int Fl::compose(int& del) {
} else if (compose_state) { // second character of compose
char c1 = char(compose_state); // retrieve first character
#ifdef __APPLE__
if ( (c1==0x60 && ascii==0xab) || (c1==0x27 && ascii==0x60)) {
del = 1;
compose_state = '^';
e_text[0] = 0xf6;
return 1;
}
if (ascii==' ') {
del = 0;
compose_state = 0;
return 0;
}
#endif
// now search for the pair in either order:
for (const char *p = compose_pairs; *p; p += 2) {
if (p[0] == ascii && p[1] == c1 || p[1] == ascii && p[0] == c1) {
@ -206,16 +196,7 @@ int Fl::compose(int& del) {
return 1;
}
#ifdef WIN32
#elif (defined __APPLE__)
if (e_state & 0x40000000) {
if (ascii<0x80)
compose_state = ascii;
else
compose_state = compose_pairs[(ascii-0x80)*2];
return 1;
}
#else
#ifndef WIN32
// See if they typed a dead key. This gets it into the same state as
// typing prefix+accent:
if (i >= 0xfe50 && i <= 0xfe5b) {
@ -246,5 +227,9 @@ int Fl::compose(int& del) {
return 0;
}
#endif // __APPLE__
//
// End of "$Id$"
//