Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'intern/ghost/intern/GHOST_WindowViewCocoa.h')
-rw-r--r--intern/ghost/intern/GHOST_WindowViewCocoa.h325
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