aboutsummaryrefslogtreecommitdiff
path: root/src/win/gui/InputMethod.cpp
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-03-24 19:14:19 +0800
committercrupest <crupest@outlook.com>2021-03-24 19:14:19 +0800
commit7f15a1ff9a2007e119798053083a0a87d042990a (patch)
treecb35c01a7eaee867376d959b96c9bbd15df939e5 /src/win/gui/InputMethod.cpp
parent74956951ee663012df0c3fe4ebe29799cb2f7732 (diff)
parent7703063a5816b089483e78ccd74bb9902ccfbea8 (diff)
downloadcru-7f15a1ff9a2007e119798053083a0a87d042990a.tar.gz
cru-7f15a1ff9a2007e119798053083a0a87d042990a.tar.bz2
cru-7f15a1ff9a2007e119798053083a0a87d042990a.zip
Merge branch 'master' of https://github.com/crupest/CruUI
Diffstat (limited to 'src/win/gui/InputMethod.cpp')
-rw-r--r--src/win/gui/InputMethod.cpp288
1 files changed, 288 insertions, 0 deletions
diff --git a/src/win/gui/InputMethod.cpp b/src/win/gui/InputMethod.cpp
new file mode 100644
index 00000000..cc237e88
--- /dev/null
+++ b/src/win/gui/InputMethod.cpp
@@ -0,0 +1,288 @@
+#include "cru/win/gui/InputMethod.hpp"
+
+#include "cru/common/Logger.hpp"
+#include "cru/common/StringUtil.hpp"
+#include "cru/platform/Check.hpp"
+#include "cru/platform/gui/DebugFlags.hpp"
+#include "cru/win/Exception.hpp"
+#include "cru/win/gui/Window.hpp"
+
+#include <vector>
+
+namespace cru::platform::gui::win {
+AutoHIMC::AutoHIMC(HWND hwnd) : hwnd_(hwnd) {
+ Expects(hwnd);
+ handle_ = ::ImmGetContext(hwnd);
+}
+
+AutoHIMC::AutoHIMC(AutoHIMC&& other)
+ : hwnd_(other.hwnd_), handle_(other.handle_) {
+ other.hwnd_ = nullptr;
+ other.handle_ = nullptr;
+}
+
+AutoHIMC& AutoHIMC::operator=(AutoHIMC&& other) {
+ if (this != &other) {
+ Object::operator=(std::move(other));
+ this->hwnd_ = other.hwnd_;
+ this->handle_ = other.handle_;
+ other.hwnd_ = nullptr;
+ other.handle_ = nullptr;
+ }
+ return *this;
+}
+
+AutoHIMC::~AutoHIMC() {
+ if (handle_) {
+ if (!::ImmReleaseContext(hwnd_, handle_))
+ log::TagWarn(log_tag, u"Failed to release HIMC.");
+ }
+}
+
+// copied from chromium
+namespace {
+// Determines whether or not the given attribute represents a target
+// (a.k.a. a selection).
+bool IsTargetAttribute(char attribute) {
+ return (attribute == ATTR_TARGET_CONVERTED ||
+ attribute == ATTR_TARGET_NOTCONVERTED);
+}
+// Helper function for ImeInput::GetCompositionInfo() method, to get the target
+// range that's selected by the user in the current composition string.
+void GetCompositionTargetRange(HIMC imm_context, int* target_start,
+ int* target_end) {
+ int attribute_size =
+ ::ImmGetCompositionString(imm_context, GCS_COMPATTR, NULL, 0);
+ if (attribute_size > 0) {
+ int start = 0;
+ int end = 0;
+ std::vector<char> attribute_data(attribute_size);
+ ::ImmGetCompositionString(imm_context, GCS_COMPATTR, attribute_data.data(),
+ attribute_size);
+ for (start = 0; start < attribute_size; ++start) {
+ if (IsTargetAttribute(attribute_data[start])) break;
+ }
+ for (end = start; end < attribute_size; ++end) {
+ if (!IsTargetAttribute(attribute_data[end])) break;
+ }
+ if (start == attribute_size) {
+ // This composition clause does not contain any target clauses,
+ // i.e. this clauses is an input clause.
+ // We treat the whole composition as a target clause.
+ start = 0;
+ end = attribute_size;
+ }
+ *target_start = start;
+ *target_end = end;
+ }
+}
+// Helper function for ImeInput::GetCompositionInfo() method, to get underlines
+// information of the current composition string.
+CompositionClauses GetCompositionClauses(HIMC imm_context, int target_start,
+ int target_end) {
+ CompositionClauses result;
+ int clause_size =
+ ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, NULL, 0);
+ int clause_length = clause_size / sizeof(std::uint32_t);
+ if (clause_length) {
+ result.reserve(clause_length - 1);
+ std::vector<std::uint32_t> clause_data(clause_length);
+ ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, clause_data.data(),
+ clause_size);
+ for (int i = 0; i < clause_length - 1; ++i) {
+ CompositionClause clause;
+ clause.start = clause_data[i];
+ clause.end = clause_data[i + 1];
+ clause.target = false;
+ // Use thick underline for the target clause.
+ if (clause.start >= target_start && clause.end <= target_end) {
+ clause.target = true;
+ }
+ result.push_back(clause);
+ }
+ }
+ return result;
+}
+
+std::u16string GetString(HIMC imm_context) {
+ LONG string_size =
+ ::ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0);
+ std::u16string result((string_size / sizeof(char16_t)), 0);
+ ::ImmGetCompositionString(imm_context, GCS_COMPSTR, result.data(),
+ string_size);
+ return result;
+}
+
+std::u16string GetResultString(HIMC imm_context) {
+ LONG string_size =
+ ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, NULL, 0);
+ std::u16string result((string_size / sizeof(char16_t)), 0);
+ ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, result.data(),
+ string_size);
+ return result;
+}
+
+CompositionText GetCompositionInfo(HIMC imm_context) {
+ // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and
+ // convert them into underlines and selection range respectively.
+
+ auto text = GetString(imm_context);
+
+ int length = static_cast<int>(text.length());
+ // Find out the range selected by the user.
+ int target_start = length;
+ int target_end = length;
+ GetCompositionTargetRange(imm_context, &target_start, &target_end);
+
+ auto clauses = GetCompositionClauses(imm_context, target_start, target_end);
+
+ int cursor = ::ImmGetCompositionString(imm_context, GCS_CURSORPOS, NULL, 0);
+
+ return CompositionText{std::move(text), std::move(clauses),
+ TextRange{cursor}};
+}
+
+} // namespace
+
+WinInputMethodContext::WinInputMethodContext(
+ gsl::not_null<WinNativeWindow*> window)
+ : native_window_(window) {
+ event_guard_ += window->NativeMessageEvent()->AddHandler(
+ std::bind(&WinInputMethodContext::OnWindowNativeMessage, this,
+ std::placeholders::_1));
+}
+
+WinInputMethodContext::~WinInputMethodContext() {}
+
+void WinInputMethodContext::EnableIME() {
+ const auto hwnd = native_window_->GetWindowHandle();
+ if (::ImmAssociateContextEx(hwnd, nullptr, IACE_DEFAULT) == FALSE) {
+ log::TagWarn(log_tag, u"Failed to enable ime.");
+ }
+}
+
+void WinInputMethodContext::DisableIME() {
+ const auto hwnd = native_window_->GetWindowHandle();
+ AutoHIMC himc{hwnd};
+
+ ::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
+
+ if (::ImmAssociateContextEx(hwnd, nullptr, 0) == FALSE) {
+ log::TagWarn(log_tag, u"Failed to disable ime.");
+ }
+}
+
+void WinInputMethodContext::CompleteComposition() {
+ auto himc = GetHIMC();
+ if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0)) {
+ log::TagWarn(log_tag, u"Failed to complete composition.");
+ }
+}
+
+void WinInputMethodContext::CancelComposition() {
+ auto himc = GetHIMC();
+ if (!::ImmNotifyIME(himc.Get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0)) {
+ log::TagWarn(log_tag, u"Failed to complete composition.");
+ }
+}
+
+CompositionText WinInputMethodContext::GetCompositionText() {
+ auto himc = GetHIMC();
+ return GetCompositionInfo(himc.Get());
+}
+
+void WinInputMethodContext::SetCandidateWindowPosition(const Point& point) {
+ auto himc = GetHIMC();
+
+ ::CANDIDATEFORM form;
+ form.dwIndex = 0;
+ form.dwStyle = CFS_CANDIDATEPOS;
+
+ form.ptCurrentPos = native_window_->DipToPixel(point);
+
+ if (!::ImmSetCandidateWindow(himc.Get(), &form))
+ log::TagDebug(log_tag,
+ u"Failed to set input method candidate window position.");
+}
+
+IEvent<std::nullptr_t>* WinInputMethodContext::CompositionStartEvent() {
+ return &composition_start_event_;
+}
+
+IEvent<std::nullptr_t>* WinInputMethodContext::CompositionEndEvent() {
+ return &composition_end_event_;
+};
+
+IEvent<std::nullptr_t>* WinInputMethodContext::CompositionEvent() {
+ return &composition_event_;
+}
+
+IEvent<std::u16string_view>* WinInputMethodContext::TextEvent() {
+ return &text_event_;
+}
+
+void WinInputMethodContext::OnWindowNativeMessage(
+ WindowNativeMessageEventArgs& args) {
+ const auto& message = args.GetWindowMessage();
+ switch (message.msg) {
+ case WM_CHAR: {
+ auto c = static_cast<char16_t>(message.w_param);
+ if (IsUtf16SurrogatePairCodeUnit(c)) {
+ // I don't think this will happen because normal key strike without ime
+ // should only trigger ascci character. If it is a charater from
+ // supplementary planes, it should be handled with ime messages.
+ log::TagWarn(log_tag,
+ u"A WM_CHAR message for character from supplementary "
+ u"planes is ignored.");
+ } else {
+ if (c != '\b') { // ignore backspace
+ if (c == '\r') c = '\n'; // Change \r to \n
+
+ char16_t s[1] = {c};
+ text_event_.Raise({s, 1});
+ }
+ }
+ args.HandleWithResult(0);
+ break;
+ }
+ case WM_IME_COMPOSITION: {
+ composition_event_.Raise(nullptr);
+ auto composition_text = GetCompositionText();
+ if constexpr (DebugFlags::input_method) {
+ log::TagDebug(log_tag, u"WM_IME_COMPOSITION composition text:\n{}",
+ composition_text);
+ }
+ if (message.l_param & GCS_RESULTSTR) {
+ auto result_string = GetResultString();
+ text_event_.Raise(result_string);
+ }
+ break;
+ }
+ case WM_IME_STARTCOMPOSITION: {
+ if constexpr (DebugFlags::input_method) {
+ log::TagDebug(log_tag, u"WM_IME_STARTCOMPOSITION received.");
+ }
+ composition_start_event_.Raise(nullptr);
+ break;
+ }
+ case WM_IME_ENDCOMPOSITION: {
+ if constexpr (DebugFlags::input_method) {
+ log::TagDebug(log_tag, u"WM_IME_ENDCOMPOSITION received.");
+ }
+ composition_end_event_.Raise(nullptr);
+ break;
+ }
+ }
+}
+
+std::u16string WinInputMethodContext::GetResultString() {
+ auto himc = GetHIMC();
+ auto result = win::GetResultString(himc.Get());
+ return result;
+}
+
+AutoHIMC WinInputMethodContext::GetHIMC() {
+ const auto hwnd = native_window_->GetWindowHandle();
+ return AutoHIMC{hwnd};
+}
+} // namespace cru::platform::gui::win