Blender V5.0
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
8
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 UTF8 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_.l_;
154 int y = caret_rect_.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_.l_ >= 0 && caret_rect_.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 }
316 for (target_end = target_start; target_end < attribute_size; ++target_end) {
317 if (!IsTargetAttribute(attribute_data[target_end])) {
318 break;
319 }
320 }
321 if (target_start == attribute_size) {
327 target_end = target_start;
328 target_start = 0;
329 }
330 if (target_start != -1 && target_start < attribute_size &&
331 attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED)
332 {
333 composition->cursor_position = target_start;
334 }
335 }
336 delete[] attribute_data;
337 }
338 }
339 }
340 composition->target_start = target_start;
341 composition->target_end = target_end;
342}
343
344bool GHOST_ImeWin32::GetString(HIMC imm_context,
345 WPARAM lparam,
346 int type,
347 ImeComposition *composition)
348{
349 bool result = false;
350 if (lparam & type) {
351 int string_size = ::ImmGetCompositionStringW(imm_context, type, nullptr, 0);
352 if (string_size > 0) {
353 int string_length = string_size / sizeof(wchar_t);
354 wchar_t *string_data = new wchar_t[string_length + 1];
355 string_data[string_length] = '\0';
356 if (string_data) {
357 /* Fill the given ImeComposition object. */
358 ::ImmGetCompositionStringW(imm_context, type, string_data, string_size);
359 composition->string_type = type;
360 composition->ime_string = string_data;
361 result = true;
362 }
363 delete[] string_data;
364 }
365 }
366 return result;
367}
368
369bool GHOST_ImeWin32::GetResult(HWND window_handle, LPARAM lparam, ImeComposition *composition)
370{
371 bool result = false;
372 HIMC imm_context = ::ImmGetContext(window_handle);
373 if (imm_context) {
374 /* Copy the result string to the ImeComposition object. */
375 result = GetString(imm_context, lparam, GCS_RESULTSTR, composition);
380 composition->cursor_position = -1;
381 composition->target_start = -1;
382 composition->target_end = -1;
383 ::ImmReleaseContext(window_handle, imm_context);
384 }
385 return result;
386}
387
388bool GHOST_ImeWin32::GetComposition(HWND window_handle, LPARAM lparam, ImeComposition *composition)
389{
390 bool result = false;
391 HIMC imm_context = ::ImmGetContext(window_handle);
392 if (imm_context) {
393 /* Copy the composition string to the ImeComposition object. */
394 result = GetString(imm_context, lparam, GCS_COMPSTR, composition);
395
396 /* Retrieve the cursor position in the IME composition. */
397 int cursor_position = ::ImmGetCompositionStringW(imm_context, GCS_CURSORPOS, nullptr, 0);
398 composition->cursor_position = cursor_position;
399 composition->target_start = -1;
400 composition->target_end = -1;
401
402 /* Retrieve the target selection and Update the ImeComposition object. */
403 GetCaret(imm_context, lparam, composition);
404
405 /* Mark that there is an ongoing composition. */
406 is_composing_ = true;
407
408 ::ImmReleaseContext(window_handle, imm_context);
409 }
410 return result;
411}
412
413void GHOST_ImeWin32::EndIME(HWND window_handle)
414{
422 if (!is_enable) {
423 return;
424 }
425 is_enable = false;
426 if (is_composing_) {
427 HIMC imm_context = ::ImmGetContext(window_handle);
428 if (imm_context) {
429 /* Cancel composition */
430 ::ImmNotifyIME(imm_context, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
431 ::ImmNotifyIME(imm_context, NI_CLOSECANDIDATE, 0, 0);
432 ::ImmReleaseContext(window_handle, imm_context);
433 }
434 ResetComposition(window_handle);
435 }
436 ::ImmAssociateContextEx(window_handle, nullptr, 0);
437 eventImeData.composite.clear();
438}
439
440void GHOST_ImeWin32::BeginIME(HWND window_handle, const GHOST_Rect &caret_rect, bool complete)
441{
442 if (is_enable && complete) {
443 return;
444 }
445 is_enable = true;
452 ::ImmAssociateContextEx(window_handle, nullptr, IACE_DEFAULT);
453 /* Complete the ongoing composition and move the IME windows. */
454 HIMC imm_context = ::ImmGetContext(window_handle);
455 if (imm_context) {
456 if (complete) {
465 CompleteComposition(window_handle, imm_context);
466 }
472 if (caret_rect.l_ >= 0 && caret_rect.t_ >= 0) {
473 caret_rect_ = caret_rect;
474 MoveImeWindow(window_handle, imm_context);
475 }
476 ::ImmReleaseContext(window_handle, imm_context);
477 }
478}
479
480static void convert_utf16_to_utf8_len(std::wstring s, int &len)
481{
482 if (len >= 0 && len <= s.size()) {
483 len = count_utf_8_from_16(s.substr(0, len).c_str()) - 1;
484 }
485 else {
486 len = -1;
487 }
488}
489
490static size_t updateUtf8Buf(ImeComposition &info)
491{
492 size_t len = count_utf_8_from_16(info.ime_string.c_str());
493 info.utf8_buf.resize(len);
494 conv_utf_16_to_8(info.ime_string.c_str(), &info.utf8_buf[0], len);
495 convert_utf16_to_utf8_len(info.ime_string, info.cursor_position);
496 convert_utf16_to_utf8_len(info.ime_string, info.target_start);
497 convert_utf16_to_utf8_len(info.ime_string, info.target_end);
498 return len - 1;
499}
500
501void GHOST_ImeWin32::UpdateInfo(HWND window_handle)
502{
503 int res = this->GetResult(window_handle, GCS_RESULTSTR, &resultInfo);
504 int comp = this->GetComposition(window_handle, GCS_COMPSTR | GCS_COMPATTR, &compInfo);
505 /* Convert wchar to UTF8. */
506 if (res) {
507 updateUtf8Buf(resultInfo);
508 eventImeData.result = std::string(&resultInfo.utf8_buf[0]);
509 }
510 else {
511 eventImeData.result = "";
512 }
513 if (comp) {
514 updateUtf8Buf(compInfo);
515 eventImeData.composite = std::string(&compInfo.utf8_buf[0]);
516 eventImeData.cursor_position = compInfo.cursor_position;
517 eventImeData.target_start = compInfo.target_start;
518 eventImeData.target_end = compInfo.target_end;
519 }
520 else {
521 eventImeData.composite = "";
522 eventImeData.cursor_position = -1;
523 eventImeData.target_start = -1;
524 eventImeData.target_end = -1;
525 }
526}
527
528#endif /* WITH_INPUT_IME */
GHOST C-API function and type declarations.
GHOST_TKey
@ GHOST_kKeyNumpadPeriod
return true
int32_t t_
int32_t l_
const char * name
i
Definition text_draw.cc:230
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
uint len