Win32 IME: Replace Usage of Language IDs

This is a slight refactoring of the Win32 IME code to remove the use of
Language IDs, which is now strongly deprecated. Instead this uses the
new recommended Locale Names, ie ISO-639-1 2-letter abbreviated names
like "en" for English rather than ID 0x09.

See D12143 for more details.

Differential Revision: https://developer.blender.org/D12143

Reviewed by Ray Molenkamp
This commit is contained in:
Harley Acheson 2021-08-10 08:52:36 -07:00
parent 6806459246
commit 946da86e02
2 changed files with 103 additions and 139 deletions

@ -30,9 +30,15 @@
# include "GHOST_WindowWin32.h"
# include "utfconv.h"
/* ISO_639-1 2-Letter Abbreviations. */
# define IMELANG_ENGLISH "en"
# define IMELANG_CHINESE "zh"
# define IMELANG_JAPANESE "ja"
# define IMELANG_KOREAN "ko"
GHOST_ImeWin32::GHOST_ImeWin32()
: is_composing_(false),
input_language_id_(LANG_USER_DEFAULT),
language_(IMELANG_ENGLISH),
conversion_modes_(IME_CMODE_ALPHANUMERIC),
sentence_mode_(IME_SMODE_NONE),
system_caret_(false),
@ -48,16 +54,21 @@ GHOST_ImeWin32::~GHOST_ImeWin32()
void GHOST_ImeWin32::UpdateInputLanguage()
{
/**
* Store the current input language.
*/
HKL input_locale = ::GetKeyboardLayout(0);
input_language_id_ = LOWORD(input_locale);
/* Get the current input locale full name. */
WCHAR locale[LOCALE_NAME_MAX_LENGTH];
LCIDToLocaleName(
MAKELCID(LOWORD(::GetKeyboardLayout(0)), SORT_DEFAULT), locale, LOCALE_NAME_MAX_LENGTH, 0);
/* Get the 2-letter ISO-63901 abbreviation of the input locale name. */
WCHAR language_u16[W32_ISO639_LEN];
GetLocaleInfoEx(locale, LOCALE_SISO639LANGNAME, language_u16, W32_ISO639_LEN);
/* Store this as a UTF-8 string. */
WideCharToMultiByte(
CP_UTF8, 0, language_u16, W32_ISO639_LEN, language_, W32_ISO639_LEN, NULL, NULL);
}
WORD GHOST_ImeWin32::GetInputLanguage()
BOOL GHOST_ImeWin32::IsLanguage(const char name[W32_ISO639_LEN])
{
return input_language_id_;
return (strcmp(name, language_) == 0);
}
void GHOST_ImeWin32::UpdateConversionStatus(HWND window_handle)
@ -92,21 +103,11 @@ bool GHOST_ImeWin32::IsImeKeyEvent(char ascii)
if ((ascii >= 'A' && ascii <= 'Z') || (ascii >= 'a' && ascii <= 'z')) {
return true;
}
switch (PRIMARYLANGID(GetInputLanguage())) {
/* In Japanese, all symbolic characters are also processed by IME. */
case LANG_JAPANESE: {
if (ascii >= ' ' && ascii <= '~') {
return true;
}
break;
}
/* In Chinese, some symbolic characters are also processed by IME. */
case LANG_CHINESE: {
if (ascii && strchr("!\"$'(),.:;<>?[\\]^_`", ascii)) {
return true;
}
break;
}
if (IsLanguage(IMELANG_JAPANESE) && (ascii >= ' ' && ascii <= '~')) {
return true;
}
else if (IsLanguage(IMELANG_CHINESE) && ascii && strchr("!\"$'(),.:;<>?[\\]^_`", ascii)) {
return true;
}
}
return false;
@ -126,13 +127,8 @@ void GHOST_ImeWin32::CreateImeWindow(HWND window_handle)
* Since some third-party Japanese IME also uses ::GetCaretPos() to determine
* their window position, we also create a caret for Japanese IMEs.
*/
if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE ||
PRIMARYLANGID(input_language_id_) == LANG_JAPANESE) {
if (!system_caret_) {
if (::CreateCaret(window_handle, NULL, 1, 1)) {
system_caret_ = true;
}
}
if (!system_caret_ && (IsLanguage(IMELANG_CHINESE) || IsLanguage(IMELANG_JAPANESE))) {
system_caret_ = ::CreateCaret(window_handle, NULL, 1, 1);
}
/* Restore the positions of the IME windows. */
UpdateImeWindow(window_handle);
@ -185,16 +181,9 @@ void GHOST_ImeWin32::MoveImeWindow(HWND window_handle, HIMC imm_context)
CANDIDATEFORM candidate_position = {0, CFS_CANDIDATEPOS, {x, y}, {0, 0, 0, 0}};
::ImmSetCandidateWindow(imm_context, &candidate_position);
if (system_caret_) {
switch (PRIMARYLANGID(input_language_id_)) {
case LANG_JAPANESE:
::SetCaretPos(x, y + caret_rect_.getHeight());
break;
default:
::SetCaretPos(x, y);
break;
}
::SetCaretPos(x, y);
}
if (PRIMARYLANGID(input_language_id_) == LANG_KOREAN) {
if (IsLanguage(IMELANG_KOREAN)) {
/**
* Chinese IMEs and Japanese IMEs require the upper-left corner of
* the caret to move the position of their candidate windows.
@ -284,83 +273,79 @@ void GHOST_ImeWin32::GetCaret(HIMC imm_context, LPARAM lparam, ImeComposition *c
*/
int target_start = -1;
int target_end = -1;
switch (PRIMARYLANGID(input_language_id_)) {
case LANG_KOREAN:
if (lparam & CS_NOMOVECARET) {
target_start = 0;
target_end = 1;
}
break;
case LANG_CHINESE: {
int clause_size = ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, NULL, 0);
if (clause_size) {
static std::vector<unsigned long> clauses;
clause_size = clause_size / sizeof(clauses[0]);
clauses.resize(clause_size);
ImmGetCompositionStringW(
imm_context, GCS_COMPCLAUSE, &clauses[0], sizeof(clauses[0]) * clause_size);
if (composition->cursor_position == composition->ime_string.size()) {
target_start = clauses[clause_size - 2];
target_end = clauses[clause_size - 1];
}
else {
for (int i = 0; i < clause_size - 1; i++) {
if (clauses[i] == composition->cursor_position) {
target_start = clauses[i];
target_end = clauses[i + 1];
break;
}
}
}
if (IsLanguage(IMELANG_KOREAN)) {
if (lparam & CS_NOMOVECARET) {
target_start = 0;
target_end = 1;
}
}
else if (IsLanguage(IMELANG_CHINESE)) {
int clause_size = ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, NULL, 0);
if (clause_size) {
static std::vector<unsigned long> clauses;
clause_size = clause_size / sizeof(clauses[0]);
clauses.resize(clause_size);
ImmGetCompositionStringW(
imm_context, GCS_COMPCLAUSE, &clauses[0], sizeof(clauses[0]) * clause_size);
if (composition->cursor_position == composition->ime_string.size()) {
target_start = clauses[clause_size - 2];
target_end = clauses[clause_size - 1];
}
else {
if (composition->cursor_position != -1) {
target_start = composition->cursor_position;
target_end = composition->ime_string.size();
}
}
break;
}
case LANG_JAPANESE:
/**
* For Japanese IMEs, the robustest way to retrieve the caret
* is scanning the attribute of the latest composition string and
* retrieving the beginning and the end of the target clause, i.e.
* a clause being converted.
*/
if (lparam & GCS_COMPATTR) {
int attribute_size = ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, NULL, 0);
if (attribute_size > 0) {
char *attribute_data = new char[attribute_size];
if (attribute_data) {
::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, attribute_data, attribute_size);
for (target_start = 0; target_start < attribute_size; ++target_start) {
if (IsTargetAttribute(attribute_data[target_start]))
break;
}
for (target_end = target_start; target_end < attribute_size; ++target_end) {
if (!IsTargetAttribute(attribute_data[target_end]))
break;
}
if (target_start == attribute_size) {
/**
* This composition clause does not contain any target clauses,
* i.e. this clauses is an input clause.
* We treat whole this clause as a target clause.
*/
target_end = target_start;
target_start = 0;
}
if (target_start != -1 && target_start < attribute_size &&
attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED) {
composition->cursor_position = target_start;
}
for (int i = 0; i < clause_size - 1; i++) {
if (clauses[i] == composition->cursor_position) {
target_start = clauses[i];
target_end = clauses[i + 1];
break;
}
delete[] attribute_data;
}
}
break;
}
else {
if (composition->cursor_position != -1) {
target_start = composition->cursor_position;
target_end = composition->ime_string.size();
}
}
}
else if (IsLanguage(IMELANG_JAPANESE)) {
/**
* For Japanese IMEs, the robustest way to retrieve the caret
* is scanning the attribute of the latest composition string and
* retrieving the beginning and the end of the target clause, i.e.
* a clause being converted.
*/
if (lparam & GCS_COMPATTR) {
int attribute_size = ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, NULL, 0);
if (attribute_size > 0) {
char *attribute_data = new char[attribute_size];
if (attribute_data) {
::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, attribute_data, attribute_size);
for (target_start = 0; target_start < attribute_size; ++target_start) {
if (IsTargetAttribute(attribute_data[target_start]))
break;
}
for (target_end = target_start; target_end < attribute_size; ++target_end) {
if (!IsTargetAttribute(attribute_data[target_end]))
break;
}
if (target_start == attribute_size) {
/**
* This composition clause does not contain any target clauses,
* i.e. this clauses is an input clause.
* We treat whole this clause as a target clause.
*/
target_end = target_start;
target_start = 0;
}
if (target_start != -1 && target_start < attribute_size &&
attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED) {
composition->cursor_position = target_start;
}
}
delete[] attribute_data;
}
}
}
composition->target_start = target_start;
composition->target_end = target_end;

@ -36,6 +36,9 @@
# include "GHOST_Rect.h"
# include <vector>
/* MSDN LOCALE_SISO639LANGNAME states maximum length of 9, including terminating null. */
# define W32_ISO639_LEN 9
class GHOST_EventIME : public GHOST_Event {
public:
/**
@ -146,13 +149,10 @@ class GHOST_ImeWin32 {
return is_composing_;
}
/**
* Retrieves the input language from Windows and update it.
*/
/* Retrieve the input language from Windows and store it. */
void UpdateInputLanguage();
/* Returns the current input language id. */
WORD GetInputLanguage();
BOOL IsLanguage(const char name[W32_ISO639_LEN]);
/* Saves the current conversion status. */
void UpdateConversionStatus(HWND window_handle);
@ -345,29 +345,8 @@ class GHOST_ImeWin32 {
*/
bool is_composing_;
/**
* The current input Language ID retrieved from Windows, which consists of:
* * Primary Language ID (bit 0 to bit 9), which shows a natural language
* (English, Korean, Chinese, Japanese, etc.) and;
* * Sub-Language ID (bit 10 to bit 15), which shows a geometrical region
* the language is spoken (For English, United States, United Kingdom,
* Australia, Canada, etc.)
* The following list enumerates some examples for the Language ID:
* * "en-US" (0x0409)
* MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
* * "ko-KR" (0x0412)
* MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN);
* * "zh-TW" (0x0404)
* MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL);
* * "zh-CN" (0x0804)
* MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED);
* * "ja-JP" (0x0411)
* MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN), etc.
* (See `winnt.h` for other available values.)
* This Language ID is used for processing language-specific operations in
* IME functions.
*/
LANGID input_language_id_;
/* Abbreviated ISO 639-1 name of the input language, such as "en" for English. */
char language_[W32_ISO639_LEN];
/* Current Conversion Mode Values. Retrieved with ImmGetConversionStatus. */
DWORD conversion_modes_;