Blender V4.3
GHOST_ImeWin32.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2010 The Chromium Authors. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#ifdef WITH_INPUT_IME
10
11# include "GHOST_ImeWin32.hh"
12# include "GHOST_C-api.h"
13# include "GHOST_WindowWin32.hh"
14# include "utfconv.hh"
15
16/* ISO_639-1 2-Letter Abbreviations. */
17# define IMELANG_ENGLISH "en"
18# define IMELANG_CHINESE "zh"
19# define IMELANG_JAPANESE "ja"
20# define IMELANG_KOREAN "ko"
21
22GHOST_ImeWin32::GHOST_ImeWin32()
23 : is_composing_(false),
24 language_(IMELANG_ENGLISH),
25 conversion_modes_(IME_CMODE_ALPHANUMERIC),
26 sentence_mode_(IME_SMODE_NONE),
27 system_caret_(false),
28 caret_rect_(-1, -1, 0, 0),
29 is_first(true),
30 is_enable(true)
31{
32}
33
34GHOST_ImeWin32::~GHOST_ImeWin32() {}
35
36void GHOST_ImeWin32::UpdateInputLanguage()
37{
38 /* Get the current input locale full name. */
39 WCHAR locale[LOCALE_NAME_MAX_LENGTH];
40 LCIDToLocaleName(
41 MAKELCID(LOWORD(::GetKeyboardLayout(0)), SORT_DEFAULT), locale, LOCALE_NAME_MAX_LENGTH, 0);
42 /* Get the 2-letter ISO-63901 abbreviation of the input locale name. */
43 WCHAR language_u16[W32_ISO639_LEN];
44 GetLocaleInfoEx(locale, LOCALE_SISO639LANGNAME, language_u16, W32_ISO639_LEN);
45 /* Store this as a UTF-8 string. */
46 WideCharToMultiByte(
47 CP_UTF8, 0, language_u16, W32_ISO639_LEN, language_, W32_ISO639_LEN, nullptr, nullptr);
48}
49
50BOOL GHOST_ImeWin32::IsLanguage(const char name[W32_ISO639_LEN])
51{
52 return (strcmp(name, language_) == 0);
53}
54
55void GHOST_ImeWin32::UpdateConversionStatus(HWND window_handle)
56{
57 HIMC imm_context = ::ImmGetContext(window_handle);
58 if (imm_context) {
59 if (::ImmGetOpenStatus(imm_context)) {
60 ::ImmGetConversionStatus(imm_context, &conversion_modes_, &sentence_mode_);
61 }
62 else {
63 conversion_modes_ = IME_CMODE_ALPHANUMERIC;
64 sentence_mode_ = IME_SMODE_NONE;
65 }
66 ::ImmReleaseContext(window_handle, imm_context);
67 }
68 else {
69 conversion_modes_ = IME_CMODE_ALPHANUMERIC;
70 sentence_mode_ = IME_SMODE_NONE;
71 }
72}
73
74bool GHOST_ImeWin32::IsEnglishMode()
75{
76 return (conversion_modes_ & IME_CMODE_NOCONVERSION) ||
77 !(conversion_modes_ & (IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE));
78}
79
80bool GHOST_ImeWin32::IsImeKeyEvent(char ascii, GHOST_TKey key)
81{
82 if (!(IsEnglishMode())) {
83 /* In Chinese, Japanese, Korean, all alpha keys are processed by IME. */
84 if ((ascii >= 'A' && ascii <= 'Z') || (ascii >= 'a' && ascii <= 'z')) {
85 return true;
86 }
87 if (IsLanguage(IMELANG_JAPANESE) && (ascii >= ' ' && ascii <= '~')) {
88 return true;
89 }
90 if (IsLanguage(IMELANG_CHINESE)) {
91 if (ascii && strchr("!\"$'(),.:;<>?[\\]^_`/", ascii) && !(key == GHOST_kKeyNumpadPeriod)) {
92 return true;
93 }
94 if (conversion_modes_ & IME_CMODE_FULLSHAPE && (ascii >= '0' && ascii <= '9')) {
95 /* When in Full Width mode the number keys are also converted. */
96 return true;
97 }
98 }
99 }
100 return false;
101}
102
103void GHOST_ImeWin32::CreateImeWindow(HWND window_handle)
104{
117 if (!system_caret_ && (IsLanguage(IMELANG_CHINESE) || IsLanguage(IMELANG_JAPANESE))) {
118 system_caret_ = ::CreateCaret(window_handle, nullptr, 1, 1);
119 }
120 /* Restore the positions of the IME windows. */
121 UpdateImeWindow(window_handle);
122}
123
124void GHOST_ImeWin32::SetImeWindowStyle(
125 HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam, BOOL *handled)
126{
137 *handled = TRUE;
138 lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
139 ::DefWindowProc(window_handle, message, wparam, lparam);
140}
141
142void GHOST_ImeWin32::DestroyImeWindow(HWND /*window_handle*/)
143{
144 /* Destroy the system caret if we have created for this IME input context. */
145 if (system_caret_) {
146 ::DestroyCaret();
147 system_caret_ = false;
148 }
149}
150
151void GHOST_ImeWin32::MoveImeWindow(HWND /*window_handle*/, HIMC imm_context)
152{
153 int x = caret_rect_.m_l;
154 int y = caret_rect_.m_t;
155 const int kCaretMargin = 1;
168 CANDIDATEFORM candidate_position = {0, CFS_CANDIDATEPOS, {x, y}, {0, 0, 0, 0}};
169 ::ImmSetCandidateWindow(imm_context, &candidate_position);
170 if (system_caret_) {
171 ::SetCaretPos(x, y);
172 }
173 if (IsLanguage(IMELANG_KOREAN)) {
180 y += kCaretMargin;
181 }
188 CANDIDATEFORM exclude_rectangle = {
189 0, CFS_EXCLUDE, {x, y}, {x, y, x + caret_rect_.getWidth(), y + caret_rect_.getHeight()}};
190 ::ImmSetCandidateWindow(imm_context, &exclude_rectangle);
191}
192
193void GHOST_ImeWin32::UpdateImeWindow(HWND window_handle)
194{
195 /* Just move the IME window attached to the given window. */
196 if (caret_rect_.m_l >= 0 && caret_rect_.m_t >= 0) {
197 HIMC imm_context = ::ImmGetContext(window_handle);
198 if (imm_context) {
199 MoveImeWindow(window_handle, imm_context);
200 ::ImmReleaseContext(window_handle, imm_context);
201 }
202 }
203}
204
205void GHOST_ImeWin32::CleanupComposition(HWND window_handle)
206{
212 if (is_composing_) {
213 HIMC imm_context = ::ImmGetContext(window_handle);
214 if (imm_context) {
215 ::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
216 ::ImmReleaseContext(window_handle, imm_context);
217 }
218 ResetComposition(window_handle);
219 }
220}
221
222void GHOST_ImeWin32::CheckFirst(HWND window_handle)
223{
224 if (is_first) {
225 this->EndIME(window_handle);
226 is_first = false;
227 }
228}
229
230void GHOST_ImeWin32::ResetComposition(HWND /*window_handle*/)
231{
232 /* Currently, just reset the composition status. */
233 is_composing_ = false;
234}
235
236void GHOST_ImeWin32::CompleteComposition(HWND window_handle, HIMC imm_context)
237{
243 if (is_composing_) {
244 ::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
245 ResetComposition(window_handle);
246 }
247}
248
249void GHOST_ImeWin32::GetCaret(HIMC imm_context, LPARAM lparam, ImeComposition *composition)
250{
261 int target_start = -1;
262 int target_end = -1;
263 if (IsLanguage(IMELANG_KOREAN)) {
264 if (lparam & CS_NOMOVECARET) {
265 target_start = 0;
266 target_end = 1;
267 }
268 }
269 else if (IsLanguage(IMELANG_CHINESE)) {
270 int clause_size = ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, nullptr, 0);
271 if (clause_size) {
272 static std::vector<ulong> clauses;
273 clause_size = clause_size / sizeof(clauses[0]);
274 clauses.resize(clause_size);
275 ImmGetCompositionStringW(
276 imm_context, GCS_COMPCLAUSE, &clauses[0], sizeof(clauses[0]) * clause_size);
277 if (composition->cursor_position == composition->ime_string.size()) {
278 target_start = clauses[clause_size - 2];
279 target_end = clauses[clause_size - 1];
280 }
281 else {
282 for (int i = 0; i < clause_size - 1; i++) {
283 if (clauses[i] == composition->cursor_position) {
284 target_start = clauses[i];
285 target_end = clauses[i + 1];
286 break;
287 }
288 }
289 }
290 }
291 else {
292 if (composition->cursor_position != -1) {
293 target_start = composition->cursor_position;
294 target_end = composition->ime_string.size();
295 }
296 }
297 }
298 else if (IsLanguage(IMELANG_JAPANESE)) {
305 if (lparam & GCS_COMPATTR) {
306 int attribute_size = ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, nullptr, 0);
307 if (attribute_size > 0) {
308 char *attribute_data = new char[attribute_size];
309 if (attribute_data) {
310 ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, attribute_data, attribute_size);
311 for (target_start = 0; target_start < attribute_size; ++target_start) {
312 if (IsTargetAttribute(attribute_data[target_start]))
313 break;
314 }
315 for (target_end = target_start; target_end < attribute_size; ++target_end) {
316 if (!IsTargetAttribute(attribute_data[target_end]))
317 break;
318 }
319 if (target_start == attribute_size) {
325 target_end = target_start;
326 target_start = 0;
327 }
328 if (target_start != -1 && target_start < attribute_size &&
329 attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED)
330 {
331 composition->cursor_position = target_start;
332 }
333 }
334 delete[] attribute_data;
335 }
336 }
337 }
338 composition->target_start = target_start;
339 composition->target_end = target_end;
340}
341
342bool GHOST_ImeWin32::GetString(HIMC imm_context,
343 WPARAM lparam,
344 int type,
345 ImeComposition *composition)
346{
347 bool result = false;
348 if (lparam & type) {
349 int string_size = ::ImmGetCompositionStringW(imm_context, type, nullptr, 0);
350 if (string_size > 0) {
351 int string_length = string_size / sizeof(wchar_t);
352 wchar_t *string_data = new wchar_t[string_length + 1];
353 string_data[string_length] = '\0';
354 if (string_data) {
355 /* Fill the given ImeComposition object. */
356 ::ImmGetCompositionStringW(imm_context, type, string_data, string_size);
357 composition->string_type = type;
358 composition->ime_string = string_data;
359 result = true;
360 }
361 delete[] string_data;
362 }
363 }
364 return result;
365}
366
367bool GHOST_ImeWin32::GetResult(HWND window_handle, LPARAM lparam, ImeComposition *composition)
368{
369 bool result = false;
370 HIMC imm_context = ::ImmGetContext(window_handle);
371 if (imm_context) {
372 /* Copy the result string to the ImeComposition object. */
373 result = GetString(imm_context, lparam, GCS_RESULTSTR, composition);
378 composition->cursor_position = -1;
379 composition->target_start = -1;
380 composition->target_end = -1;
381 ::ImmReleaseContext(window_handle, imm_context);
382 }
383 return result;
384}
385
386bool GHOST_ImeWin32::GetComposition(HWND window_handle, LPARAM lparam, ImeComposition *composition)
387{
388 bool result = false;
389 HIMC imm_context = ::ImmGetContext(window_handle);
390 if (imm_context) {
391 /* Copy the composition string to the ImeComposition object. */
392 result = GetString(imm_context, lparam, GCS_COMPSTR, composition);
393
394 /* Retrieve the cursor position in the IME composition. */
395 int cursor_position = ::ImmGetCompositionStringW(imm_context, GCS_CURSORPOS, nullptr, 0);
396 composition->cursor_position = cursor_position;
397 composition->target_start = -1;
398 composition->target_end = -1;
399
400 /* Retrieve the target selection and Update the ImeComposition object. */
401 GetCaret(imm_context, lparam, composition);
402
403 /* Mark that there is an ongoing composition. */
404 is_composing_ = true;
405
406 ::ImmReleaseContext(window_handle, imm_context);
407 }
408 return result;
409}
410
411void GHOST_ImeWin32::EndIME(HWND window_handle)
412{
420 if (!is_enable)
421 return;
422 is_enable = false;
423 CleanupComposition(window_handle);
424 ::ImmAssociateContextEx(window_handle, nullptr, 0);
425 eventImeData.composite_len = 0;
426}
427
428void GHOST_ImeWin32::BeginIME(HWND window_handle, const GHOST_Rect &caret_rect, bool complete)
429{
430 if (is_enable && complete)
431 return;
432 is_enable = true;
439 ::ImmAssociateContextEx(window_handle, nullptr, IACE_DEFAULT);
440 /* Complete the ongoing composition and move the IME windows. */
441 HIMC imm_context = ::ImmGetContext(window_handle);
442 if (imm_context) {
443 if (complete) {
452 CompleteComposition(window_handle, imm_context);
453 }
459 if (caret_rect.m_l >= 0 && caret_rect.m_t >= 0) {
460 caret_rect_ = caret_rect;
461 MoveImeWindow(window_handle, imm_context);
462 }
463 ::ImmReleaseContext(window_handle, imm_context);
464 }
465}
466
467static void convert_utf16_to_utf8_len(std::wstring s, int &len)
468{
469 if (len >= 0 && len <= s.size())
470 len = count_utf_8_from_16(s.substr(0, len).c_str()) - 1;
471 else
472 len = -1;
473}
474
475static size_t updateUtf8Buf(ImeComposition &info)
476{
477 size_t len = count_utf_8_from_16(info.ime_string.c_str());
478 info.utf8_buf.resize(len);
479 conv_utf_16_to_8(info.ime_string.c_str(), &info.utf8_buf[0], len);
480 convert_utf16_to_utf8_len(info.ime_string, info.cursor_position);
481 convert_utf16_to_utf8_len(info.ime_string, info.target_start);
482 convert_utf16_to_utf8_len(info.ime_string, info.target_end);
483 return len - 1;
484}
485
486void GHOST_ImeWin32::UpdateInfo(HWND window_handle)
487{
488 int res = this->GetResult(window_handle, GCS_RESULTSTR, &resultInfo);
489 int comp = this->GetComposition(window_handle, GCS_COMPSTR | GCS_COMPATTR, &compInfo);
490 /* convert wchar to utf8 */
491 if (res) {
492 eventImeData.result_len = (GHOST_TUserDataPtr)updateUtf8Buf(resultInfo);
493 eventImeData.result = &resultInfo.utf8_buf[0];
494 }
495 else {
496 eventImeData.result = 0;
497 eventImeData.result_len = 0;
498 }
499 if (comp) {
500 eventImeData.composite_len = (GHOST_TUserDataPtr)updateUtf8Buf(compInfo);
501 eventImeData.composite = &compInfo.utf8_buf[0];
502 eventImeData.cursor_position = compInfo.cursor_position;
503 eventImeData.target_start = compInfo.target_start;
504 eventImeData.target_end = compInfo.target_end;
505 }
506 else {
507 eventImeData.composite = 0;
508 eventImeData.composite_len = 0;
509 eventImeData.cursor_position = -1;
510 eventImeData.target_start = -1;
511 eventImeData.target_end = -1;
512 }
513}
514
515#endif /* WITH_INPUT_IME */
GHOST C-API function and type declarations.
void * GHOST_TUserDataPtr
Definition GHOST_Types.h:85
GHOST_TKey
@ GHOST_kKeyNumpadPeriod
int32_t m_l
int32_t m_t
int len
size_t count_utf_8_from_16(const wchar_t *string16)
Definition utfconv.cc:11
int conv_utf_16_to_8(const wchar_t *in16, char *out8, size_t size8)
Definition utfconv.cc:116