1 /*
2  * SPDX-FileCopyrightText: 2012-2017 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 #include "unicode.h"
8 #include <fmt/format.h>
9 #include "fcitx-utils/i18n.h"
10 #include "fcitx-utils/inputbuffer.h"
11 #include "fcitx-utils/utf8.h"
12 #include "fcitx/addonmanager.h"
13 #include "fcitx/inputcontext.h"
14 #include "fcitx/inputcontextmanager.h"
15 #include "fcitx/inputpanel.h"
16 
17 namespace fcitx {
18 
19 class UnicodeState : public InputContextProperty {
20 public:
UnicodeState(Unicode * q)21     UnicodeState(Unicode *q) : q_(q) { buffer_.setMaxSize(30); }
22 
23     bool enabled_ = false;
24     InputBuffer buffer_;
25     Unicode *q_;
26 
reset(InputContext * ic)27     void reset(InputContext *ic) {
28         enabled_ = false;
29         buffer_.clear();
30         buffer_.shrinkToFit();
31         ic->inputPanel().reset();
32         ic->updatePreedit();
33         ic->updateUserInterface(UserInterfaceComponent::InputPanel);
34     }
35 };
36 
37 class UnicodeCandidateWord : public CandidateWord {
38 public:
UnicodeCandidateWord(Unicode * q,int c)39     UnicodeCandidateWord(Unicode *q, int c) : q_(q) {
40         Text text;
41         text.append(utf8::UCS4ToUTF8(c));
42         text.append(" ");
43         text.append(q->data().name(c));
44         setText(std::move(text));
45     }
46 
select(InputContext * inputContext) const47     void select(InputContext *inputContext) const override {
48         auto commit = text().stringAt(0);
49         auto *state = inputContext->propertyFor(&q_->factory());
50         state->reset(inputContext);
51         inputContext->commitString(commit);
52     }
53 
54     Unicode *q_;
55 };
56 
Unicode(Instance * instance)57 Unicode::Unicode(Instance *instance)
58     : instance_(instance),
59       factory_([this](InputContext &) { return new UnicodeState(this); }) {
60     instance_->inputContextManager().registerProperty("unicodeState",
61                                                       &factory_);
62 
63     KeySym syms[] = {
64         FcitxKey_1, FcitxKey_2, FcitxKey_3, FcitxKey_4, FcitxKey_5,
65         FcitxKey_6, FcitxKey_7, FcitxKey_8, FcitxKey_9, FcitxKey_0,
66     };
67 
68     KeyStates states = KeyState::Alt;
69 
70     for (auto sym : syms) {
71         selectionKeys_.emplace_back(sym, states);
72     }
73     eventHandlers_.emplace_back(instance_->watchEvent(
74         EventType::InputContextKeyEvent, EventWatcherPhase::Default,
__anone76485b30202(Event &event) 75         [this](Event &event) {
76             auto &keyEvent = static_cast<KeyEvent &>(event);
77             if (keyEvent.isRelease()) {
78                 return;
79             }
80             if (keyEvent.key().checkKeyList(*config_.triggerKey) &&
81                 trigger(keyEvent.inputContext())) {
82                 keyEvent.filterAndAccept();
83                 return;
84             }
85         }));
86 
__anone76485b30302(Event &event) 87     auto reset = [this](Event &event) {
88         auto &icEvent = static_cast<InputContextEvent &>(event);
89         auto *state = icEvent.inputContext()->propertyFor(&factory_);
90         if (state->enabled_) {
91             state->reset(icEvent.inputContext());
92         }
93     };
94     eventHandlers_.emplace_back(instance_->watchEvent(
95         EventType::InputContextFocusOut, EventWatcherPhase::Default, reset));
96     eventHandlers_.emplace_back(instance_->watchEvent(
97         EventType::InputContextReset, EventWatcherPhase::Default, reset));
98     eventHandlers_.emplace_back(
99         instance_->watchEvent(EventType::InputContextSwitchInputMethod,
100                               EventWatcherPhase::Default, reset));
101     eventHandlers_.emplace_back(instance_->watchEvent(
102         EventType::InputContextKeyEvent, EventWatcherPhase::PreInputMethod,
__anone76485b30402(Event &event) 103         [this](Event &event) {
104             auto &keyEvent = static_cast<KeyEvent &>(event);
105             auto *inputContext = keyEvent.inputContext();
106             auto *state = inputContext->propertyFor(&factory_);
107             if (!state->enabled_) {
108                 return;
109             }
110 
111             // make sure no one else will handle it
112             keyEvent.filter();
113             if (keyEvent.isRelease()) {
114                 return;
115             }
116 
117             auto candidateList = inputContext->inputPanel().candidateList();
118             if (candidateList) {
119                 int idx = keyEvent.key().keyListIndex(selectionKeys_);
120                 if (idx >= 0) {
121                     keyEvent.accept();
122                     if (idx < candidateList->size()) {
123                         candidateList->candidate(idx).select(inputContext);
124                     }
125                     return;
126                 }
127 
128                 if (keyEvent.key().checkKeyList(
129                         instance_->globalConfig().defaultPrevPage())) {
130                     auto *pageable = candidateList->toPageable();
131                     if (!pageable->hasPrev()) {
132                         if (pageable->usedNextBefore()) {
133                             event.accept();
134                             return;
135                         }
136                     } else {
137                         event.accept();
138                         pageable->prev();
139                         inputContext->updateUserInterface(
140                             UserInterfaceComponent::InputPanel);
141                         return;
142                     }
143                 }
144 
145                 if (keyEvent.key().checkKeyList(
146                         instance_->globalConfig().defaultNextPage())) {
147                     keyEvent.filterAndAccept();
148                     candidateList->toPageable()->next();
149                     inputContext->updateUserInterface(
150                         UserInterfaceComponent::InputPanel);
151                     return;
152                 }
153 
154                 if (keyEvent.key().checkKeyList(
155                         instance_->globalConfig().defaultPrevCandidate())) {
156                     keyEvent.filterAndAccept();
157                     candidateList->toCursorMovable()->prevCandidate();
158                     inputContext->updateUserInterface(
159                         UserInterfaceComponent::InputPanel);
160                     return;
161                 }
162 
163                 if (keyEvent.key().checkKeyList(
164                         instance_->globalConfig().defaultNextCandidate())) {
165                     keyEvent.filterAndAccept();
166                     candidateList->toCursorMovable()->nextCandidate();
167                     inputContext->updateUserInterface(
168                         UserInterfaceComponent::InputPanel);
169                     return;
170                 }
171             }
172 
173             // and by pass all modifier
174             if (keyEvent.key().isModifier() || keyEvent.key().hasModifier()) {
175                 return;
176             }
177             if (keyEvent.key().check(FcitxKey_Escape)) {
178                 keyEvent.accept();
179                 state->reset(inputContext);
180                 return;
181             }
182             if (keyEvent.key().check(FcitxKey_Return) ||
183                 keyEvent.key().check(FcitxKey_KP_Enter)) {
184                 keyEvent.accept();
185                 if (candidateList->size() > 0 &&
186                     candidateList->cursorIndex() >= 0) {
187                     candidateList->candidate(candidateList->cursorIndex())
188                         .select(inputContext);
189                 }
190                 return;
191             }
192             if (keyEvent.key().check(FcitxKey_BackSpace)) {
193                 if (state->buffer_.empty()) {
194                     state->reset(inputContext);
195                 } else {
196                     if (state->buffer_.backspace()) {
197                         if (state->buffer_.empty()) {
198                             state->reset(inputContext);
199                         } else {
200                             updateUI(inputContext);
201                         }
202                     }
203                 }
204                 keyEvent.accept();
205                 return;
206             }
207 
208             // check compose first.
209             auto compose = instance_->processComposeString(
210                 inputContext, keyEvent.key().sym());
211 
212             // compose is invalid, ignore it.
213             if (!compose) {
214                 return event.accept();
215             }
216 
217             if (!compose->empty()) {
218                 state->buffer_.type(*compose);
219             } else {
220                 state->buffer_.type(Key::keySymToUnicode(keyEvent.key().sym()));
221             }
222             event.accept();
223 
224             updateUI(inputContext);
225         }));
226 
227     reloadConfig();
228 }
229 
~Unicode()230 Unicode::~Unicode() {}
231 
trigger(InputContext * inputContext)232 bool Unicode::trigger(InputContext *inputContext) {
233     if (!data_.load())
234         return false;
235     auto *state = inputContext->propertyFor(&factory_);
236     state->enabled_ = true;
237     updateUI(inputContext, true);
238     return true;
239 }
updateUI(InputContext * inputContext,bool trigger)240 void Unicode::updateUI(InputContext *inputContext, bool trigger) {
241     auto *state = inputContext->propertyFor(&factory_);
242     inputContext->inputPanel().reset();
243     if (!state->buffer_.empty()) {
244         auto result = data_.find(state->buffer_.userInput());
245         auto candidateList = std::make_unique<CommonCandidateList>();
246         candidateList->setPageSize(instance_->globalConfig().defaultPageSize());
247         for (auto c : result) {
248             if (utf8::UCS4IsValid(c)) {
249                 candidateList->append<UnicodeCandidateWord>(this, c);
250             }
251         }
252         if (candidateList->size()) {
253             candidateList->setGlobalCursorIndex(0);
254         }
255         candidateList->setSelectionKey(selectionKeys_);
256         candidateList->setLayoutHint(CandidateLayoutHint::Vertical);
257         inputContext->inputPanel().setCandidateList(std::move(candidateList));
258     } else if (trigger) {
259         auto candidateList = std::make_unique<CommonCandidateList>();
260 
261         std::vector<std::string> selectedTexts;
262         if (inputContext->capabilityFlags().test(
263                 CapabilityFlag::SurroundingText)) {
264             if (auto selected = inputContext->surroundingText().selectedText();
265                 !selected.empty()) {
266                 selectedTexts.push_back(std::move(selected));
267             }
268         }
269         if (clipboard()) {
270             if (selectedTexts.empty()) {
271                 auto primary =
272                     clipboard()->call<IClipboard::primary>(inputContext);
273                 if (std::find(selectedTexts.begin(), selectedTexts.end(),
274                               primary) == selectedTexts.end()) {
275                     selectedTexts.push_back(std::move(primary));
276                 }
277             }
278             auto clip = clipboard()->call<IClipboard::clipboard>(inputContext);
279             if (std::find(selectedTexts.begin(), selectedTexts.end(), clip) ==
280                 selectedTexts.end()) {
281                 selectedTexts.push_back(std::move(clip));
282             }
283         }
284         std::unordered_set<uint32_t> seenChar;
285         for (const auto &str : selectedTexts) {
286             if (!utf8::validate(str)) {
287                 continue;
288             }
289             // Hard limit to prevent do too much lookup.
290             constexpr int limit = 20;
291             int counter = 0;
292             for (auto c : utf8::MakeUTF8CharRange(str)) {
293                 if (!seenChar.insert(c).second) {
294                     continue;
295                 }
296                 auto name = data_.name(c);
297                 std::string display;
298                 if (!name.empty()) {
299                     display = fmt::format("{0} U+{1:04x} {2}",
300                                           utf8::UCS4ToUTF8(c), c, name);
301                 } else {
302                     display =
303                         fmt::format("{0} U+{1:04x}", utf8::UCS4ToUTF8(c), c);
304                 }
305                 candidateList->append<DisplayOnlyCandidateWord>(Text(display));
306                 if (counter >= limit) {
307                     break;
308                 }
309                 counter += 1;
310             }
311         }
312         candidateList->setSelectionKey(KeyList(10));
313         candidateList->setPageSize(instance_->globalConfig().defaultPageSize());
314         if (candidateList->size()) {
315             candidateList->setGlobalCursorIndex(0);
316         }
317 
318         candidateList->setLayoutHint(CandidateLayoutHint::Vertical);
319         inputContext->inputPanel().setCandidateList(std::move(candidateList));
320     }
321     Text preedit;
322     preedit.append(state->buffer_.userInput());
323     if (!state->buffer_.empty()) {
324         preedit.setCursor(state->buffer_.cursorByChar());
325     }
326 
327     Text auxUp(_("Unicode: "));
328     if (trigger) {
329         auxUp.append(_("(Type to search unicode by code or description)"));
330     }
331     // inputContext->inputPanel().setClientPreedit(preedit);
332     inputContext->inputPanel().setAuxUp(auxUp);
333     inputContext->inputPanel().setPreedit(preedit);
334     inputContext->updatePreedit();
335     inputContext->updateUserInterface(UserInterfaceComponent::InputPanel);
336 }
337 
338 class UnicodeModuleFactory : public AddonFactory {
create(AddonManager * manager)339     AddonInstance *create(AddonManager *manager) override {
340         return new Unicode(manager->instance());
341     }
342 };
343 } // namespace fcitx
344 
345 FCITX_ADDON_FACTORY(fcitx::UnicodeModuleFactory);
346