Author: manolo
Date: 2013-01-17 09:40:53 -0800 (Thu, 17 Jan 2013)
New Revision: 9799
Log:
Mac OS: reorganized the text input handling code. Added a detailed description
of what the code does
for this rather complex issue in comments.
Modified:
branches/branch-1.3/src/Fl_cocoa.mm
Modified: branches/branch-1.3/src/Fl_cocoa.mm
===================================================================
--- branches/branch-1.3/src/Fl_cocoa.mm 2013-01-16 15:25:39 UTC (rev 9798)
+++ branches/branch-1.3/src/Fl_cocoa.mm 2013-01-17 17:40:53 UTC (rev 9799)
@@ -105,6 +105,7 @@
Window fl_window;
Fl_Window *Fl_Window::current_;
int fl_mac_os_version = calc_mac_os_version(); // the version number
of the running Mac OS X (e.g., 100604 for 10.6.4)
+static SEL inputContextSEL = (fl_mac_os_version >= 100600 ?
@selector(inputContext) : @selector(FLinputContext));
// forward declarations of variables in this file
static int got_events = 0;
@@ -899,92 +900,30 @@
return;
}
-@interface FLTextView : NSTextView
-// this subclass is needed under OS X <= 10.5 but not under >= 10.6 where the
base class is enough
+@interface FLTextView : NSTextView // this subclass is only needed under OS X
< 10.6
{
+ BOOL isActive;
}
-- (void)interpretKeyEvents:(NSArray *)eventArray;
+- (void)insertText:(id)aString;
+- (void)doCommandBySelector:(SEL)aSelector;
+- (void)setActive:(BOOL)a;
@end
@implementation FLTextView
- (void)insertText:(id)aString
{
- [[[NSApp keyWindow] contentView] insertText:aString];
+ if (isActive) [[[NSApp keyWindow] contentView] insertText:aString];
}
- (void)doCommandBySelector:(SEL)aSelector
{
[[[NSApp keyWindow] contentView] doCommandBySelector:aSelector];
}
-- (void)interpretKeyEvents:(NSArray *)eventArray
+- (void)setActive:(BOOL)a
{
- if (Fl::e_keysym == FL_BackSpace || Fl::e_keysym == FL_KP_Enter ||
- Fl::e_keysym == FL_Enter || Fl::e_keysym == FL_Escape || Fl::e_keysym ==
FL_Tab ) {
- NSEvent *theEvent = (NSEvent*)[eventArray objectAtIndex:0];
- // interpretKeyEvents doesn't output anything for these 5 keys under 10.5
or below
- NSString *s = [theEvent characters];
- if ([s length] >= 1) {
- static char utf[2] = {0, 0};
- utf[0] = [s UTF8String][0];
- Fl::e_text = utf;
- Fl::e_length = 1;
- }
- Fl_Window *window = [(FLWindow*)[theEvent window] getFl_Window];
- Fl::handle(FL_KEYBOARD, window);
- }
- else [super interpretKeyEvents:eventArray];
+ isActive = a;
}
@end
/*
-Handle cocoa keyboard events
-Events during a character composition sequence:
- - keydown with deadkey -> [[theEvent characters] length] is 0
- - keyup -> [theEvent characters] contains the deadkey
- - keydown with next key -> [theEvent characters] contains the composed
character
- - keyup -> [theEvent characters] contains the standard character
- */
-static void cocoaKeyboardHandler(NSEvent *theEvent)
-{
- NSUInteger mods;
-
- // get the modifiers
- mods = [theEvent modifierFlags];
- // get the key code
- UInt32 keyCode = 0, maskedKeyCode = 0;
- unsigned short sym = 0;
- keyCode = [theEvent keyCode];
- // 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.
- // In this mode, there seem to be no key-down codes
- // printf("%08x %08x %08x\n", keyCode, mods, key);
- maskedKeyCode = keyCode & 0x7f;
-
- if ([theEvent type] == NSKeyUp) {
- Fl::e_state &= 0xbfffffff; // clear the deadkey flag
- }
-
- mods_to_e_state( mods ); // process modifier keys
- sym = macKeyLookUp[maskedKeyCode];
- 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);
- // charactersIgnoringModifiers doesn't ignore shift, remove it when it's on
- if(one >= 'A' && one <= 'Z') one += 32;
- if (one > 0 && one <= 0x7f && (sym<'0' || sym>'9') ) sym = one;
- }
- Fl::e_keysym = Fl::e_original_keysym = sym;
-
- /*NSLog(@"cocoaKeyboardHandler: keycode=%08x keysym=%08x mods=%08x symbol=%@
(%@)",
- keyCode, sym, mods, [theEvent characters], [theEvent
charactersIgnoringModifiers]);*/
-
- // If there is text associated with this key, it will be filled in later.
- Fl::e_length = 0;
- Fl::e_text = (char*)"";
-}
-
-
-/*
* Open callback function to call...
*/
static void (*open_cb)(const char *) = 0;
@@ -1667,7 +1606,121 @@
}
}
+/** How FLTK handles Mac OS text input
+
+ Let myview be the instance of the FLView class that has the keyboard focus.
FLView is an FLTK-defined NSView subclass
+ that implements the NSTextInputClient protocol to properly handle text input.
It also implements the old NSTextInput
+ protocol to run with OS <= 10.4. The few NSTextInput protocol methods that
differ in signature from the NSTextInputClient
+ protocol transmit the received message to the corresponding NSTextInputClient
method.
+ Keyboard input sends keyDown: and performKeyEquivalent: messages to myview.
The latter occurs for keys such as
+ ForwardDelete, arrows and F1, and when the Ctrl or Cmd modifiers are used.
Other key presses send keyDown: messages.
+ Both keyDown: and performKeyEquivalent: methods call [[myview inputContext]
handleEvent:theEvent] that triggers system
+ processing of keyboard events. Three sorts of messages are then sent back by
the system to myview: doCommandBySelector:,
+ setMarkedText: and insertText:. All 3 messages eventually produce
Fl::handle(FL_KEYBOARD, focus-window) calls.
+ The handleEvent: method, however, does not send any message back to myview
when both the Alt and Cmd modifiers
+ are pressed. In this situation, the performKeyEquivalent: method directly
sends the doCommandBySelector: message to myview.
+ The doCommandBySelector: message allows to process events such as new-line,
forward and backward delete, arrows, escape,
+ tab, F1 and when the Ctrl or Cmd modifiers are used. The message
setMarkedText:
+ is sent when marked text, that is, temporary text that gets replaced later by
some other text, is inserted. This happens
+ when a dead key is pressed, and also when entering complex scripts (e.g.,
Chinese). Fl_X::next_marked_length gives the byte
+ length of marked text before the FL_KEYBOARD event is processed.
Fl::compose_state gives this length after this processing.
+ Message insertText: is sent to enter text in the focused widget. If there's
marked text, Fl::compose_state is > 0, and this
+ marked text gets replaced by the inserted text. If there's no marked text,
the new text is inserted at the insertion point.
+ When the character palette is used to enter text, the system sends an
insertText: message to myview. The code processes it
+ as an FL_PASTE event. The in_key_event field of the FLView class allows to
differentiate keyboard from palette inputs.
+
+ OS >= 10.7 contains a feature where pressing and holding certain keys opens a
menu window that shows a list
+ of possible accented variants of this key. The selectedRange field of the
FLView class and the selectedRange, insertText:
+ and setMarkedText: methods of the NSTextInputClient protocol are used to
support this feature.
+ The notion of selected text (!= marked text) is monitored by the
selectedRange field.
+ The -(NSRange)[FLView selectedRange] method is used to control whether an
FLTK widget opens accented character windows
+ by returning .location = NSNotFound to disable that, or returning the value
of the selectedRange field to enable the feature.
+ When selectedRange.location >= 0, the value of selectedRange.length is
meaningful. 0 means no text is currently selected,
+ > 0 means this number of characters before the insertion point are selected.
The insertText: method does
+ selectedRange = NSMakeRange(100, 0); to indicate no text is selected. The
setMarkedText: method does
+ selectedRange = NSMakeRange(100, newSelection.length); to indicate that this
length of text is selected.
+
+ With OS <= 10.5, the crucial call [[myview inputContext]
handleEvent:theEvent] is not possible because neither the
+ inputContext nor the handleEvent: methods are implemented. This call is
re-written:
+ static SEL inputContextSEL = (fl_mac_os_version >= 100600 ?
@selector(inputContext) : @selector(FLinputContext));
+ [[myview performSelector:inputContextSEL] handleEvent:theEvent];
+ that replaces the 10.6 inputContext message by the FLinputContext message.
This message and two FLTK-defined classes,
+ FLTextInputContext and FLTextView, are used to emulate with OS <= 10.5 what's
possible with OS >= 10.6.
+ Method -(FLTextInputContext*)[FLView FLinputContext] returns an instance of
class FLTextInputContext that possesses
+ a handleEvent: method. FLView's FLinputContext method also calls [[self
window] fieldEditor:YES forObject:nil] which
+ returns the so-called view's "field editor". This editor is an instance of
the FLTextView class allocated by the
+ -(id)[FLWindowDelegate windowWillReturnFieldEditor: toObject:] method.
+ The -(BOOL)[FLTextInputContext handleEvent:] method emulates the missing 10.6
-(BOOL)[NSTextInputContext handleEvent:]
+ by sending the interpretKeyEvents: message to the FLTextView object. The
system sends back doCommandBySelector: and
+ insertText: messages to the FLTextView object that are transmitted unchanged
to myview to be processed as with OS >= 10.6.
+ The system also sends setMarkedText: messages directly to myview.
+
+ There is furthermore an oddity of dead key processing with OS <= 10.5. It
occurs when a dead key followed by a non-accented
+ key are pressed. Say, for example, that keys '^' followed by 'p' are pressed
on a French or German keyboard. Resulting
+ messages are: [myview setMarkedText:@"^"], [myview insertText:@"^"], [myview
insertText:@"p"], [FLTextView insertText:@"^p"].
+ The 2nd '^' replaces the marked 1st one, followed by p^p. The resulting text
in the widget is "^p^p" instead of the
+ desired "^p". To avoid that, the FLTextView object is deactivated by the
insertText: message and reactivated after
+ the handleEvent: message has been processed.
+
+ NSEvent's during a character composition sequence:
+ - keyDown with deadkey -> [[theEvent characters] length] is 0
+ - keyUp -> [theEvent characters] contains the deadkey
+ - keyDown with next key -> [theEvent characters] contains the composed
character
+ - keyUp -> [theEvent characters] contains the standard character
+ */
+
+static void cocoaKeyboardHandler(NSEvent *theEvent)
+{
+ NSUInteger mods;
+ // get the modifiers
+ mods = [theEvent modifierFlags];
+ // get the key code
+ UInt32 keyCode = 0, maskedKeyCode = 0;
+ unsigned short sym = 0;
+ keyCode = [theEvent keyCode];
+ // 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.
+ // In this mode, there seem to be no key-down codes
+ // printf("%08x %08x %08x\n", keyCode, mods, key);
+ maskedKeyCode = keyCode & 0x7f;
+ if ([theEvent type] == NSKeyUp) {
+ Fl::e_state &= 0xbfffffff; // clear the deadkey flag
+ }
+ mods_to_e_state( mods ); // process modifier keys
+ sym = macKeyLookUp[maskedKeyCode];
+ 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);
+ // charactersIgnoringModifiers doesn't ignore shift, remove it when it's on
+ if(one >= 'A' && one <= 'Z') one += 32;
+ if (one > 0 && one <= 0x7f && (sym<'0' || sym>'9') ) sym = one;
+ }
+ Fl::e_keysym = Fl::e_original_keysym = sym;
+ /*NSLog(@"cocoaKeyboardHandler: keycode=%08x keysym=%08x mods=%08x symbol=%@
(%@)",
+ keyCode, sym, mods, [theEvent characters], [theEvent
charactersIgnoringModifiers]);*/
+ // If there is text associated with this key, it will be filled in later.
+ Fl::e_length = 0;
+ Fl::e_text = (char*)"";
+}
+
+@interface FLTextInputContext : NSObject { // "emulates" NSTextInputContext
before OS 10.6
+@public
+ FLTextView *edit;
+}
+-(BOOL)handleEvent:(NSEvent*)theEvent;
+@end
+@implementation FLTextInputContext
+-(BOOL)handleEvent:(NSEvent*)theEvent {
+ [self->edit setActive:YES];
+ [self->edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
+ [self->edit setActive:YES];
+ return YES;
+}
+@end
+
@interface FLView : NSView <NSTextInput
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
, NSTextInputClient
@@ -1702,6 +1755,14 @@
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
- (void)draggingExited:(id < NSDraggingInfo >)sender;
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal;
+- (FLTextInputContext*)FLinputContext;
+#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange;
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection
replacementRange:(NSRange)replacementRange;
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange
actualRange:(NSRangePointer)actualRange;
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange
actualRange:(NSRangePointer)actualRange;
+- (NSInteger)windowLevel;
+#endif
@end
@implementation FLView
@@ -1730,28 +1791,17 @@
}
- (BOOL)performKeyEquivalent:(NSEvent*)theEvent
{
- int handled = 1;
//NSLog(@"performKeyEquivalent:");
fl_lock_function();
cocoaKeyboardHandler(theEvent);
- Fl_Window *window = [(FLWindow*)[theEvent window] getFl_Window];
- NSString *s = [theEvent characters];
+ in_key_event = YES;
NSUInteger mods = [theEvent modifierFlags];
- if ( (mods & NSShiftKeyMask) && (mods & NSCommandKeyMask) ) {
- s = [s uppercaseString]; // US keyboards return lowercase letter in s if
cmd-shift-key is hit
- }
- if ([s length] >= 1) [FLView prepareEtext:s];
- if ( (mods & NSControlKeyMask) || (mods & NSCommandKeyMask) ) {
- handled = Fl::handle(FL_KEYBOARD, window);
- }
- else {
- in_key_event = YES;
- NSText *edit = [[theEvent window] fieldEditor:YES forObject:nil];
- [edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
- in_key_event = NO;
- }
+ BOOL handled = YES;
+ if ( (mods & NSAlternateKeyMask) && (mods & NSCommandKeyMask) ) [self
doCommandBySelector:NULL];
+ else handled = [[self performSelector:inputContextSEL] handleEvent:theEvent];
+ in_key_event = NO;
fl_unlock_function();
- return (handled ? YES : NO);
+ return handled;
}
- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent
{
@@ -1793,23 +1843,18 @@
cocoaMouseWheelHandler(theEvent);
}
- (void)keyDown:(NSEvent *)theEvent {
- //NSLog(@"keyDown");
+ //NSLog(@"keyDown:%@",[theEvent characters]);
fl_lock_function();
-
Fl_Window *window = [(FLWindow*)[theEvent window] getFl_Window];
Fl::first_window(window);
-
- // First let's process the raw key press
cocoaKeyboardHandler(theEvent);
-
- NSText *edit = [[theEvent window] fieldEditor:YES forObject:nil];
in_key_event = YES;
- [edit interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
+ [[self performSelector:inputContextSEL] handleEvent:theEvent];
in_key_event = NO;
fl_unlock_function();
}
- (void)keyUp:(NSEvent *)theEvent {
- //NSLog(@"keyUp: ");
+ //NSLog(@"keyUp:%@",[theEvent characters]);
fl_lock_function();
Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
Fl::first_window(window);
@@ -1932,6 +1977,15 @@
return NSDragOperationGeneric;
}
+- (FLTextInputContext*)FLinputContext { // used only if OS < 10.6 to replace
[NSView inputContext]
+ static FLTextInputContext *context = NULL;
+ if (!context) {
+ context = [[FLTextInputContext alloc] init];
+ }
+ context->edit = (FLTextView*)[[self window] fieldEditor:YES forObject:nil];
+ return context;
+}
+
+ (void)prepareEtext:(NSString*)aString {
// fills Fl::e_text with UTF-8 encoded aString using an adequate memory
allocation
static char *received_utf8 = NULL;
@@ -1953,12 +2007,22 @@
Fl::e_length = l;
}
-// These functions implement text input.
- (void)doCommandBySelector:(SEL)aSelector {
+ //NSLog(@"doCommandBySelector:%s",sel_getName(aSelector));
+ NSString *s = [[NSApp currentEvent] characters];
+ NSUInteger mods = [[NSApp currentEvent] modifierFlags];
+ if ( (mods & NSShiftKeyMask) && (mods & NSCommandKeyMask) ) {
+ s = [s uppercaseString]; // US keyboards return lowercase letter in s if
cmd-shift-key is hit
+ }
+ [FLView prepareEtext:s];
+ Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
+ Fl::handle(FL_KEYBOARD, target);
}
+
- (void)insertText:(id)aString {
[self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0)];
}
+
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
NSString *received;
if ([aString isKindOfClass:[NSAttributedString class]]) {
@@ -1980,11 +2044,13 @@
// We can get called outside of key events (e.g., from the character
palette, from CJK text input).
// Transform character palette actions to FL_PASTE events.
Fl_X::next_marked_length = 0;
- Fl::handle( (in_key_event || Fl::marked_text_length()) ? FL_KEYBOARD :
FL_PASTE, target);
+ int flevent = (in_key_event || Fl::marked_text_length()) ? FL_KEYBOARD :
FL_PASTE;
+ Fl::handle( flevent, target);
selectedRange = NSMakeRange(100, 0); // 100 is an arbitrary value
// for some reason, with the palette, 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();
+ if (flevent == FL_PASTE) Fl::flush();
+ if (fl_mac_os_version < 100600) [(FLTextView*)[[self window] fieldEditor:YES
forObject:nil] setActive:NO];
fl_unlock_function();
}
@@ -3448,7 +3514,7 @@
CGImageRef img;
if (fl_mac_os_version >= 100500) {
NSBitmapImageRep *bitmap = rect_to_NSBitmapImageRep(win, x, y, w, h);
- img = [bitmap CGImage]; // requires Mac OS 10.5
+ img = (CGImageRef)[bitmap performSelector:@selector(CGImage)]; // requires
Mac OS 10.5
CGImageRetain(img);
[bitmap release];
}
_______________________________________________
fltk-commit mailing list
[email protected]
http://lists.easysw.com/mailman/listinfo/fltk-commit