forked from bartvdbraak/blender
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:
parent
6806459246
commit
946da86e02
@ -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_;
|
||||
|
Loading…
Reference in New Issue
Block a user