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