Changeset View
Changeset View
Standalone View
Standalone View
intern/ghost/intern/GHOST_ImeWin32.cpp
| Show All 24 Lines | |||||
| #ifdef WITH_INPUT_IME | #ifdef WITH_INPUT_IME | ||||
| # include "GHOST_ImeWin32.h" | # include "GHOST_ImeWin32.h" | ||||
| # include "GHOST_C-api.h" | # include "GHOST_C-api.h" | ||||
| # include "GHOST_WindowWin32.h" | # include "GHOST_WindowWin32.h" | ||||
| # include "utfconv.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() | GHOST_ImeWin32::GHOST_ImeWin32() | ||||
| : is_composing_(false), | : is_composing_(false), | ||||
| input_language_id_(LANG_USER_DEFAULT), | language_(IMELANG_ENGLISH), | ||||
| conversion_modes_(IME_CMODE_ALPHANUMERIC), | conversion_modes_(IME_CMODE_ALPHANUMERIC), | ||||
| sentence_mode_(IME_SMODE_NONE), | sentence_mode_(IME_SMODE_NONE), | ||||
| system_caret_(false), | system_caret_(false), | ||||
| caret_rect_(-1, -1, 0, 0), | caret_rect_(-1, -1, 0, 0), | ||||
| is_first(true), | is_first(true), | ||||
| is_enable(true) | is_enable(true) | ||||
| { | { | ||||
| } | } | ||||
| GHOST_ImeWin32::~GHOST_ImeWin32() | GHOST_ImeWin32::~GHOST_ImeWin32() | ||||
| { | { | ||||
| } | } | ||||
| void GHOST_ImeWin32::UpdateInputLanguage() | void GHOST_ImeWin32::UpdateInputLanguage() | ||||
| { | { | ||||
| /** | /* Get the current input locale full name. */ | ||||
| * Store the current input language. | WCHAR locale[LOCALE_NAME_MAX_LENGTH]; | ||||
| */ | LCIDToLocaleName( | ||||
| HKL input_locale = ::GetKeyboardLayout(0); | MAKELCID(LOWORD(::GetKeyboardLayout(0)), SORT_DEFAULT), locale, LOCALE_NAME_MAX_LENGTH, 0); | ||||
| input_language_id_ = LOWORD(input_locale); | /* 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) | void GHOST_ImeWin32::UpdateConversionStatus(HWND window_handle) | ||||
| { | { | ||||
| HIMC imm_context = ::ImmGetContext(window_handle); | HIMC imm_context = ::ImmGetContext(window_handle); | ||||
| if (imm_context) { | if (imm_context) { | ||||
| if (::ImmGetOpenStatus(imm_context)) { | if (::ImmGetOpenStatus(imm_context)) { | ||||
| ::ImmGetConversionStatus(imm_context, &conversion_modes_, &sentence_mode_); | ::ImmGetConversionStatus(imm_context, &conversion_modes_, &sentence_mode_); | ||||
| Show All 18 Lines | |||||
| bool GHOST_ImeWin32::IsImeKeyEvent(char ascii) | bool GHOST_ImeWin32::IsImeKeyEvent(char ascii) | ||||
| { | { | ||||
| if (!(IsEnglishMode())) { | if (!(IsEnglishMode())) { | ||||
| /* In Chinese, Japanese, Korean, all alpha keys are processed by IME. */ | /* In Chinese, Japanese, Korean, all alpha keys are processed by IME. */ | ||||
| if ((ascii >= 'A' && ascii <= 'Z') || (ascii >= 'a' && ascii <= 'z')) { | if ((ascii >= 'A' && ascii <= 'Z') || (ascii >= 'a' && ascii <= 'z')) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| switch (PRIMARYLANGID(GetInputLanguage())) { | if (IsLanguage(IMELANG_JAPANESE) && (ascii >= ' ' && ascii <= '~')) { | ||||
| /* In Japanese, all symbolic characters are also processed by IME. */ | |||||
| case LANG_JAPANESE: { | |||||
| if (ascii >= ' ' && ascii <= '~') { | |||||
| return true; | return true; | ||||
| } | } | ||||
| break; | else if (IsLanguage(IMELANG_CHINESE) && ascii && strchr("!\"$'(),.:;<>?[\\]^_`", ascii)) { | ||||
| } | |||||
| /* In Chinese, some symbolic characters are also processed by IME. */ | |||||
| case LANG_CHINESE: { | |||||
| if (ascii && strchr("!\"$'(),.:;<>?[\\]^_`", ascii)) { | |||||
| return true; | return true; | ||||
sntulix: You should use a constant or a symbol instead of a literal by hand.
It is like this:
```… | |||||
Not Done Inline Actionsupdated. Adding GHOST_IMEWIN32_LANG_ISO639_LENGTH = 9, and GHOST_IMEWIN32_LANG_ISO639_HAWAII ('haw').
static const int GHOST_IMEWIN32_LANG_ISO639_LENGTH = 9;
const char GHOST_IMEWIN32_LANG_ISO639_ENGLISH[GHOST_IMEWIN32_LANG_ISO639_LENGTH] = {'e', 'n', 0};
const char GHOST_IMEWIN32_LANG_ISO639_CHINESE[GHOST_IMEWIN32_LANG_ISO639_LENGTH] = {'z', 'h', 0};
const char GHOST_IMEWIN32_LANG_ISO639_JAPANESE[GHOST_IMEWIN32_LANG_ISO639_LENGTH] = {'j', 'a', 0};
const char GHOST_IMEWIN32_LANG_ISO639_KOREAN[GHOST_IMEWIN32_LANG_ISO639_LENGTH] = {'k', 'o', 0};
const char GHOST_IMEWIN32_LANG_ISO639_HAWAII[GHOST_IMEWIN32_LANG_ISO639_LENGTH] = {'h', 'a', 'w', 0};sntulix: updated. Adding GHOST_IMEWIN32_LANG_ISO639_LENGTH = 9, and GHOST_IMEWIN32_LANG_ISO639_HAWAII… | |||||
| } | } | ||||
| break; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| void GHOST_ImeWin32::CreateImeWindow(HWND window_handle) | void GHOST_ImeWin32::CreateImeWindow(HWND window_handle) | ||||
| { | { | ||||
| /** | /** | ||||
| * When a user disables TSF (Text Service Framework) and CUAS (Cicero | * When a user disables TSF (Text Service Framework) and CUAS (Cicero | ||||
| * Unaware Application Support), Chinese IMEs somehow ignore function calls | * Unaware Application Support), Chinese IMEs somehow ignore function calls | ||||
| * to ::ImmSetCandidateWindow(), i.e. they do not move their candidate | * to ::ImmSetCandidateWindow(), i.e. they do not move their candidate | ||||
| * window to the position given as its parameters, and use the position | * window to the position given as its parameters, and use the position | ||||
| * of the current system caret instead, i.e. it uses ::GetCaretPos() to | * of the current system caret instead, i.e. it uses ::GetCaretPos() to | ||||
| * retrieve the position of their IME candidate window. | * retrieve the position of their IME candidate window. | ||||
| * Therefore, we create a temporary system caret for Chinese IMEs and use | * Therefore, we create a temporary system caret for Chinese IMEs and use | ||||
| * it during this input context. | * it during this input context. | ||||
| * Since some third-party Japanese IME also uses ::GetCaretPos() to determine | * Since some third-party Japanese IME also uses ::GetCaretPos() to determine | ||||
| * their window position, we also create a caret for Japanese IMEs. | * their window position, we also create a caret for Japanese IMEs. | ||||
| */ | */ | ||||
| if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE || | if (!system_caret_ && (IsLanguage(IMELANG_CHINESE) || IsLanguage(IMELANG_JAPANESE))) { | ||||
| PRIMARYLANGID(input_language_id_) == LANG_JAPANESE) { | system_caret_ = ::CreateCaret(window_handle, NULL, 1, 1); | ||||
| if (!system_caret_) { | |||||
| if (::CreateCaret(window_handle, NULL, 1, 1)) { | |||||
| system_caret_ = true; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| /* Restore the positions of the IME windows. */ | /* Restore the positions of the IME windows. */ | ||||
| UpdateImeWindow(window_handle); | UpdateImeWindow(window_handle); | ||||
| } | } | ||||
| void GHOST_ImeWin32::SetImeWindowStyle( | void GHOST_ImeWin32::SetImeWindowStyle( | ||||
| HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam, BOOL *handled) | HWND window_handle, UINT message, WPARAM wparam, LPARAM lparam, BOOL *handled) | ||||
| { | { | ||||
| Show All 36 Lines | void GHOST_ImeWin32::MoveImeWindow(HWND window_handle, HIMC imm_context) | ||||
| * parameters given to ::ImmSetCandidateWindow() with its 'dwStyle' | * parameters given to ::ImmSetCandidateWindow() with its 'dwStyle' | ||||
| * parameter CFS_CANDIDATEPOS. | * parameter CFS_CANDIDATEPOS. | ||||
| * Therefore, we do not only call ::ImmSetCandidateWindow() but also | * Therefore, we do not only call ::ImmSetCandidateWindow() but also | ||||
| * set the positions of the temporary system caret if it exists. | * set the positions of the temporary system caret if it exists. | ||||
| */ | */ | ||||
| CANDIDATEFORM candidate_position = {0, CFS_CANDIDATEPOS, {x, y}, {0, 0, 0, 0}}; | CANDIDATEFORM candidate_position = {0, CFS_CANDIDATEPOS, {x, y}, {0, 0, 0, 0}}; | ||||
| ::ImmSetCandidateWindow(imm_context, &candidate_position); | ::ImmSetCandidateWindow(imm_context, &candidate_position); | ||||
| if (system_caret_) { | if (system_caret_) { | ||||
| switch (PRIMARYLANGID(input_language_id_)) { | |||||
| case LANG_JAPANESE: | |||||
| ::SetCaretPos(x, y + caret_rect_.getHeight()); | |||||
| break; | |||||
| default: | |||||
| ::SetCaretPos(x, y); | ::SetCaretPos(x, y); | ||||
| break; | |||||
| } | } | ||||
| } | if (IsLanguage(IMELANG_KOREAN)) { | ||||
| if (PRIMARYLANGID(input_language_id_) == LANG_KOREAN) { | |||||
| /** | /** | ||||
| * Chinese IMEs and Japanese IMEs require the upper-left corner of | * Chinese IMEs and Japanese IMEs require the upper-left corner of | ||||
| * the caret to move the position of their candidate windows. | * the caret to move the position of their candidate windows. | ||||
| * On the other hand, Korean IMEs require the lower-left corner of the | * On the other hand, Korean IMEs require the lower-left corner of the | ||||
| * caret to move their candidate windows. | * caret to move their candidate windows. | ||||
| */ | */ | ||||
| y += kCaretMargin; | y += kCaretMargin; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | void GHOST_ImeWin32::GetCaret(HIMC imm_context, LPARAM lparam, ImeComposition *composition) | ||||
| * (It contains only one hangul character); | * (It contains only one hangul character); | ||||
| * * Chinese IMEs: the caret is a blinking line, | * * Chinese IMEs: the caret is a blinking line, | ||||
| * (i.e. they do not need to retrieve the target selection); | * (i.e. they do not need to retrieve the target selection); | ||||
| * * Japanese IMEs: the caret is a selection (or underlined) block, | * * Japanese IMEs: the caret is a selection (or underlined) block, | ||||
| * (which can contain one or more Japanese characters). | * (which can contain one or more Japanese characters). | ||||
| */ | */ | ||||
| int target_start = -1; | int target_start = -1; | ||||
| int target_end = -1; | int target_end = -1; | ||||
| switch (PRIMARYLANGID(input_language_id_)) { | if (IsLanguage(IMELANG_KOREAN)) { | ||||
| case LANG_KOREAN: | |||||
| if (lparam & CS_NOMOVECARET) { | if (lparam & CS_NOMOVECARET) { | ||||
| target_start = 0; | target_start = 0; | ||||
| target_end = 1; | target_end = 1; | ||||
| } | } | ||||
| break; | } | ||||
| case LANG_CHINESE: { | else if (IsLanguage(IMELANG_CHINESE)) { | ||||
| int clause_size = ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, NULL, 0); | int clause_size = ImmGetCompositionStringW(imm_context, GCS_COMPCLAUSE, NULL, 0); | ||||
| if (clause_size) { | if (clause_size) { | ||||
| static std::vector<unsigned long> clauses; | static std::vector<unsigned long> clauses; | ||||
| clause_size = clause_size / sizeof(clauses[0]); | clause_size = clause_size / sizeof(clauses[0]); | ||||
| clauses.resize(clause_size); | clauses.resize(clause_size); | ||||
| ImmGetCompositionStringW( | ImmGetCompositionStringW( | ||||
| imm_context, GCS_COMPCLAUSE, &clauses[0], sizeof(clauses[0]) * clause_size); | imm_context, GCS_COMPCLAUSE, &clauses[0], sizeof(clauses[0]) * clause_size); | ||||
| if (composition->cursor_position == composition->ime_string.size()) { | if (composition->cursor_position == composition->ime_string.size()) { | ||||
| target_start = clauses[clause_size - 2]; | target_start = clauses[clause_size - 2]; | ||||
| target_end = clauses[clause_size - 1]; | target_end = clauses[clause_size - 1]; | ||||
| } | } | ||||
| else { | else { | ||||
| for (int i = 0; i < clause_size - 1; i++) { | for (int i = 0; i < clause_size - 1; i++) { | ||||
| if (clauses[i] == composition->cursor_position) { | if (clauses[i] == composition->cursor_position) { | ||||
| target_start = clauses[i]; | target_start = clauses[i]; | ||||
| target_end = clauses[i + 1]; | target_end = clauses[i + 1]; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| if (composition->cursor_position != -1) { | if (composition->cursor_position != -1) { | ||||
| target_start = composition->cursor_position; | target_start = composition->cursor_position; | ||||
| target_end = composition->ime_string.size(); | target_end = composition->ime_string.size(); | ||||
| } | } | ||||
| } | } | ||||
| break; | |||||
| } | } | ||||
| case LANG_JAPANESE: | else if (IsLanguage(IMELANG_JAPANESE)) { | ||||
| /** | /** | ||||
| * For Japanese IMEs, the robustest way to retrieve the caret | * For Japanese IMEs, the robustest way to retrieve the caret | ||||
| * is scanning the attribute of the latest composition string and | * is scanning the attribute of the latest composition string and | ||||
| * retrieving the beginning and the end of the target clause, i.e. | * retrieving the beginning and the end of the target clause, i.e. | ||||
| * a clause being converted. | * a clause being converted. | ||||
| */ | */ | ||||
| if (lparam & GCS_COMPATTR) { | if (lparam & GCS_COMPATTR) { | ||||
| int attribute_size = ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, NULL, 0); | int attribute_size = ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, NULL, 0); | ||||
| if (attribute_size > 0) { | if (attribute_size > 0) { | ||||
| char *attribute_data = new char[attribute_size]; | char *attribute_data = new char[attribute_size]; | ||||
| if (attribute_data) { | if (attribute_data) { | ||||
| ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, attribute_data, attribute_size); | ::ImmGetCompositionStringW(imm_context, GCS_COMPATTR, attribute_data, attribute_size); | ||||
| for (target_start = 0; target_start < attribute_size; ++target_start) { | for (target_start = 0; target_start < attribute_size; ++target_start) { | ||||
| if (IsTargetAttribute(attribute_data[target_start])) | if (IsTargetAttribute(attribute_data[target_start])) | ||||
| break; | break; | ||||
| } | } | ||||
| for (target_end = target_start; target_end < attribute_size; ++target_end) { | for (target_end = target_start; target_end < attribute_size; ++target_end) { | ||||
| if (!IsTargetAttribute(attribute_data[target_end])) | if (!IsTargetAttribute(attribute_data[target_end])) | ||||
| break; | break; | ||||
| } | } | ||||
| if (target_start == attribute_size) { | if (target_start == attribute_size) { | ||||
| /** | /** | ||||
| * This composition clause does not contain any target clauses, | * This composition clause does not contain any target clauses, | ||||
| * i.e. this clauses is an input clause. | * i.e. this clauses is an input clause. | ||||
| * We treat whole this clause as a target clause. | * We treat whole this clause as a target clause. | ||||
| */ | */ | ||||
| target_end = target_start; | target_end = target_start; | ||||
| target_start = 0; | target_start = 0; | ||||
| } | } | ||||
| if (target_start != -1 && target_start < attribute_size && | if (target_start != -1 && target_start < attribute_size && | ||||
| attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED) { | attribute_data[target_start] == ATTR_TARGET_NOTCONVERTED) { | ||||
| composition->cursor_position = target_start; | composition->cursor_position = target_start; | ||||
| } | } | ||||
| } | } | ||||
| delete[] attribute_data; | delete[] attribute_data; | ||||
| } | } | ||||
| } | } | ||||
| break; | |||||
| } | } | ||||
| composition->target_start = target_start; | composition->target_start = target_start; | ||||
| composition->target_end = target_end; | composition->target_end = target_end; | ||||
| } | } | ||||
| bool GHOST_ImeWin32::GetString(HIMC imm_context, | bool GHOST_ImeWin32::GetString(HIMC imm_context, | ||||
| WPARAM lparam, | WPARAM lparam, | ||||
| int type, | int type, | ||||
| ▲ Show 20 Lines • Show All 171 Lines • Show Last 20 Lines | |||||
You should use a constant or a symbol instead of a literal by hand.
It is like this:
const char GHOST_IMEWIN32_LANG_ISO639_ENGLISH[3] = {'e', 'n', 0}; const char GHOST_IMEWIN32_LANG_ISO639_CHINESE[3] = {'z', 'h', 0}; const char GHOST_IMEWIN32_LANG_ISO639_JAPANESE[3] = {'j', 'a', 0}; const char GHOST_IMEWIN32_LANG_ISO639_KOREAN[3] = {'k', 'o', 0};if ((ascii >= 'A' && ascii <= 'Z') || (ascii >= 'a' && ascii <= 'z')) { return true; } if (IsLanguage(GHOST_IMEWIN32_LANG_ISO639_JAPANESE) && (ascii >= ' ' && ascii <= '~')) { return true; } else if (IsLanguage(GHOST_IMEWIN32_LANG_ISO639_CHINESE) && ascii && strchr("!\"$'(),.:;<>?[\\]^_`", ascii)) { return true; }P.S. I don't know the char array's length of each LANGNAME is [3].