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