From 083df11eea2599a5abdb6a213faa2991a5e6ada1 Mon Sep 17 00:00:00 2001 From: Kevin Walzer Date: Sun, 29 Jan 2012 16:36:28 +0000 Subject: Fix for serious bugs with input methods, and for display of certain fonts in buttons; thanks to Adrian Robert for extensive patches --- macosx/tkMacOSXButton.c | 9 + macosx/tkMacOSXKeyEvent.c | 573 +++++++++++++++++++++++++++++++++++++--------- macosx/tkMacOSXKeyboard.c | 6 +- macosx/tkMacOSXMenu.c | 6 + macosx/tkMacOSXPrivate.h | 7 +- 5 files changed, 494 insertions(+), 107 deletions(-) diff --git a/macosx/tkMacOSXButton.c b/macosx/tkMacOSXButton.c index 8eb3a1f..036624d 100644 --- a/macosx/tkMacOSXButton.c +++ b/macosx/tkMacOSXButton.c @@ -576,6 +576,15 @@ ComputeNativeButtonGeometry( [button setImagePosition:pos]; } + // if font is too tall, we can't use the fixed-height rounded bezel + if (!haveImage && haveText && style == NSRoundedBezelStyle) { + Tk_FontMetrics fm; + Tk_GetFontMetrics(butPtr->tkfont, &fm); + if (fm.linespace > 18) { + [button setBezelStyle:(style = NSShadowlessSquareBezelStyle)]; + } + } + bounds.size = [cell cellSize]; if (haveText) { titleRect = [cell titleRectForBounds:bounds]; diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index b3b016e..1d24960 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -6,6 +6,7 @@ * * Copyright 2001-2009, Apple Inc. * Copyright (c) 2006-2009 Daniel A. Steffen + * Copyright (c) 2012 Adrian Robert. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -19,6 +20,8 @@ #define TK_MAC_DEBUG_KEYBOARD #endif */ +#define NS_KEYLOG 0 + static Tk_Window grabWinPtr = NULL; /* Current grab window, NULL if no grab. */ @@ -26,9 +29,19 @@ static Tk_Window keyboardGrabWinPtr = NULL; /* Current keyboard grab window. */ static NSModalSession modalSession = NULL; +static BOOL processingCompose = NO; +static BOOL finishedCompose = NO; + +static int caret_x = 0, caret_y = 0, caret_height = 0; + +static void setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state); +static unsigned isFunctionKey(unsigned int code); + + #pragma mark TKApplication(TKKeyEvent) @implementation TKApplication(TKKeyEvent) + - (NSEvent *) tkProcessKeyEvent: (NSEvent *) theEvent { #ifdef TK_MAC_DEBUG_EVENTS @@ -36,24 +49,38 @@ static NSModalSession modalSession = NULL; #endif NSWindow* w; NSEventType type = [theEvent type]; - NSUInteger modifiers, len; + NSUInteger modifiers, len = 0; BOOL repeat = NO; unsigned short keyCode; NSString *characters = nil, *charactersIgnoringModifiers = nil; static NSUInteger savedModifiers = 0; + static NSMutableArray *nsEvArray; + + if (nsEvArray == nil) + { + nsEvArray = [[NSMutableArray alloc] initWithCapacity: 1]; + processingCompose = NO; + } switch (type) { case NSKeyUp: + if (finishedCompose) + { + // if we were composing, swallow the last release since we already sent + finishedCompose = NO; + return theEvent; + } case NSKeyDown: repeat = [theEvent isARepeat]; characters = [theEvent characters]; charactersIgnoringModifiers = [theEvent charactersIgnoringModifiers]; + len = [charactersIgnoringModifiers length]; case NSFlagsChanged: modifiers = [theEvent modifierFlags]; keyCode = [theEvent keyCode]; w = [self windowWithWindowNumber:[theEvent windowNumber]]; -#ifdef TK_MAC_DEBUG_EVENTS - TKLog(@"-[%@(%p) %s] %d %u %@ %@ %u %@", [self class], self, _cmd, repeat, modifiers, characters, charactersIgnoringModifiers, keyCode, w); +#if defined(TK_MAC_DEBUG_EVENTS) || NS_KEYLOG == 1 + NSLog(@"-[%@(%p) %s] r=%d mods=%u '%@' '%@' code=%u c=%d %@ %d", [self class], self, _cmd, repeat, modifiers, characters, charactersIgnoringModifiers, keyCode,([charactersIgnoringModifiers length] == 0) ? 0 : [charactersIgnoringModifiers characterAtIndex: 0], w, type); #endif break; @@ -61,122 +88,353 @@ static NSModalSession modalSession = NULL; return theEvent; } - unsigned int state = 0; + if (!processingCompose) { + unsigned int state = 0; - if (modifiers & NSAlphaShiftKeyMask) { - state |= LockMask; - } - if (modifiers & NSShiftKeyMask) { - state |= ShiftMask; - } - if (modifiers & NSControlKeyMask) { - state |= ControlMask; - } - if (modifiers & NSCommandKeyMask) { - state |= Mod1Mask; /* command key */ - } - if (modifiers & NSAlternateKeyMask) { - state |= Mod2Mask; /* option key */ - } - if (modifiers & NSNumericPadKeyMask) { - state |= Mod3Mask; - } - if (modifiers & NSFunctionKeyMask) { - state |= Mod4Mask; - } + if (modifiers & NSAlphaShiftKeyMask) { + state |= LockMask; + } + if (modifiers & NSShiftKeyMask) { + state |= ShiftMask; + } + if (modifiers & NSControlKeyMask) { + state |= ControlMask; + } + if (modifiers & NSCommandKeyMask) { + state |= Mod1Mask; /* command key */ + } + if (modifiers & NSAlternateKeyMask) { + state |= Mod2Mask; /* option key */ + } + if (modifiers & NSNumericPadKeyMask) { + state |= Mod3Mask; + } + if (modifiers & NSFunctionKeyMask) { + state |= Mod4Mask; + } - /* - * The focus must be in the FrontWindow on the Macintosh. We then query Tk - * to determine the exact Tk window that owns the focus. - */ + /* + * The focus must be in the FrontWindow on the Macintosh. We then query Tk + * to determine the exact Tk window that owns the focus. + */ - TkWindow *winPtr = TkMacOSXGetTkWindow(w); - Tk_Window tkwin = (Tk_Window) winPtr; + TkWindow *winPtr = TkMacOSXGetTkWindow(w); + Tk_Window tkwin = (Tk_Window) winPtr; - if (!tkwin) { - TkMacOSXDbgMsg("tkwin == NULL"); - return theEvent; - } - tkwin = (Tk_Window) winPtr->dispPtr->focusPtr; - if (!tkwin) { - TkMacOSXDbgMsg("tkwin == NULL"); - return theEvent; + if (!tkwin) { + TkMacOSXDbgMsg("tkwin == NULL"); + return theEvent; + } + tkwin = (Tk_Window) winPtr->dispPtr->focusPtr; + if (!tkwin) { + TkMacOSXDbgMsg("tkwin == NULL"); + return theEvent; + } + + /* + * If it's a function key, or we have modifiers other than Shift or Alt, + * pass it straight to Tk. Otherwise we'll send for input processing. + */ + int code = (len == 0) ? + 0 : [charactersIgnoringModifiers characterAtIndex: 0]; + if (type != NSKeyDown || isFunctionKey(code) + || (len > 0 && state & (ControlMask | Mod1Mask | Mod3Mask | Mod4Mask))) { + + XEvent xEvent; + setupXEvent(&xEvent, w, state); + + if (type == NSFlagsChanged) { + if (savedModifiers > modifiers) { + xEvent.xany.type = KeyRelease; + } else { + xEvent.xany.type = KeyPress; + } + + /* + * Use special '-1' to signify a special keycode to our platform + * specific code in tkMacOSXKeyboard.c. This is rather like what + * happens on Windows. + */ + + xEvent.xany.send_event = -1; + + /* + * Set keycode (which was zero) to the changed modifier + */ + + xEvent.xkey.keycode = (modifiers ^ savedModifiers); + } else { + if (type == NSKeyUp || repeat) { + xEvent.xany.type = KeyRelease; + } else { + xEvent.xany.type = KeyPress; + } + + /* For command key, take input manager's word so things + like dvorak / qwerty layout work. */ + if ((modifiers & NSCommandKeyMask) == NSCommandKeyMask + && (modifiers & NSAlternateKeyMask) != NSAlternateKeyMask + && len > 0 && !isFunctionKey(code)) { + // head off keycode-based translation in tkMacOSXKeyboard.c + xEvent.xkey.nbytes = [characters length]; //len + } + + if ([characters length] > 0) { + xEvent.xkey.keycode = + (keyCode << 16) | (UInt16) [characters characterAtIndex:0]; + if (![characters getCString:xEvent.xkey.trans_chars + maxLength:XMaxTransChars encoding:NSUTF8StringEncoding]) { + /* prevent SF bug 2907388 (crash on some composite chars) */ + //PENDING: we might not need this anymore + TkMacOSXDbgMsg("characters too long"); + return theEvent; + } + } + + if (repeat) { + Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); + xEvent.xany.type = KeyPress; + xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); + } + } + Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); + savedModifiers = modifiers; + return theEvent; + } /* if send straight to TK */ + + } /* if not processing compose */ + + if (type == NSKeyDown) { + if (NS_KEYLOG) + fprintf (stderr, "keyDown: %s compose sequence.\n", + processingCompose == YES ? "Continue" : "Begin"); + processingCompose = YES; + [nsEvArray addObject: theEvent]; + [[w contentView] interpretKeyEvents: nsEvArray]; + [nsEvArray removeObject: theEvent]; + } + + savedModifiers = modifiers; + + return theEvent; +} +@end + + + +@implementation TKContentView(TKKeyEvent) +/* implementation (called through interpretKeyEvents:]). */ + +/* : called when done composing; + NOTE: also called when we delete over working text, followed immed. + by doCommandBySelector: deleteBackward: */ +- (void)insertText: (id)aString +{ + int i, len = [(NSString *)aString length]; + XEvent xEvent; + TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); + Tk_Window tkwin = (Tk_Window) winPtr; + + if (NS_KEYLOG) + NSLog (@"insertText '%@'\tlen = %d", aString, len); + processingCompose = NO; + finishedCompose = YES; + + /* first, clear any working text */ + if (_workingText != nil) + [self deleteWorkingText]; + + /* now insert the string as keystrokes */ + setupXEvent(&xEvent, [self window], 0); + xEvent.xany.type = KeyPress; + + for (i =0; i modifiers) { - xEvent.xany.type = KeyRelease; - } else { - xEvent.xany.type = KeyPress; - } - /* - * Use special '-1' to signify a special keycode to our platform - * specific code in tkMacOSXKeyboard.c. This is rather like what - * happens on Windows. - */ +/* : inserts display of composing characters */ +- (void)setMarkedText: (id)aString selectedRange: (NSRange)selRange +{ + NSString *str = [aString respondsToSelector: @selector (string)] ? + [aString string] : aString; + if (NS_KEYLOG) + NSLog (@"setMarkedText '%@' len =%d range %d from %d", str, [str length], + selRange.length, selRange.location); - xEvent.xany.send_event = -1; + if (_workingText != nil) + [self deleteWorkingText]; + if ([str length] == 0) + return; - /* - * Set keycode (which was zero) to the changed modifier - */ + processingCompose = YES; + _workingText = [str copy]; - xEvent.xkey.keycode = (modifiers ^ savedModifiers); - } else { - if (type == NSKeyUp || repeat) { - xEvent.xany.type = KeyRelease; - } else { - xEvent.xany.type = KeyPress; - } + //PENDING: insert workingText underlined +} -/* prevent SF bug 2907388 here (crash on composite characters) */ -if ([characters length] > 0) { - xEvent.xkey.keycode = (keyCode << 16) | (UInt16) - [characters characterAtIndex:0]; - if (![characters getCString:xEvent.xkey.trans_chars - maxLength:XMaxTransChars encoding:NSUTF8StringEncoding]) { - TkMacOSXDbgMsg("characters too long"); - return theEvent; - } + +/* delete display of composing characters [not in ] */ +- (void)deleteWorkingText +{ + if (_workingText == nil) + return; + if (NS_KEYLOG) + NSLog(@"deleteWorkingText len = %d\n", [_workingText length]); + [_workingText release]; + _workingText = nil; + processingCompose = NO; + + //PENDING: delete working text } -/* end workaround */ - len = [charactersIgnoringModifiers length]; - if (len) { - xEvent.xkey.nbytes = [charactersIgnoringModifiers characterAtIndex:0]; - if (len > 1) { - TkMacOSXDbgMsg("more than one charactersIgnoringModifiers"); - } - } - if (repeat) { - Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); - xEvent.xany.type = KeyPress; - xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); - } + +- (BOOL)hasMarkedText +{ + return _workingText != nil; +} + + +- (NSRange)markedRange +{ + NSRange rng = _workingText != nil + ? NSMakeRange (0, [_workingText length]) : NSMakeRange (NSNotFound, 0); + if (NS_KEYLOG) + NSLog (@"markedRange request"); + return rng; +} + + +- (void)unmarkText +{ + if (NS_KEYLOG) + NSLog (@"unmark (accept) text"); + [self deleteWorkingText]; + processingCompose = NO; +} + + +/* used to position char selection windows, etc. */ +- (NSRect)firstRectForCharacterRange: (NSRange)theRange +{ + NSRect rect; + NSPoint pt; + + pt.x = caret_x; + pt.y = caret_y; + + pt = [self convertPoint: pt toView: nil]; + pt = [[self window] convertBaseToScreen: pt]; + pt.y -= caret_height; + + rect.origin = pt; + rect.size.width = caret_height; + rect.size.height = caret_height; + return rect; +} + + +- (NSInteger)conversationIdentifier +{ + return (NSInteger)self; +} + + +- (void)doCommandBySelector: (SEL)aSelector +{ + if (NS_KEYLOG) + NSLog (@"doCommandBySelector: %@", NSStringFromSelector (aSelector)); + processingCompose = NO; + if (aSelector == @selector (deleteBackward:)) + { + /* happens when user backspaces over an ongoing composition: + throw a 'delete' into the event queue */ + XEvent xEvent; + setupXEvent(&xEvent, [self window], 0); + xEvent.xany.type = KeyPress; + xEvent.xkey.nbytes = 1; + xEvent.xkey.keycode = (0x33 << 16) | 0x7F; + xEvent.xkey.trans_chars[0] = 0x7F; + xEvent.xkey.trans_chars[1] = 0x0; + Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); } - Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); - savedModifiers = modifiers; +} - return theEvent; + +- (NSArray *)validAttributesForMarkedText +{ + static NSArray *arr = nil; + if (arr == nil) arr = [NSArray new]; + /* [[NSArray arrayWithObject: NSUnderlineStyleAttributeName] retain]; */ + return arr; } + + +- (NSRange)selectedRange +{ + if (NS_KEYLOG) + NSLog (@"selectedRange request"); + return NSMakeRange (NSNotFound, 0); +} + + +- (NSUInteger)characterIndexForPoint: (NSPoint)thePoint +{ + if (NS_KEYLOG) + NSLog (@"characterIndexForPoint request"); + return 0; +} + + +- (NSAttributedString *)attributedSubstringFromRange: (NSRange)theRange +{ + static NSAttributedString *str = nil; + if (str == nil) str = [NSAttributedString new]; + if (NS_KEYLOG) + NSLog (@"attributedSubstringFromRange request"); + return str; +} +/* End impl. */ @end + + + +/* + * Set up basic fields in xevent for keyboard input. + */ +static void +setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state) +{ + TkWindow *winPtr = TkMacOSXGetTkWindow(w); + Tk_Window tkwin = (Tk_Window) winPtr; + + memset(xEvent, 0, sizeof(XEvent)); + xEvent->xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); + xEvent->xany.send_event = false; + xEvent->xany.display = Tk_Display(tkwin); + xEvent->xany.window = Tk_WindowId(tkwin); + + xEvent->xkey.root = XRootWindow(Tk_Display(tkwin), 0); + xEvent->xkey.subwindow = None; + xEvent->xkey.time = TkpGetMS(); + xEvent->xkey.state = state; + xEvent->xkey.same_screen = true; + xEvent->xkey.trans_chars[0] = 0; + xEvent->xkey.nbytes = 0; +} #pragma mark - @@ -343,9 +601,118 @@ Tk_SetCaretPos( int x, int y, int height) -{ + { + TkCaret *caretPtr = &(((TkWindow *) tkwin)->dispPtr->caret); + + /* + * Prevent processing anything if the values haven't changed. Windows only + * has one display, so we can do this with statics. + */ + + if ((caretPtr->winPtr == ((TkWindow *) tkwin)) + && (caretPtr->x == x) && (caretPtr->y == y)) { + return; + } + + caretPtr->winPtr = ((TkWindow *) tkwin); + caretPtr->x = x; + caretPtr->y = y; + caretPtr->height = height; + + /* + * As in Windows, adjust to the toplevel to get the coords right. + */ + + while (!Tk_IsTopLevel(tkwin)) { + x += Tk_X(tkwin); + y += Tk_Y(tkwin); + tkwin = Tk_Parent(tkwin); + if (tkwin == NULL) { + return; + } + } + + /* But adjust for fact that NS uses flipped view. */ + y = Tk_Height(tkwin) - y; + + caret_x = x; + caret_y = y; + caret_height = height; } + +static unsigned convert_ns_to_X_keysym[] = +{ + NSHomeFunctionKey, 0x50, + NSLeftArrowFunctionKey, 0x51, + NSUpArrowFunctionKey, 0x52, + NSRightArrowFunctionKey, 0x53, + NSDownArrowFunctionKey, 0x54, + NSPageUpFunctionKey, 0x55, + NSPageDownFunctionKey, 0x56, + NSEndFunctionKey, 0x57, + NSBeginFunctionKey, 0x58, + NSSelectFunctionKey, 0x60, + NSPrintFunctionKey, 0x61, + NSExecuteFunctionKey, 0x62, + NSInsertFunctionKey, 0x63, + NSUndoFunctionKey, 0x65, + NSRedoFunctionKey, 0x66, + NSMenuFunctionKey, 0x67, + NSFindFunctionKey, 0x68, + NSHelpFunctionKey, 0x6A, + NSBreakFunctionKey, 0x6B, + + NSF1FunctionKey, 0xBE, + NSF2FunctionKey, 0xBF, + NSF3FunctionKey, 0xC0, + NSF4FunctionKey, 0xC1, + NSF5FunctionKey, 0xC2, + NSF6FunctionKey, 0xC3, + NSF7FunctionKey, 0xC4, + NSF8FunctionKey, 0xC5, + NSF9FunctionKey, 0xC6, + NSF10FunctionKey, 0xC7, + NSF11FunctionKey, 0xC8, + NSF12FunctionKey, 0xC9, + NSF13FunctionKey, 0xCA, + NSF14FunctionKey, 0xCB, + NSF15FunctionKey, 0xCC, + NSF16FunctionKey, 0xCD, + NSF17FunctionKey, 0xCE, + NSF18FunctionKey, 0xCF, + NSF19FunctionKey, 0xD0, + NSF20FunctionKey, 0xD1, + NSF21FunctionKey, 0xD2, + NSF22FunctionKey, 0xD3, + NSF23FunctionKey, 0xD4, + NSF24FunctionKey, 0xD5, + + NSBackspaceCharacter, 0x08, /* 8: Not on some KBs. */ + NSDeleteCharacter, 0xFF, /* 127: Big 'delete' key upper right. */ + NSDeleteFunctionKey, 0x9F, /* 63272: Del forw key off main array. */ + + NSTabCharacter, 0x09, + 0x19, 0x09, /* left tab->regular since pass shift */ + NSCarriageReturnCharacter, 0x0D, + NSNewlineCharacter, 0x0D, + NSEnterCharacter, 0x8D, + + 0x1B, 0x1B /* escape */ +}; + + +static unsigned isFunctionKey(unsigned code) +{ + const unsigned last_keysym = (sizeof (convert_ns_to_X_keysym) + / sizeof (convert_ns_to_X_keysym[0])); + unsigned keysym; + for (keysym = 0; keysym < last_keysym; keysym += 2) + if (code == convert_ns_to_X_keysym[keysym]) + return 0xFF00 | convert_ns_to_X_keysym[keysym+1]; + return 0; + } + /* * Local Variables: * mode: objc diff --git a/macosx/tkMacOSXKeyboard.c b/macosx/tkMacOSXKeyboard.c index a51f970..d388f60 100644 --- a/macosx/tkMacOSXKeyboard.c +++ b/macosx/tkMacOSXKeyboard.c @@ -766,11 +766,11 @@ TkpGetKeySym( } } -#if 0 + /* If nbytes has been set, it's not a function key, but a regular key that + has been translated in tkMacOSXKeyEvent.c; just use that. */ if (eventPtr->xkey.nbytes) { - return eventPtr->xkey.nbytes; + return eventPtr->xkey.keycode & 0xFFFF; } -#endif /* * Figure out which of the four slots in the keymap vector to use for this diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c index 19db490..d04f0cd 100644 --- a/macosx/tkMacOSXMenu.c +++ b/macosx/tkMacOSXMenu.c @@ -292,6 +292,12 @@ static int ModifierCharWidth(Tk_Font tkfont); return NO; } + // For command key, take input manager's word so things + // like dvorak / qwerty layout work. + if (([event modifierFlags] & NSCommandKeyMask) == NSCommandKeyMask) { + key = [event characters]; + } + NSArray *itemArray = [self itemArray]; for (NSMenuItem *item in itemArray) { diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h index 347f448..034c450 100644 --- a/macosx/tkMacOSXPrivate.h +++ b/macosx/tkMacOSXPrivate.h @@ -316,13 +316,18 @@ VISIBILITY_HIDDEN @end VISIBILITY_HIDDEN -@interface TKContentView : NSView { +@interface TKContentView : NSView { @private id _savedSubviews; BOOL _subviewsSetAside; + NSString *_workingText; } @end +@interface TKContentView(TKKeyEvent) +- (void) deleteWorkingText; +@end + VISIBILITY_HIDDEN @interface TKWindow : NSWindow @end -- cgit v0.12