aboutsummaryrefslogtreecommitdiff
path: root/src/win/native/input_method.cpp
blob: 4a125e8d4ded2e6bc5dcb6c0057297c25623d43b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include "cru/win/native/input_method.hpp"

#include "cru/common/logger.hpp"
#include "cru/platform/check.hpp"
#include "cru/win/exception.hpp"
#include "cru/win/native/window.hpp"
#include "cru/win/string.hpp"
#include "dpi_util.hpp"

#include <vector>

namespace cru::platform::native::win {
WinInputMethodContextRef::WinInputMethodContextRef(WinNativeWindow* window)
    : window_(window) {
  Expects(window);

  window_handle_ = window->GetWindowHandle();
  handle_ = ::ImmGetContext(window_handle_);

  // TODO: Events

  event_revoker_guards_.push_back(
      EventRevokerGuard(window->NativeMessageEvent()->AddHandler(
          std::bind(&WinInputMethodContextRef::OnWindowNativeMessage, this,
                    std::placeholders::_1))));
}

WinInputMethodContextRef::~WinInputMethodContextRef() {
  ::ImmReleaseContext(window_handle_, handle_);
}

void WinInputMethodContextRef::Reset() {
  wchar_t s[1] = {L'\0'};

  if (!::ImmSetCompositionStringW(handle_, SCS_SETSTR, static_cast<LPVOID>(s),
                                  sizeof(s), static_cast<LPVOID>(s),
                                  sizeof(s))) {
    log::Warn(
        "WinInputMethodContextRef: Failed to reset input method context.");
  }
}

std::string WinInputMethodContextRef::GetCompositionText() {
  const auto length = gsl::narrow_cast<DWORD>(
      ::ImmGetCompositionStringW(handle_, GCS_RESULTREADSTR, NULL, 0) +
      sizeof(wchar_t));
  std::vector<std::byte> data(length);
  const auto result = ::ImmGetCompositionStringW(
      handle_, GCS_RESULTREADSTR, static_cast<LPVOID>(data.data()), length);

  if (result == IMM_ERROR_NODATA) {
    return std::string{};
  } else if (result == IMM_ERROR_GENERAL) {
    throw new platform::win::Win32Error(::GetLastError(),
                                        "Failed to get composition string.");
  }

  return platform::win::ToUtf8String(
      std::wstring_view(reinterpret_cast<wchar_t*>(data.data())));
}

void WinInputMethodContextRef::SetCandidateWindowPosition(const Point& point) {
  ::CANDIDATEFORM form;
  form.dwIndex = 1;
  form.dwStyle = CFS_CANDIDATEPOS;
  form.ptCurrentPos = DipToPi(point);

  if (!::ImmSetCandidateWindow(handle_, &form))
    log::Debug(
        "WinInputMethodContextRef: Failed to set input method candidate window "
        "position.");
}

IEvent<std::nullptr_t>* WinInputMethodContextRef::CompositionStartEvent() {
  return &composition_start_event_;
}

IEvent<std::nullptr_t>* WinInputMethodContextRef::CompositionEndEvent() {
  return &composition_end_event_;
};

IEvent<std::string>* WinInputMethodContextRef::CompositionTextChangeEvent() {
  return &composition_text_change_event_;
}

void WinInputMethodContextRef::OnWindowNativeMessage(
    WindowNativeMessageEventArgs& args) {
  const auto message = args.GetWindowMessage();
  switch (message.msg) {
    case WM_IME_COMPOSITION: {
      composition_text_change_event_.Raise(this->GetCompositionText());
      break;
    }
    case WM_IME_STARTCOMPOSITION: {
      composition_start_event_.Raise(nullptr);
      break;
    }
    case WM_IME_ENDCOMPOSITION: {
      composition_end_event_.Raise(nullptr);
      break;
    }
  }
}

WinInputMethodManager::WinInputMethodManager(WinUiApplication*) {}

WinInputMethodManager::~WinInputMethodManager() {}

std::unique_ptr<IInputMethodContextRef> WinInputMethodManager::GetContext(
    INativeWindow* window) {
  Expects(window);
  const auto w = CheckPlatform<WinNativeWindow>(window, GetPlatformId());
  return std::make_unique<WinInputMethodContextRef>(w);
}
}  // namespace cru::platform::native::win