1 /*
2 * SPDX-FileCopyrightText: 2020~2020 CSSlayer <wengxt@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 */
7 #include "imselector.h"
8 #include "fcitx/addonfactory.h"
9 #include "fcitx/addonmanager.h"
10 #include "fcitx/inputmethodentry.h"
11 #include "fcitx/inputmethodmanager.h"
12
13 namespace fcitx {
14
15 namespace {
16
selectInputMethod(InputContext * ic,IMSelector * imSelector,const std::string & uniqueName,bool local)17 void selectInputMethod(InputContext *ic, IMSelector *imSelector,
18 const std::string &uniqueName, bool local) {
19 auto instance = imSelector->instance();
20 auto *state = ic->propertyFor(&imSelector->factory());
21 instance->setCurrentInputMethod(ic, uniqueName, local);
22 state->reset(ic);
23 instance->showInputMethodInformation(ic);
24 }
25
selectInputMethod(InputContext * ic,IMSelector * imSelector,size_t index,bool local)26 bool selectInputMethod(InputContext *ic, IMSelector *imSelector, size_t index,
27 bool local) {
28 auto &inputMethodManager = imSelector->instance()->inputMethodManager();
29 auto &list = inputMethodManager.currentGroup().inputMethodList();
30 if (index >= list.size()) {
31 return false;
32 }
33 selectInputMethod(
34 ic, imSelector,
35 inputMethodManager.entry(list[index].name())->uniqueName(), local);
36 return true;
37 }
38
39 } // namespace
40
41 class IMSelectorCandidateWord : public CandidateWord {
42 public:
IMSelectorCandidateWord(IMSelector * q,const InputMethodEntry * entry,bool local)43 IMSelectorCandidateWord(IMSelector *q, const InputMethodEntry *entry,
44 bool local)
45 : CandidateWord(Text(entry->name())), q_(q),
46 uniqueName_(entry->uniqueName()), local_(local) {}
47
select(InputContext * ic) const48 void select(InputContext *ic) const override {
49 selectInputMethod(ic, q_, uniqueName_, local_);
50 }
51
52 IMSelector *q_;
53 std::string uniqueName_;
54 bool local_;
55 };
56
IMSelector(Instance * instance)57 IMSelector::IMSelector(Instance *instance)
58 : instance_(instance),
59 factory_([](InputContext &) { return new IMSelectorState; }) {
60 eventHandlers_.emplace_back(instance_->watchEvent(
61 EventType::InputContextKeyEvent, EventWatcherPhase::Default,
__anonb952a0930302(Event &event) 62 [this](Event &event) {
63 auto &keyEvent = static_cast<KeyEvent &>(event);
64 if (keyEvent.isRelease()) {
65 return;
66 }
67 if (keyEvent.key().checkKeyList(config_.triggerKey.value()) &&
68 trigger(keyEvent.inputContext(), false)) {
69 keyEvent.filterAndAccept();
70 return;
71 }
72 if (keyEvent.key().checkKeyList(config_.triggerKeyLocal.value()) &&
73 trigger(keyEvent.inputContext(), true)) {
74 keyEvent.filterAndAccept();
75 return;
76 }
77 }));
78
79 // Select input method via hotkey.
80 eventHandlers_.emplace_back(instance_->watchEvent(
81 EventType::InputContextKeyEvent, EventWatcherPhase::PreInputMethod,
__anonb952a0930402(Event &event) 82 [this](Event &event) {
83 auto &keyEvent = static_cast<KeyEvent &>(event);
84 auto *inputContext = keyEvent.inputContext();
85 if (int index =
86 keyEvent.key().keyListIndex(config_.switchKey.value());
87 index >= 0 &&
88 selectInputMethod(inputContext, this, index, /*local=*/false)) {
89 keyEvent.filterAndAccept();
90 return;
91 }
92 if (int index =
93 keyEvent.key().keyListIndex(config_.switchKeyLocal.value());
94 index >= 0 &&
95 selectInputMethod(inputContext, this, index, /*local=*/true)) {
96 keyEvent.filterAndAccept();
97 return;
98 }
99 }));
100
__anonb952a0930502(Event &event) 101 auto reset = [this](Event &event) {
102 auto &icEvent = static_cast<InputContextEvent &>(event);
103 auto *state = icEvent.inputContext()->propertyFor(&factory_);
104 if (state->enabled_) {
105 state->reset(icEvent.inputContext());
106 }
107 };
108 eventHandlers_.emplace_back(instance_->watchEvent(
109 EventType::InputContextFocusOut, EventWatcherPhase::Default, reset));
110 eventHandlers_.emplace_back(instance_->watchEvent(
111 EventType::InputContextReset, EventWatcherPhase::Default, reset));
112 eventHandlers_.emplace_back(
113 instance_->watchEvent(EventType::InputContextSwitchInputMethod,
114 EventWatcherPhase::Default, reset));
115 eventHandlers_.emplace_back(instance_->watchEvent(
116 EventType::InputContextKeyEvent, EventWatcherPhase::PreInputMethod,
__anonb952a0930602(Event &event) 117 [this](Event &event) {
118 auto &keyEvent = static_cast<KeyEvent &>(event);
119 auto *inputContext = keyEvent.inputContext();
120 auto *state = inputContext->propertyFor(&factory_);
121 if (!state->enabled_) {
122 return;
123 }
124
125 // make sure no one else will handle it
126 keyEvent.filterAndAccept();
127 if (keyEvent.isRelease()) {
128 return;
129 }
130
131 auto candidateList = inputContext->inputPanel().candidateList();
132 if (candidateList && !candidateList->empty()) {
133 int idx = keyEvent.key().keyListIndex(selectionKeys_);
134 if (idx >= 0) {
135 if (idx < candidateList->size()) {
136 keyEvent.accept();
137 candidateList->candidate(idx).select(inputContext);
138 return;
139 }
140 }
141
142 if (keyEvent.key().check(FcitxKey_space) ||
143 keyEvent.key().check(FcitxKey_Return) ||
144 keyEvent.key().check(FcitxKey_KP_Enter)) {
145 keyEvent.accept();
146 if (candidateList->cursorIndex() >= 0) {
147 candidateList->candidate(candidateList->cursorIndex())
148 .select(inputContext);
149 }
150 return;
151 }
152
153 if (keyEvent.key().checkKeyList(
154 instance_->globalConfig().defaultPrevPage())) {
155 keyEvent.filterAndAccept();
156 candidateList->toPageable()->prev();
157 inputContext->updateUserInterface(
158 UserInterfaceComponent::InputPanel);
159 return;
160 }
161
162 if (keyEvent.key().checkKeyList(
163 instance_->globalConfig().defaultNextPage())) {
164 keyEvent.filterAndAccept();
165 candidateList->toPageable()->next();
166 inputContext->updateUserInterface(
167 UserInterfaceComponent::InputPanel);
168 return;
169 }
170
171 if (candidateList->size() &&
172 keyEvent.key().checkKeyList(
173 instance_->globalConfig().defaultPrevCandidate())) {
174 keyEvent.filterAndAccept();
175 candidateList->toCursorMovable()->prevCandidate();
176 inputContext->updateUserInterface(
177 UserInterfaceComponent::InputPanel);
178 return;
179 }
180
181 if (candidateList->size() &&
182 keyEvent.key().checkKeyList(
183 instance_->globalConfig().defaultNextCandidate())) {
184 keyEvent.filterAndAccept();
185 candidateList->toCursorMovable()->nextCandidate();
186 inputContext->updateUserInterface(
187 UserInterfaceComponent::InputPanel);
188 return;
189 }
190 }
191
192 // and by pass all modifier
193 if (keyEvent.key().isModifier() || keyEvent.key().hasModifier()) {
194 return;
195 }
196 if (keyEvent.key().check(FcitxKey_Escape) ||
197 keyEvent.key().check(FcitxKey_BackSpace) ||
198 keyEvent.key().check(FcitxKey_Delete)) {
199 keyEvent.accept();
200 state->reset(inputContext);
201 return;
202 }
203 }));
204
205 instance_->inputContextManager().registerProperty("imselector", &factory_);
206
207 std::array<KeySym, 10> syms = {
208 FcitxKey_1, FcitxKey_2, FcitxKey_3, FcitxKey_4, FcitxKey_5,
209 FcitxKey_6, FcitxKey_7, FcitxKey_8, FcitxKey_9, FcitxKey_0,
210 };
211
212 for (auto sym : syms) {
213 selectionKeys_.emplace_back(sym);
214 }
215
216 reloadConfig();
217 }
218
reloadConfig()219 void IMSelector::reloadConfig() { readAsIni(config_, "conf/imselector.conf"); }
220
trigger(InputContext * inputContext,bool local)221 bool IMSelector::trigger(InputContext *inputContext, bool local) {
222 const auto &list =
223 instance_->inputMethodManager().currentGroup().inputMethodList();
224 if (list.empty()) {
225 return false;
226 }
227 auto *state = inputContext->propertyFor(&factory_);
228 state->enabled_ = true;
229 inputContext->inputPanel().reset();
230 auto currentIM = instance_->inputMethod(inputContext);
231 auto candidateList = std::make_unique<CommonCandidateList>();
232 candidateList->setPageSize(10);
233 int idx = -1;
234 for (const auto &item : list) {
235 if (const auto *entry =
236 instance_->inputMethodManager().entry(item.name())) {
237 if (entry->uniqueName() == currentIM) {
238 idx = candidateList->totalSize();
239 }
240 candidateList->append<IMSelectorCandidateWord>(this, entry, local);
241 }
242 }
243 candidateList->setLayoutHint(CandidateLayoutHint::Vertical);
244 candidateList->setSelectionKey(selectionKeys_);
245 candidateList->setCursorPositionAfterPaging(
246 CursorPositionAfterPaging::ResetToFirst);
247 if (candidateList->size()) {
248 if (idx < 0) {
249 candidateList->setGlobalCursorIndex(0);
250 } else {
251 candidateList->setGlobalCursorIndex(idx);
252 candidateList->setPage(idx / candidateList->pageSize());
253 }
254 inputContext->inputPanel().setAuxUp(
255 Text(local ? _("Select local input method:")
256 : _("Select input method:")));
257 }
258 inputContext->inputPanel().setCandidateList(std::move(candidateList));
259 inputContext->updatePreedit();
260 inputContext->updateUserInterface(UserInterfaceComponent::InputPanel);
261 return true;
262 }
263
264 class IMSelectorFactory : public AddonFactory {
265 public:
create(AddonManager * manager)266 AddonInstance *create(AddonManager *manager) override {
267 return new IMSelector(manager->instance());
268 }
269 };
270
271 } // namespace fcitx
272
273 FCITX_ADDON_FACTORY(fcitx::IMSelectorFactory);
274