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:
authorYuki Hashimoto <hzuika>2021-07-05 14:11:17 +0300
committerBrecht Van Lommel <brecht@blender.org>2021-07-05 18:24:27 +0300
commit83e2f8c993dc8068eb1145e9b8f4756a54f96144 (patch)
tree5d3df9bb8e8f237c4336371f42f9bb2672107141
parentac1ed19eaefe356a1ae2ff3bfb471f940231ce06 (diff)
macOS: support Japanese input for text buttons
Blender did not support to input East Asian characters (Chinese, Japanese, Korean) on macOS. This patch adds support for Japanese input, by implementing the appropriate processing for the NSTextInputClient protocol. Technical notes: * The conversion candidate window is drawn by the input method program calling `firstRectForCharacterRange`. * The string before confirmation (called `composite` in blender) is handled in the `setMarkedText` method called by the input method program. * The string after confirmation (called `result` in the blender) is processed in the `insertText` method called by the input method program. Ref T51283 Differential Revision: https://developer.blender.org/D11695
-rw-r--r--CMakeLists.txt3
-rw-r--r--build_files/cmake/config/blender_full.cmake1
-rw-r--r--build_files/cmake/config/blender_release.cmake1
-rw-r--r--intern/ghost/CMakeLists.txt3
-rw-r--r--intern/ghost/intern/GHOST_WindowCocoa.h33
-rw-r--r--intern/ghost/intern/GHOST_WindowCocoa.mm23
-rw-r--r--intern/ghost/intern/GHOST_WindowViewCocoa.h265
-rw-r--r--source/blender/blentranslation/CMakeLists.txt2
-rw-r--r--source/blender/editors/interface/CMakeLists.txt2
-rw-r--r--source/blender/makesdna/DNA_windowmanager_types.h6
-rw-r--r--source/blender/windowmanager/CMakeLists.txt2
-rw-r--r--source/blender/windowmanager/intern/wm.c2
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