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