1 /*
2 * SPDX-FileCopyrightText: 2017~2017 CSSlayer <wengxt@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6
7 #include "rimestate.h"
8 #include "rimecandidate.h"
9 #include <fcitx-utils/utf8.h>
10 #include <fcitx/candidatelist.h>
11 #include <fcitx/inputcontext.h>
12 #include <fcitx/inputpanel.h>
13
14 namespace fcitx {
15
16 namespace {
17
emptyExceptAux(const InputPanel & inputPanel)18 bool emptyExceptAux(const InputPanel &inputPanel) {
19
20 return inputPanel.preedit().size() == 0 &&
21 inputPanel.preedit().size() == 0 &&
22 (!inputPanel.candidateList() ||
23 inputPanel.candidateList()->size() == 0);
24 }
25 } // namespace
26
RimeState(RimeEngine * engine)27 RimeState::RimeState(RimeEngine *engine) : engine_(engine) {
28 if (auto api = engine_->api()) {
29 session_ = api->create_session();
30 }
31 }
32
~RimeState()33 RimeState::~RimeState() {
34 if (auto api = engine_->api()) {
35 if (session_) {
36 api->destroy_session(session_);
37 }
38 }
39 }
40
clear()41 void RimeState::clear() {
42 if (auto api = engine_->api()) {
43 if (session_) {
44 api->clear_composition(session_);
45 }
46 }
47 }
48
subMode()49 std::string RimeState::subMode() {
50 std::string result;
51 RIME_STRUCT(RimeStatus, status);
52 if (getStatus(&status)) {
53 if (status.is_disabled) {
54 result = "\xe2\x8c\x9b";
55 } else if (status.is_ascii_mode) {
56 result = _("Latin Mode");
57 } else if (status.schema_name && status.schema_name[0] != '.') {
58 result = status.schema_name;
59 }
60 engine_->api()->free_status(&status);
61 }
62 return result;
63 }
64
subModeLabel()65 std::string RimeState::subModeLabel() {
66 std::string result;
67 RIME_STRUCT(RimeStatus, status);
68 if (getStatus(&status)) {
69 if (status.is_disabled) {
70 result = "";
71 } else if (status.is_ascii_mode) {
72 result = "A";
73 } else if (status.schema_name && status.schema_name[0] != '.') {
74 result = status.schema_name;
75 if (!result.empty() &&
76 utf8::lengthValidated(result) != utf8::INVALID_LENGTH) {
77 result = result.substr(
78 0, std::distance(result.begin(),
79 utf8::nextChar(result.begin())));
80 }
81 }
82 engine_->api()->free_status(&status);
83 }
84 return result;
85 }
86
setLatinMode(bool latin)87 void RimeState::setLatinMode(bool latin) {
88 auto api = engine_->api();
89 if (!api || api->is_maintenance_mode()) {
90 return;
91 }
92 api->set_option(session_, "ascii_mode", latin);
93 }
94
selectSchema(const std::string & schema)95 void RimeState::selectSchema(const std::string &schema) {
96 auto api = engine_->api();
97 if (!api || api->is_maintenance_mode()) {
98 return;
99 }
100 api->set_option(session_, "ascii_mode", false);
101 api->select_schema(session_, schema.data());
102 }
103
keyEvent(KeyEvent & event)104 void RimeState::keyEvent(KeyEvent &event) {
105 auto api = engine_->api();
106 if (!api || api->is_maintenance_mode()) {
107 return;
108 }
109 if (!api->find_session(session_)) {
110 session_ = api->create_session();
111 }
112 if (!session_) {
113 return;
114 }
115
116 lastMode_ = subMode();
117 auto states = event.rawKey().states() &
118 KeyStates{KeyState::Mod1, KeyState::CapsLock, KeyState::Shift,
119 KeyState::Ctrl, KeyState::Super};
120 if (states.test(KeyState::Super)) {
121 // IBus uses virtual super mask.
122 states |= KeyState::Super2;
123 }
124 uint32_t intStates = states;
125 if (event.isRelease()) {
126 // IBUS_RELEASE_MASK
127 intStates |= (1 << 30);
128 }
129 auto result = api->process_key(session_, event.rawKey().sym(), intStates);
130
131 auto ic = event.inputContext();
132 RIME_STRUCT(RimeCommit, commit);
133 if (api->get_commit(session_, &commit)) {
134 ic->commitString(commit.text);
135 api->free_commit(&commit);
136 }
137
138 updateUI(ic, event.isRelease());
139
140 if (result) {
141 event.filterAndAccept();
142 }
143 }
144
getStatus(RimeStatus * status)145 bool RimeState::getStatus(RimeStatus *status) {
146 auto api = engine_->api();
147 if (!api) {
148 return false;
149 }
150 if (!api->find_session(session_)) {
151 session_ = api->create_session();
152 }
153 if (!session_) {
154 return false;
155 }
156 return api->get_status(session_, status);
157 }
158
updatePreedit(InputContext * ic,const RimeContext & context)159 void RimeState::updatePreedit(InputContext *ic, const RimeContext &context) {
160 Text preedit;
161 Text clientPreedit;
162
163 TextFormatFlags flag;
164 if (engine_->config().showPreeditInApplication.value() &&
165 ic->capabilityFlags().test(CapabilityFlag::Preedit)) {
166 flag = TextFormatFlag::Underline;
167 }
168 do {
169 if (context.composition.length == 0) {
170 break;
171 }
172
173 // validation.
174 if (!(context.composition.sel_start >= 0 &&
175 context.composition.sel_start <= context.composition.sel_end &&
176 context.composition.sel_end <= context.composition.length)) {
177 break;
178 }
179
180 /* converted text */
181 if (context.composition.sel_start > 0) {
182 preedit.append(std::string(context.composition.preedit,
183 context.composition.sel_start),
184 flag);
185 if (context.commit_text_preview) {
186 clientPreedit.append(std::string(context.commit_text_preview,
187 context.composition.sel_start),
188 flag);
189 }
190 }
191
192 /* converting candidate */
193 if (context.composition.sel_start < context.composition.sel_end) {
194 preedit.append(
195 std::string(
196 &context.composition.preedit[context.composition.sel_start],
197 &context.composition.preedit[context.composition.sel_end]),
198 flag | TextFormatFlag::HighLight);
199 if (context.commit_text_preview) {
200 clientPreedit.append(
201 std::string(&context.commit_text_preview[context.composition
202 .sel_start]),
203 flag | TextFormatFlag::HighLight);
204 }
205 }
206
207 /* remaining input to convert */
208 if (context.composition.sel_end < context.composition.length) {
209 preedit.append(
210 std::string(
211 &context.composition.preedit[context.composition.sel_end],
212 &context.composition.preedit[context.composition.length]),
213 flag);
214 }
215
216 preedit.setCursor(context.composition.cursor_pos);
217 } while (0);
218
219 if (engine_->config().showPreeditInApplication.value() &&
220 ic->capabilityFlags().test(CapabilityFlag::Preedit)) {
221 ic->inputPanel().setClientPreedit(preedit);
222 } else {
223 ic->inputPanel().setPreedit(preedit);
224 ic->inputPanel().setClientPreedit(clientPreedit);
225 }
226 }
227
updateUI(InputContext * ic,bool keyRelease)228 void RimeState::updateUI(InputContext *ic, bool keyRelease) {
229 auto &inputPanel = ic->inputPanel();
230 if (!keyRelease) {
231 inputPanel.reset();
232 }
233 bool oldEmptyExceptAux = emptyExceptAux(inputPanel);
234 engine_->updateAction(ic);
235
236 do {
237 auto api = engine_->api();
238 if (!api || api->is_maintenance_mode()) {
239 return;
240 }
241 if (!api->find_session(session_)) {
242 return;
243 }
244
245 RIME_STRUCT(RimeContext, context);
246 if (!api->get_context(session_, &context)) {
247 break;
248 }
249
250 updatePreedit(ic, context);
251
252 if (context.menu.num_candidates) {
253 ic->inputPanel().setCandidateList(
254 std::make_unique<RimeCandidateList>(engine_, ic, context));
255 } else {
256 ic->inputPanel().setCandidateList(nullptr);
257 }
258
259 api->free_context(&context);
260 } while (0);
261
262 ic->updatePreedit();
263 // HACK: for show input method information.
264 // Since we don't use aux, which is great for this hack.
265 bool newEmptyExceptAux = emptyExceptAux(inputPanel);
266 // If it's key release and old information is not "empty", do the rest of
267 // "reset".
268 if (keyRelease && !newEmptyExceptAux) {
269 inputPanel.setAuxUp(Text());
270 inputPanel.setAuxDown(Text());
271 }
272 if (newEmptyExceptAux && lastMode_ != subMode()) {
273 engine_->instance()->showInputMethodInformation(ic);
274 ic->updateUserInterface(UserInterfaceComponent::StatusArea);
275 }
276
277 if (!keyRelease || !oldEmptyExceptAux || !newEmptyExceptAux) {
278 ic->updateUserInterface(UserInterfaceComponent::InputPanel);
279 }
280 }
281
release()282 void RimeState::release() {
283 if (auto api = engine_->api()) {
284 if (session_) {
285 api->destroy_session(session_);
286 }
287 session_ = 0;
288 }
289 }
290
commitPreedit(InputContext * ic)291 void RimeState::commitPreedit(InputContext *ic) {
292 if (auto api = engine_->api()) {
293 RIME_STRUCT(RimeContext, context);
294 if (!api->get_context(session_, &context)) {
295 return;
296 }
297 if (context.commit_text_preview) {
298 ic->commitString(context.commit_text_preview);
299 }
300 }
301 }
302
303 } // namespace fcitx
304