Index: tkMacOSXKeyEvent.c
===================================================================
RCS file: /cvsroot/tktoolkit/tk/macosx/tkMacOSXKeyEvent.c,v
retrieving revision 1.31
diff -u -p -r1.31 tkMacOSXKeyEvent.c
--- tkMacOSXKeyEvent.c	24 Jan 2011 15:20:50 -0000	1.31
+++ tkMacOSXKeyEvent.c	22 Mar 2011 22:09:47 -0000
@@ -21,6 +21,8 @@
 #define TK_MAC_DEBUG_KEYBOARD
 #endif
 */
+#define NS_KEYLOG 0
+
 
 static Tk_Window grabWinPtr = NULL;
 				/* Current grab window, NULL if no grab. */
@@ -28,9 +30,17 @@ static Tk_Window keyboardGrabWinPtr = NU
 				/* Current keyboard grab window. */
 static NSModalSession modalSession = NULL;
 
+static void setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state);
+
+static BOOL processingCompose = NO;
+
+static int caret_x = 0, caret_y = 0, caret_height = 0;
+
+
 #pragma mark TKApplication(TKKeyEvent)
 
 @implementation TKApplication(TKKeyEvent)
+
 - (NSEvent *) tkProcessKeyEvent: (NSEvent *) theEvent
 {
 #ifdef TK_MAC_DEBUG_EVENTS
@@ -38,11 +48,18 @@ static NSModalSession modalSession = NUL
 #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:
@@ -50,12 +67,13 @@ static NSModalSession modalSession = NUL
 	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);
+	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;
 
@@ -63,123 +81,343 @@ static NSModalSession modalSession = NUL
 	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.
+         */
+
+        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 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 = ([charactersIgnoringModifiers length] == 0) ?
+          0 : [charactersIgnoringModifiers characterAtIndex: 0];
+        if (type != NSKeyDown || (code < 0x20 || code > 0x80)
+            || (len > 0 && state && state != Mod2Mask
+                && state != ShiftMask && state != LockMask)) {
+
+            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;
+              }
+
+              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 here (crash on some composite characters) */
+                  //PENDING: we might not need this anymore
+                  TkMacOSXDbgMsg("characters too long");
+                  return theEvent;
+                }
+              }
+
+              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));
+              }
+            }
+            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];
+      }
 
-    /*
-     * The focus must be in the FrontWindow on the Macintosh. We then query Tk
-     * to determine the exact Tk window that owns the focus.
-     */
+    savedModifiers = modifiers;
 
-    TkWindow *winPtr = TkMacOSXGetTkWindow(w);
-    Tk_Window tkwin = (Tk_Window) winPtr;
+    return theEvent;
+}
+@end
 
-    if (!tkwin) {
-	TkMacOSXDbgMsg("tkwin == NULL");
-	return theEvent;
-    }
-    tkwin = (Tk_Window) winPtr->dispPtr->focusPtr;
-    if (!tkwin) {
-	TkMacOSXDbgMsg("tkwin == NULL");
-	return theEvent;
+
+
+@implementation TKContentView(TKKeyEvent)
+/* <NSTextInput> implementation (called through interpretKeyEvents:]). */
+
+/* <NSTextInput>: 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;
+
+  if (NS_KEYLOG)
+    NSLog (@"insertText '%@'\tlen = %d", aString, len);
+  processingCompose = NO;
+
+  /* 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<len; i++)
+    {
+      xEvent.xkey.keycode = (UInt16) [aString characterAtIndex: i];
+      [[aString substringWithRange: NSMakeRange(i,1)]
+        getCString: xEvent.xkey.trans_chars
+         maxLength: XMaxTransChars encoding: NSUTF8StringEncoding];
+      xEvent.xkey.nbytes = strlen(xEvent.xkey.trans_chars);
+      Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
     }
+}
 
-    XEvent xEvent;
 
-    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;
-
-    if (type == NSFlagsChanged) {
-	if (savedModifiers > modifiers) {
-	    xEvent.xany.type = KeyRelease;
-	} else {
-	    xEvent.xany.type = KeyPress;
-	}
+/* <NSTextInput>: 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);
+
+  if (workingText != nil)
+    [self deleteWorkingText];
+  if ([str length] == 0)
+    return;
 
-	/*
-	 * 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;
-	}
+  processingCompose = YES;
+  workingText = [str copy];
 
-/* 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;
-	}
+  //PENDING: insert workingText underlined
 }
-/* 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));
-	}
+
+/* delete display of composing characters [not in <NSTextInput>] */
+- (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
+}
+
+
+- (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;
+}
+
+
+- (long)conversationIdentifier
+{
+  return (long)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 <NSTextInput> 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 -
 
 /*
@@ -346,6 +584,42 @@ Tk_SetCaretPos(
     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;
 }
 
 /*
Index: tkMacOSXPrivate.h
===================================================================
RCS file: /cvsroot/tktoolkit/tk/macosx/tkMacOSXPrivate.h,v
retrieving revision 1.12
diff -u -p -r1.12 tkMacOSXPrivate.h
--- tkMacOSXPrivate.h	7 Jul 2009 08:08:18 -0000	1.12
+++ tkMacOSXPrivate.h	22 Mar 2011 22:09:47 -0000
@@ -331,13 +331,18 @@ VISIBILITY_HIDDEN
 @end
 
 VISIBILITY_HIDDEN
-@interface TKContentView : NSView {
+@interface TKContentView : NSView <NSTextInput> {
 @private
     id _savedSubviews;
     BOOL _subviewsSetAside;
+    NSString *workingText;
 }
 @end
 
+@interface TKContentView(TKKeyEvent)
+- (void) deleteWorkingText;
+@end
+
 VISIBILITY_HIDDEN
 @interface TKWindow : NSWindow
 @end
