diff options
Diffstat (limited to 'intern/ghost/intern/GHOST_WindowViewCocoa.h')
-rw-r--r-- | intern/ghost/intern/GHOST_WindowViewCocoa.h | 325 |
1 files changed, 304 insertions, 21 deletions
diff --git a/intern/ghost/intern/GHOST_WindowViewCocoa.h b/intern/ghost/intern/GHOST_WindowViewCocoa.h index f47e02704b2..fa629528809 100644 --- a/intern/ghost/intern/GHOST_WindowViewCocoa.h +++ b/intern/ghost/intern/GHOST_WindowViewCocoa.h @@ -17,6 +17,11 @@ * All rights reserved. */ +/* The Carbon API is still needed to check if the Input Source (Input Method or IME) is valid. */ +#ifdef WITH_INPUT_IME +# import <Carbon/Carbon.h> +#endif + /* NSView subclass for drawing and handling input. * * COCOA_VIEW_BASE_CLASS will be either NSView or NSOpenGLView depending if @@ -25,7 +30,7 @@ * Objective-C does not have multiple inheritance. */ // We need to subclass it in order to give Cocoa the feeling key events are trapped -@interface COCOA_VIEW_CLASS : COCOA_VIEW_BASE_CLASS <NSTextInput> +@interface COCOA_VIEW_CLASS : COCOA_VIEW_BASE_CLASS <NSTextInputClient> { GHOST_SystemCocoa *systemCocoa; GHOST_WindowCocoa *associatedWindow; @@ -33,10 +38,27 @@ bool composing; NSString *composing_text; - bool immediate_draw; +#ifdef WITH_INPUT_IME + struct { + GHOST_ImeStateFlagCocoa state_flag; + NSRect candidate_window_position; + + /* Event data. */ + GHOST_TEventImeData event; + std::string result; + std::string composite; + std::string combined_result; + } ime; +#endif } - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa; + +#ifdef WITH_INPUT_IME +- (void)beginIME:(int32_t)x y:(int32_t)y w:(int32_t)w h:(int32_t)h completed:(bool)completed; + +- (void)endIME; +#endif @end @implementation COCOA_VIEW_CLASS @@ -50,7 +72,21 @@ composing = false; composing_text = nil; - immediate_draw = false; +#ifdef WITH_INPUT_IME + ime.state_flag = 0; + ime.candidate_window_position = NSZeroRect; + ime.event.cursor_position = -1; + ime.event.target_start = -1; + ime.event.target_end = -1; + + /* Register a function to be executed when Input Method is changed using + * 'Control + Space' or language-specific keys (such as 'Eisu / Kana' key for Japanese).*/ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(ImeDidChangeCallback:) + name:NSTextInputContextKeyboardSelectionDidChangeNotification + object:nil]; +#endif } - (BOOL)acceptsFirstResponder @@ -66,20 +102,38 @@ // The trick to prevent Cocoa from complaining (beeping) - (void)keyDown:(NSEvent *)event { - systemCocoa->handleKeyEvent(event); +#ifdef WITH_INPUT_IME + [self checkKeyCodeIsControlChar:event]; + const bool ime_process = [self isProcessedByIme]; +#else + const bool ime_process = false; +#endif + + if (!ime_process) { + systemCocoa->handleKeyEvent(event); + } /* Start or continue composing? */ if ([[event characters] length] == 0 || [[event charactersIgnoringModifiers] length] == 0 || - composing) { + composing || ime_process) { composing = YES; // interpret event to call insertText - NSMutableArray *events; - events = [[NSMutableArray alloc] initWithCapacity:1]; - [events addObject:event]; - [self interpretKeyEvents:events]; // calls insertText - [events removeObject:event]; - [events release]; + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; // calls insertText + +#ifdef WITH_INPUT_IME + // For Korean input, control characters are also processed by handleKeyEvent. + const int controlCharForKorean = (GHOST_IME_COMPOSITION_EVENT | GHOST_IME_RESULT_EVENT | + GHOST_IME_KEY_CONTROL_CHAR); + if (((ime.state_flag & controlCharForKorean) == controlCharForKorean)) { + systemCocoa->handleKeyEvent(event); + } + + ime.state_flag &= ~(GHOST_IME_COMPOSITION_EVENT | GHOST_IME_RESULT_EVENT); + + ime.combined_result.clear(); +#endif + return; } } @@ -207,24 +261,90 @@ } } -- (void)insertText:(id)chars +// Processes the Result String sent from the Input Method. +- (void)insertText:(id)chars replacementRange:(NSRange)replacementRange { [self composing_free]; + +#ifdef WITH_INPUT_IME + if (ime.state_flag & GHOST_IME_ENABLED) { + if (!(ime.state_flag & GHOST_IME_COMPOSING)) { + [self processImeEvent:GHOST_kEventImeCompositionStart]; + } + + // For Chinese and Korean input, insertText may be executed twice with a single keyDown. + if (ime.state_flag & GHOST_IME_RESULT_EVENT) { + ime.combined_result += [self convertNSString:chars]; + } + else { + ime.combined_result = [self convertNSString:chars]; + } + + [self setImeResult:ime.combined_result]; + + /* For Korean input, both "Result Event" and "Composition Event" + * can occur in a single keyDown. */ + if (![self ime_did_composition]) { + [self processImeEvent:GHOST_kEventImeComposition]; + } + ime.state_flag |= GHOST_IME_RESULT_EVENT; + + [self processImeEvent:GHOST_kEventImeCompositionEnd]; + ime.state_flag &= ~GHOST_IME_COMPOSING; + } +#endif } -- (void)setMarkedText:(id)chars selectedRange:(NSRange)range +// Processes the Composition String sent from the Input Method. +- (void)setMarkedText:(id)chars + selectedRange:(NSRange)range + replacementRange:(NSRange)replacementRange { [self composing_free]; - if ([chars length] == 0) + + if ([chars length] == 0) { +#ifdef WITH_INPUT_IME + // Processes when the last Composition String is deleted. + if (ime.state_flag & GHOST_IME_COMPOSING) { + [self setImeResult:std::string()]; + [self processImeEvent:GHOST_kEventImeComposition]; + [self processImeEvent:GHOST_kEventImeCompositionEnd]; + ime.state_flag &= ~GHOST_IME_COMPOSING; + } +#endif + return; + } // start composing composing = YES; composing_text = [chars copy]; + // chars of markedText by Input Method is an instance of NSAttributedString + if ([chars isKindOfClass:[NSAttributedString class]]) { + composing_text = [[chars string] copy]; + } + // if empty, cancel if ([composing_text length] == 0) [self composing_free]; + +#ifdef WITH_INPUT_IME + if (ime.state_flag & GHOST_IME_ENABLED) { + if (!(ime.state_flag & GHOST_IME_COMPOSING)) { + ime.state_flag |= GHOST_IME_COMPOSING; + [self processImeEvent:GHOST_kEventImeCompositionStart]; + } + + [self setImeComposition:composing_text selectedRange:range]; + + // For Korean input, setMarkedText may be executed twice with a single keyDown. + if (![self ime_did_composition]) { + ime.state_flag |= GHOST_IME_COMPOSITION_EVENT; + [self processImeEvent:GHOST_kEventImeComposition]; + } + } +#endif } - (void)unmarkText @@ -246,12 +366,8 @@ return composing; } -- (NSInteger)conversationIdentifier -{ - return (NSInteger)self; -} - -- (NSAttributedString *)attributedSubstringFromRange:(NSRange)range +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange { return [[[NSAttributedString alloc] init] autorelease]; } @@ -272,8 +388,14 @@ return NSMakeRange(0, length); } -- (NSRect)firstRectForCharacterRange:(NSRange)range +// Specify the position where the Chinese and Japanese candidate windows are displayed. +- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange { +#ifdef WITH_INPUT_IME + if (ime.state_flag & GHOST_IME_ENABLED) { + return ime.candidate_window_position; + } +#endif return NSZeroRect; } @@ -287,4 +409,165 @@ return [NSArray array]; } +#ifdef WITH_INPUT_IME +- (void)checkImeEnabled +{ + ime.state_flag &= ~GHOST_IME_ENABLED; + + if (ime.state_flag & GHOST_IME_INPUT_FOCUSED) { + /* Since there are no functions in Cocoa API, + * we will use the functions in the Carbon API. */ + TISInputSourceRef currentKeyboardInputSource = TISCopyCurrentKeyboardInputSource(); + bool ime_enabled = !CFBooleanGetValue((CFBooleanRef)TISGetInputSourceProperty( + currentKeyboardInputSource, kTISPropertyInputSourceIsASCIICapable)); + CFRelease(currentKeyboardInputSource); + + if (ime_enabled) { + ime.state_flag |= GHOST_IME_ENABLED; + return; + } + } + return; +} + +- (void)ImeDidChangeCallback:(NSNotification *)notification +{ + [self checkImeEnabled]; +} + +- (void)setImeCandidateWinPos:(int32_t)x y:(int32_t)y w:(int32_t)w h:(int32_t)h +{ + int32_t outX, outY; + associatedWindow->clientToScreen(x, y, outX, outY); + ime.candidate_window_position = NSMakeRect((CGFloat)outX, (CGFloat)outY, (CGFloat)w, (CGFloat)h); +} + +- (void)beginIME:(int32_t)x y:(int32_t)y w:(int32_t)w h:(int32_t)h completed:(bool)completed +{ + ime.state_flag |= GHOST_IME_INPUT_FOCUSED; + [self checkImeEnabled]; + [self setImeCandidateWinPos:x y:y w:w h:h]; +} + +- (void)endIME +{ + ime.state_flag = 0; + ime.result.clear(); + ime.composite.clear(); + + [self unmarkText]; + [[NSTextInputContext currentInputContext] discardMarkedText]; +} + +- (void)processImeEvent:(GHOST_TEventType)imeEventType +{ + ime.event.result_len = (GHOST_TUserDataPtr)ime.result.size(); + ime.event.result = (GHOST_TUserDataPtr)ime.result.c_str(); + ime.event.composite_len = (GHOST_TUserDataPtr)ime.composite.size(); + ime.event.composite = (GHOST_TUserDataPtr)ime.composite.c_str(); + + GHOST_Event *event = new GHOST_EventIME( + systemCocoa->getMilliSeconds(), imeEventType, associatedWindow, &ime.event); + systemCocoa->pushEvent(event); +} + +- (std::string)convertNSString:(NSString *)inString +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + std::string str([inString UTF8String]); + [pool drain]; + return str; +} + +- (void)setImeComposition:(NSString *)inString selectedRange:(NSRange)range +{ + ime.composite = [self convertNSString:inString]; + + // For Korean input, both "Result Event" and "Composition Event" can occur in a single keyDown. + if (!(ime.state_flag & GHOST_IME_RESULT_EVENT)) { + ime.result.clear(); + } + + /* The target string is equivalent to the string in selectedRange of setMarkedText. + * The cursor is displayed at the beginning of the target string. */ + char *front_string = (char *)[[inString substringWithRange:NSMakeRange(0, range.location)] + UTF8String]; + char *selected_string = (char *)[[inString substringWithRange:range] UTF8String]; + ime.event.cursor_position = strlen(front_string); + ime.event.target_start = ime.event.cursor_position; + ime.event.target_end = ime.event.target_start + strlen(selected_string); +} + +- (void)setImeResult:(std::string)result +{ + ime.result = result; + ime.composite.clear(); + ime.event.cursor_position = -1; + ime.event.target_start = -1; + ime.event.target_end = -1; +} + +- (void)checkKeyCodeIsControlChar:(NSEvent *)event +{ + ime.state_flag &= ~GHOST_IME_KEY_CONTROL_CHAR; + switch ([event keyCode]) { + case kVK_ANSI_KeypadEnter: + case kVK_ANSI_KeypadClear: + case kVK_F1: + case kVK_F2: + case kVK_F3: + case kVK_F4: + case kVK_F5: + case kVK_F6: + case kVK_F7: + case kVK_F8: + case kVK_F9: + case kVK_F10: + case kVK_F11: + case kVK_F12: + case kVK_F13: + case kVK_F14: + case kVK_F15: + case kVK_F16: + case kVK_F17: + case kVK_F18: + case kVK_F19: + case kVK_F20: + case kVK_UpArrow: + case kVK_DownArrow: + case kVK_LeftArrow: + case kVK_RightArrow: + case kVK_Return: + case kVK_Delete: + case kVK_ForwardDelete: + case kVK_Escape: + case kVK_Tab: + case kVK_Home: + case kVK_End: + case kVK_PageUp: + case kVK_PageDown: + case kVK_VolumeUp: + case kVK_VolumeDown: + case kVK_Mute: + ime.state_flag |= GHOST_IME_KEY_CONTROL_CHAR; + return; + } +} + +- (bool)ime_did_composition +{ + return (ime.state_flag & GHOST_IME_COMPOSITION_EVENT) || + (ime.state_flag & GHOST_IME_RESULT_EVENT); +} + +/* Even if IME is enabled, when not composing, control characters + * (such as arrow, enter, delete) are handled by handleKeyEvent. */ +- (bool)isProcessedByIme +{ + return ( + (ime.state_flag & GHOST_IME_ENABLED) && + ((ime.state_flag & GHOST_IME_COMPOSING) || !(ime.state_flag & GHOST_IME_KEY_CONTROL_CHAR))); +} +#endif /* WITH_INPUT_IME */ + @end |