[Bf-blender-cvs] [83e2f8c993d] master: macOS: support Japanese input for text buttons

Yuki Hashimoto noreply at git.blender.org
Mon Jul 5 17:26:07 CEST 2021


Commit: 83e2f8c993dc8068eb1145e9b8f4756a54f96144
Author: Yuki Hashimoto
Date:   Mon Jul 5 13:11:17 2021 +0200
Branches: master
https://developer.blender.org/rB83e2f8c993dc8068eb1145e9b8f4756a54f96144

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

===================================================================

M	CMakeLists.txt
M	build_files/cmake/config/blender_full.cmake
M	build_files/cmake/config/blender_release.cmake
M	intern/ghost/CMakeLists.txt
M	intern/ghost/intern/GHOST_WindowCocoa.h
M	intern/ghost/intern/GHOST_WindowCocoa.mm
M	intern/ghost/intern/GHOST_WindowViewCocoa.h
M	source/blender/blentranslation/CMakeLists.txt
M	source/blender/editors/interface/CMakeLists.txt
M	source/blender/makesdna/DNA_windowmanager_types.h
M	source/blender/windowmanager/CMakeLists.txt
M	source/blender/windowmanager/intern/wm.c

===================================================================

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

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list