diff options
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | build_files/cmake/config/blender_full.cmake | 1 | ||||
-rw-r--r-- | build_files/cmake/config/blender_release.cmake | 1 | ||||
-rw-r--r-- | intern/ghost/CMakeLists.txt | 3 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowCocoa.h | 33 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowCocoa.mm | 23 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowViewCocoa.h | 265 | ||||
-rw-r--r-- | source/blender/blentranslation/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/editors/interface/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_windowmanager_types.h | 6 | ||||
-rw-r--r-- | source/blender/windowmanager/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm.c | 2 |
12 files changed, 330 insertions, 13 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 297e32bd67e..91ac63d5e50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -349,7 +349,7 @@ mark_as_advanced(WITH_SYSTEM_GLOG) option(WITH_FREESTYLE "Enable Freestyle (advanced edges rendering)" ON) # Misc -if(WIN32) +if(WIN32 OR APPLE) option(WITH_INPUT_IME "Enable Input Method Editor (IME) for complex Asian character input" ON) endif() option(WITH_INPUT_NDOF "Enable NDOF input devices (SpaceNavigator and friends)" ON) @@ -1915,6 +1915,7 @@ if(FIRST_RUN) info_cfg_option(WITH_IK_ITASC) info_cfg_option(WITH_IK_SOLVER) info_cfg_option(WITH_INPUT_NDOF) + info_cfg_option(WITH_INPUT_IME) info_cfg_option(WITH_INTERNATIONAL) info_cfg_option(WITH_OPENCOLLADA) info_cfg_option(WITH_OPENCOLORIO) diff --git a/build_files/cmake/config/blender_full.cmake b/build_files/cmake/config/blender_full.cmake index ee6413774aa..ccd5b47c776 100644 --- a/build_files/cmake/config/blender_full.cmake +++ b/build_files/cmake/config/blender_full.cmake @@ -29,6 +29,7 @@ set(WITH_IMAGE_OPENEXR ON CACHE BOOL "" FORCE) set(WITH_IMAGE_OPENJPEG ON CACHE BOOL "" FORCE) set(WITH_IMAGE_TIFF ON CACHE BOOL "" FORCE) set(WITH_INPUT_NDOF ON CACHE BOOL "" FORCE) +set(WITH_INPUT_IME ON CACHE BOOL "" FORCE) set(WITH_INTERNATIONAL ON CACHE BOOL "" FORCE) set(WITH_LIBMV ON CACHE BOOL "" FORCE) set(WITH_LIBMV_SCHUR_SPECIALIZATIONS ON CACHE BOOL "" FORCE) diff --git a/build_files/cmake/config/blender_release.cmake b/build_files/cmake/config/blender_release.cmake index 75aafd45c82..b8180d733de 100644 --- a/build_files/cmake/config/blender_release.cmake +++ b/build_files/cmake/config/blender_release.cmake @@ -30,6 +30,7 @@ set(WITH_IMAGE_OPENEXR ON CACHE BOOL "" FORCE) set(WITH_IMAGE_OPENJPEG ON CACHE BOOL "" FORCE) set(WITH_IMAGE_TIFF ON CACHE BOOL "" FORCE) set(WITH_INPUT_NDOF ON CACHE BOOL "" FORCE) +set(WITH_INPUT_IME ON CACHE BOOL "" FORCE) set(WITH_INTERNATIONAL ON CACHE BOOL "" FORCE) set(WITH_LIBMV ON CACHE BOOL "" FORCE) set(WITH_LIBMV_SCHUR_SPECIALIZATIONS ON CACHE BOOL "" FORCE) diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 77ec307e604..e98faf522e6 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -155,6 +155,9 @@ if(WITH_HEADLESS OR WITH_GHOST_SDL) endif() elseif(APPLE AND NOT WITH_GHOST_X11) + if(WITH_INPUT_IME) + add_definitions(-DWITH_INPUT_IME) + endif() list(APPEND SRC intern/GHOST_DisplayManagerCocoa.mm intern/GHOST_SystemCocoa.mm diff --git a/intern/ghost/intern/GHOST_WindowCocoa.h b/intern/ghost/intern/GHOST_WindowCocoa.h index 3cfe46a080b..fdc806e2167 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.h +++ b/intern/ghost/intern/GHOST_WindowCocoa.h @@ -29,6 +29,9 @@ #endif // __APPLE__ #include "GHOST_Window.h" +#ifdef WITH_INPUT_IME +# include "GHOST_Event.h" +#endif @class CAMetalLayer; @class CocoaMetalView; @@ -263,6 +266,11 @@ class GHOST_WindowCocoa : public GHOST_Window { return m_immediateDraw; } +#ifdef WITH_INPUT_IME + void beginIME(GHOST_TInt32 x, GHOST_TInt32 y, GHOST_TInt32 w, GHOST_TInt32 h, int completed); + void endIME(); +#endif /* WITH_INPUT_IME */ + protected: /** * \param type: The type of rendering context create. @@ -326,3 +334,28 @@ class GHOST_WindowCocoa : public GHOST_Window { bool m_debug_context; // for debug messages during context setup bool m_is_dialog; }; + +#ifdef WITH_INPUT_IME +class GHOST_EventIME : public GHOST_Event { + public: + /** + * Constructor. + * \param msec: The time this event was generated. + * \param type: The type of key event. + * \param key: The key code of the key. + */ + GHOST_EventIME(GHOST_TUns64 msec, GHOST_TEventType type, GHOST_IWindow *window, void *customdata) + : GHOST_Event(msec, type, window) + { + this->m_data = customdata; + } +}; + +typedef int GHOST_ImeStateFlagCocoa; +enum { + GHOST_IME_INPUT_FOCUSED = (1 << 0), + GHOST_IME_ENABLED = (1 << 1), + GHOST_IME_COMPOSING = (1 << 2), + GHOST_IME_KEY_CONTROL_CHAR = (1 << 3) +}; +#endif /* WITH_INPUT_IME */ diff --git a/intern/ghost/intern/GHOST_WindowCocoa.mm b/intern/ghost/intern/GHOST_WindowCocoa.mm index d082fa99ad8..cea2969739c 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.mm +++ b/intern/ghost/intern/GHOST_WindowCocoa.mm @@ -1221,3 +1221,26 @@ GHOST_TSuccess GHOST_WindowCocoa::setWindowCustomCursorShape(GHOST_TUns8 *bitmap [pool drain]; return GHOST_kSuccess; } + +#ifdef WITH_INPUT_IME +void GHOST_WindowCocoa::beginIME( + GHOST_TInt32 x, GHOST_TInt32 y, GHOST_TInt32 w, GHOST_TInt32 h, int completed) +{ + if (m_openGLView) { + [m_openGLView beginIME:x y:y w:w h:h completed:(bool)completed]; + } + else { + [m_metalView beginIME:x y:y w:w h:h completed:(bool)completed]; + } +} + +void GHOST_WindowCocoa::endIME() +{ + if (m_openGLView) { + [m_openGLView endIME]; + } + else { + [m_metalView endIME]; + } +} +#endif /* WITH_INPUT_IME */ diff --git a/intern/ghost/intern/GHOST_WindowViewCocoa.h b/intern/ghost/intern/GHOST_WindowViewCocoa.h index af84297626b..42f0fcc6ac6 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 @@ -33,10 +38,30 @@ 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; + } ime; +#endif } - (void)setSystemAndWindowCocoa:(GHOST_SystemCocoa *)sysCocoa windowCocoa:(GHOST_WindowCocoa *)winCocoa; + +#ifdef WITH_INPUT_IME +- (void)beginIME:(GHOST_TInt32)x + y:(GHOST_TInt32)y + w:(GHOST_TInt32)w + h:(GHOST_TInt32)h + completed:(bool)completed; + +- (void)endIME; +#endif @end @implementation COCOA_VIEW_CLASS @@ -50,7 +75,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,11 +105,20 @@ // 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 @@ -202,26 +250,72 @@ } } +// 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]; + } + + [self setImeResult:[self convertNSString:chars]]; + + [self processImeEvent:GHOST_kEventImeComposition]; + [self processImeEvent:GHOST_kEventImeCompositionEnd]; + ime.state_flag &= ~GHOST_IME_COMPOSING; + } +#endif } +// 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]; + + [self processImeEvent:GHOST_kEventImeComposition]; + } +#endif } - (void)unmarkText @@ -265,8 +359,14 @@ return NSMakeRange(0, length); } +// 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; } @@ -280,4 +380,159 @@ 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:(GHOST_TInt32)x y:(GHOST_TInt32)y w:(GHOST_TInt32)w h:(GHOST_TInt32)h +{ + GHOST_TInt32 outX, outY; + associatedWindow->clientToScreen(x, y, outX, outY); + ime.candidate_window_position = NSMakeRect((CGFloat)outX, (CGFloat)outY, (CGFloat)w, (CGFloat)h); +} + +- (void)beginIME:(GHOST_TInt32)x + y:(GHOST_TInt32)y + w:(GHOST_TInt32)w + h:(GHOST_TInt32)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]; + 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; + } +} + +/* 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 diff --git a/source/blender/blentranslation/CMakeLists.txt b/source/blender/blentranslation/CMakeLists.txt index 70e68ca06d7..bfb812ac64d 100644 --- a/source/blender/blentranslation/CMakeLists.txt +++ b/source/blender/blentranslation/CMakeLists.txt @@ -59,7 +59,7 @@ if(WITH_PYTHON) ) endif() -if(WIN32) +if(WIN32 OR APPLE) if(WITH_INPUT_IME) add_definitions(-DWITH_INPUT_IME) endif() diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 5011a50ed73..2cc3830042c 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -102,7 +102,7 @@ if(WITH_PYTHON) add_definitions(-DWITH_PYTHON) endif() -if(WIN32) +if(WIN32 OR APPLE) if(WITH_INPUT_IME) add_definitions(-DWITH_INPUT_IME) endif() diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index cb5c62e4507..94e89944f08 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -217,8 +217,8 @@ enum { #define WM_KEYCONFIG_STR_DEFAULT "Blender" -/* IME is win32 only! */ -#if !defined(WIN32) && !defined(DNA_DEPRECATED) +/* IME is win32 and apple only! */ +#if !(defined(WIN32) || defined(__APPLE__)) && !defined(DNA_DEPRECATED) # ifdef __GNUC__ # define ime_data ime_data __attribute__((deprecated)) # endif @@ -302,7 +302,7 @@ typedef struct wmWindow { struct wmGesture *tweak; /* Input Method Editor data - complex character input (especially for Asian character input) - * Currently WIN32, runtime-only data. */ + * Currently WIN32 and APPLE, runtime-only data. */ struct wmIMEData *ime_data; /** All events #wmEvent (ghost level events were handled). */ diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 183b22c9791..e513c49c11b 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -181,7 +181,7 @@ if(WITH_INPUT_NDOF) add_definitions(-DWITH_INPUT_NDOF) endif() -if(WIN32) +if(WIN32 OR APPLE) if(WITH_INPUT_IME) add_definitions(-DWITH_INPUT_IME) endif() diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index d59ebabc643..9657f8aa03c 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -168,7 +168,7 @@ static void window_manager_blend_read_data(BlendDataReader *reader, ID *id) win->eventstate = NULL; win->cursor_keymap_status = NULL; win->tweak = NULL; -#ifdef WIN32 +#if defined(WIN32) || defined(__APPLE__) win->ime_data = NULL; #endif |