1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "openwnninputmethod_p.h" |
5 | #include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h> |
6 | #include <QLoggingCategory> |
7 | #include <openwnnenginejajp.h> |
8 | #include <composingtext.h> |
9 | #include <romkan.h> |
10 | #include <romkanfullkatakana.h> |
11 | #include <romkanhalfkatakana.h> |
12 | #include <QTextFormat> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | namespace QtVirtualKeyboard { |
16 | |
17 | Q_LOGGING_CATEGORY(lcOpenWnn, "qt.virtualkeyboard.openwnn" ) |
18 | |
19 | class OpenWnnInputMethodPrivate |
20 | { |
21 | Q_DECLARE_PUBLIC(OpenWnnInputMethod) |
22 | public: |
23 | enum EngineMode { |
24 | ENGINE_MODE_DEFAULT, |
25 | ENGINE_MODE_DIRECT, |
26 | ENGINE_MODE_NO_LV2_CONV, |
27 | ENGINE_MODE_FULL_KATAKANA, |
28 | ENGINE_MODE_HALF_KATAKANA, |
29 | }; |
30 | |
31 | enum ConvertType { |
32 | CONVERT_TYPE_NONE = 0, |
33 | CONVERT_TYPE_RENBUN = 1, |
34 | }; |
35 | |
36 | enum { |
37 | MAX_COMPOSING_TEXT = 30 |
38 | }; |
39 | |
40 | OpenWnnInputMethodPrivate(OpenWnnInputMethod *q_ptr) : |
41 | q_ptr(q_ptr), |
42 | inputMode(QVirtualKeyboardInputEngine::InputMode::Latin), |
43 | exactMatchMode(false), |
44 | converter(nullptr), |
45 | converterJAJP(), |
46 | activeConvertType(CONVERT_TYPE_NONE), |
47 | preConverter(nullptr), |
48 | enableLearning(true), |
49 | enablePrediction(true), |
50 | enableConverter(true), |
51 | disableUpdate(false), |
52 | commitCount(0), |
53 | targetLayer(ComposingText::LAYER1), |
54 | activeWordIndex(-1) |
55 | { |
56 | } |
57 | |
58 | void changeEngineMode(EngineMode mode) |
59 | { |
60 | switch (mode) { |
61 | case ENGINE_MODE_DIRECT: |
62 | /* Full/Half-width number or Full-width alphabet */ |
63 | converter = nullptr; |
64 | preConverter.reset(); |
65 | break; |
66 | |
67 | case ENGINE_MODE_NO_LV2_CONV: |
68 | converter = nullptr; |
69 | preConverter.reset(other: new Romkan()); |
70 | break; |
71 | |
72 | case ENGINE_MODE_FULL_KATAKANA: |
73 | converter = nullptr; |
74 | preConverter.reset(other: new RomkanFullKatakana()); |
75 | break; |
76 | |
77 | case ENGINE_MODE_HALF_KATAKANA: |
78 | converter = nullptr; |
79 | preConverter.reset(other: new RomkanHalfKatakana()); |
80 | break; |
81 | |
82 | default: |
83 | /* HIRAGANA input mode */ |
84 | setDictionary(OpenWnnEngineJAJP::DIC_LANG_JP); |
85 | converter = &converterJAJP; |
86 | preConverter.reset(other: new Romkan()); |
87 | break; |
88 | } |
89 | } |
90 | |
91 | void setDictionary(OpenWnnEngineJAJP::DictionaryType mode) |
92 | { |
93 | converterJAJP.setDictionary(mode); |
94 | } |
95 | |
96 | void breakSequence() |
97 | { |
98 | converterJAJP.breakSequence(); |
99 | } |
100 | |
101 | bool isEnableL2Converter() |
102 | { |
103 | return converter != nullptr && enableConverter; |
104 | } |
105 | |
106 | void startConvert(ConvertType convertType) |
107 | { |
108 | if (!isEnableL2Converter()) |
109 | return; |
110 | |
111 | if (activeConvertType != convertType) { |
112 | if (!exactMatchMode) { |
113 | if (convertType == CONVERT_TYPE_RENBUN) { |
114 | /* not specify */ |
115 | composingText.setCursor(layer: ComposingText::LAYER1, pos: 0); |
116 | } else { |
117 | if (activeConvertType == CONVERT_TYPE_RENBUN) { |
118 | exactMatchMode = true; |
119 | } else { |
120 | /* specify all range */ |
121 | composingText.setCursor(layer: ComposingText::LAYER1, |
122 | pos: composingText.size(layer: ComposingText::LAYER1)); |
123 | } |
124 | } |
125 | } |
126 | |
127 | if (convertType == CONVERT_TYPE_RENBUN) |
128 | /* clears variables for the prediction */ |
129 | exactMatchMode = false; |
130 | |
131 | /* clears variables for the convert */ |
132 | commitCount = 0; |
133 | |
134 | activeConvertType = convertType; |
135 | |
136 | updateViewStatus(layer: ComposingText::LAYER2, updateCandidates: true, updateEmptyText: true); |
137 | |
138 | focusNextCandidate(); |
139 | } |
140 | } |
141 | |
142 | void changeL2Segment(const QSharedPointer<WnnWord> &word) |
143 | { |
144 | if (word.isNull()) |
145 | return; |
146 | QList<StrSegment> ss; |
147 | ss.append(t: composingText.getStrSegment(layer: ComposingText::LAYER2, pos: 0)); |
148 | if (!ss[0].clause.isNull()) |
149 | ss[0].clause->candidate = word->candidate; |
150 | ss[0].string = word->candidate; |
151 | composingText.replaceStrSegment(layer: ComposingText::LAYER2, str: ss); |
152 | if (lcOpenWnn().isDebugEnabled()) |
153 | composingText.debugout(); |
154 | updateViewStatus(layer: ComposingText::LAYER2, updateCandidates: false, updateEmptyText: false); |
155 | } |
156 | |
157 | void initializeScreen() |
158 | { |
159 | if (composingText.size(layer: ComposingText::LAYER0) != 0) { |
160 | Q_Q(OpenWnnInputMethod); |
161 | q->inputContext()->commit(text: QString()); |
162 | } |
163 | composingText.clear(); |
164 | exactMatchMode = false; |
165 | activeConvertType = CONVERT_TYPE_NONE; |
166 | clearCandidates(); |
167 | } |
168 | |
169 | void updateViewStatusForPrediction(bool updateCandidates, bool updateEmptyText) |
170 | { |
171 | activeConvertType = CONVERT_TYPE_NONE; |
172 | |
173 | updateViewStatus(layer: ComposingText::LAYER1, updateCandidates, updateEmptyText); |
174 | } |
175 | |
176 | void updateViewStatus(ComposingText::TextLayer layer, bool updateCandidates, bool updateEmptyText) |
177 | { |
178 | targetLayer = layer; |
179 | |
180 | if (updateCandidates) |
181 | updateCandidateView(); |
182 | |
183 | /* set the text for displaying as the composing text */ |
184 | displayText.clear(); |
185 | displayText.insert(i: 0, s: composingText.toString(layer)); |
186 | |
187 | /* add decoration to the text */ |
188 | if (!displayText.isEmpty() || updateEmptyText) { |
189 | |
190 | QList<QInputMethodEvent::Attribute> attributes; |
191 | |
192 | int cursor = composingText.getCursor(layer); |
193 | if (cursor != 0) { |
194 | int highlightEnd = 0; |
195 | |
196 | if (exactMatchMode) { |
197 | |
198 | QTextCharFormat textFormat; |
199 | textFormat.setBackground(QBrush(QColor(0x66, 0xCD, 0xAA))); |
200 | textFormat.setForeground(QBrush(Qt::black)); |
201 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, cursor, textFormat)); |
202 | highlightEnd = cursor; |
203 | |
204 | } else if (layer == ComposingText::LAYER2) { |
205 | |
206 | highlightEnd = composingText.toString(layer, from: 0, to: 0).size(); |
207 | |
208 | /* highlights the first segment */ |
209 | QTextCharFormat textFormat; |
210 | textFormat.setBackground(QBrush(QColor(0x88, 0x88, 0xFF))); |
211 | textFormat.setForeground(QBrush(Qt::black)); |
212 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, highlightEnd, textFormat)); |
213 | } |
214 | |
215 | if (highlightEnd != 0 && highlightEnd < displayText.size()) { |
216 | /* highlights remaining text */ |
217 | QTextCharFormat textFormat; |
218 | textFormat.setBackground(QBrush(QColor(0xF0, 0xFF, 0xFF))); |
219 | textFormat.setForeground(QBrush(Qt::black)); |
220 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, highlightEnd, displayText.size() - highlightEnd, textFormat)); |
221 | } |
222 | } |
223 | |
224 | QTextCharFormat textFormat; |
225 | textFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); |
226 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, displayText.size(), textFormat)); |
227 | |
228 | int displayCursor = composingText.toString(layer, from: 0, to: cursor - 1).size(); |
229 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, displayCursor, 1, QVariant())); |
230 | |
231 | Q_Q(OpenWnnInputMethod); |
232 | q->inputContext()->setPreeditText(text: displayText, attributes); |
233 | } |
234 | } |
235 | |
236 | void updateCandidateView() |
237 | { |
238 | switch (targetLayer) { |
239 | case ComposingText::LAYER0: |
240 | case ComposingText::LAYER1: /* prediction */ |
241 | if (enablePrediction) |
242 | /* update the candidates view */ |
243 | updatePrediction(); |
244 | break; |
245 | case ComposingText::LAYER2: /* convert */ |
246 | if (commitCount == 0) |
247 | converter->convert(text&: composingText); |
248 | |
249 | if (converter->makeCandidateListOf(clausePosition: commitCount) != 0) { |
250 | composingText.setCursor(layer: ComposingText::LAYER2, pos: 1); |
251 | displayCandidates(); |
252 | } else { |
253 | composingText.setCursor(layer: ComposingText::LAYER1, |
254 | pos: composingText.toString(layer: ComposingText::LAYER1).size()); |
255 | clearCandidates(); |
256 | } |
257 | break; |
258 | default: |
259 | break; |
260 | } |
261 | } |
262 | |
263 | void updatePrediction() |
264 | { |
265 | int candidates = 0; |
266 | int cursor = composingText.getCursor(layer: ComposingText::LAYER1); |
267 | if (isEnableL2Converter()) { |
268 | if (exactMatchMode) |
269 | /* exact matching */ |
270 | candidates = converter->predict(text: composingText, minLen: 0, maxLen: cursor); |
271 | else |
272 | /* normal prediction */ |
273 | candidates = converter->predict(text: composingText, minLen: 0, maxLen: -1); |
274 | } |
275 | |
276 | /* update the candidates view */ |
277 | if (candidates > 0) |
278 | displayCandidates(); |
279 | else |
280 | clearCandidates(); |
281 | } |
282 | |
283 | void displayCandidates() |
284 | { |
285 | int previousActiveWordIndex = activeWordIndex; |
286 | bool wasEmpty = candidateList.isEmpty(); |
287 | clearCandidates(deferUpdate: true); |
288 | |
289 | QSharedPointer<WnnWord> result; |
290 | while ((result = converter->getNextCandidate())) |
291 | candidateList.append(t: result); |
292 | |
293 | Q_Q(OpenWnnInputMethod); |
294 | if (!candidateList.isEmpty() || !wasEmpty) |
295 | emit q->selectionListChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList); |
296 | if (previousActiveWordIndex != activeWordIndex) |
297 | emit q->selectionListActiveItemChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList, index: activeWordIndex); |
298 | } |
299 | |
300 | void clearCandidates(bool deferUpdate = false) |
301 | { |
302 | if (!candidateList.isEmpty()) { |
303 | candidateList.clear(); |
304 | if (!deferUpdate) { |
305 | Q_Q(OpenWnnInputMethod); |
306 | emit q->selectionListChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList); |
307 | } |
308 | clearFocusCandidate(deferUpdate); |
309 | } |
310 | } |
311 | |
312 | QSharedPointer<WnnWord> focusNextCandidate() |
313 | { |
314 | Q_Q(OpenWnnInputMethod); |
315 | if (candidateList.isEmpty()) |
316 | return QSharedPointer<WnnWord>(); |
317 | activeWordIndex++; |
318 | if (activeWordIndex >= candidateList.size()) |
319 | activeWordIndex = 0; |
320 | emit q->selectionListActiveItemChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList, index: activeWordIndex); |
321 | return candidateList.at(i: activeWordIndex); |
322 | } |
323 | |
324 | void clearFocusCandidate(bool deferUpdate = false) |
325 | { |
326 | Q_Q(OpenWnnInputMethod); |
327 | if (activeWordIndex != -1) { |
328 | activeWordIndex = -1; |
329 | if (!deferUpdate) |
330 | emit q->selectionListActiveItemChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList, index: activeWordIndex); |
331 | } |
332 | } |
333 | |
334 | void fitInputType() |
335 | { |
336 | Q_Q(OpenWnnInputMethod); |
337 | enableConverter = true; |
338 | |
339 | Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints(); |
340 | if (inputMethodHints.testFlag(flag: Qt::ImhDigitsOnly) || |
341 | inputMethodHints.testFlag(flag: Qt::ImhFormattedNumbersOnly) || |
342 | inputMethodHints.testFlag(flag: Qt::ImhDialableCharactersOnly)) { |
343 | enableConverter = false; |
344 | } |
345 | |
346 | if (inputMethodHints.testFlag(flag: Qt::ImhLatinOnly)) { |
347 | enableConverter = false; |
348 | } |
349 | |
350 | if (inputMode != QVirtualKeyboardInputEngine::InputMode::Hiragana || |
351 | inputMethodHints.testFlag(flag: Qt::ImhHiddenText) || |
352 | inputMethodHints.testFlag(flag: Qt::ImhSensitiveData) || |
353 | inputMethodHints.testFlag(flag: Qt::ImhNoPredictiveText)) { |
354 | if (enablePrediction) { |
355 | enablePrediction = false; |
356 | emit q->selectionListsChanged(); |
357 | } |
358 | } else if (inputMode == QVirtualKeyboardInputEngine::InputMode::Hiragana && !enablePrediction) { |
359 | enablePrediction = true; |
360 | emit q->selectionListsChanged(); |
361 | } |
362 | |
363 | activeConvertType = CONVERT_TYPE_NONE; |
364 | } |
365 | |
366 | void learnWord(WnnWord &word) |
367 | { |
368 | if (enableLearning) |
369 | converter->learn(word); |
370 | } |
371 | |
372 | void learnWord(int index) |
373 | { |
374 | if (enableLearning && index < composingText.size(layer: ComposingText::LAYER2)) { |
375 | StrSegment seg = composingText.getStrSegment(layer: ComposingText::LAYER2, pos: index); |
376 | if (!seg.clause.isNull()) { |
377 | converter->learn(word&: *seg.clause); |
378 | } else { |
379 | QString stroke = composingText.toString(layer: ComposingText::LAYER1, from: seg.from, to: seg.to); |
380 | WnnWord word(seg.string, stroke); |
381 | converter->learn(word); |
382 | } |
383 | } |
384 | } |
385 | |
386 | void commitAll() |
387 | { |
388 | if (activeConvertType != CONVERT_TYPE_NONE) { |
389 | commitConvertingText(); |
390 | } else { |
391 | composingText.setCursor(layer: ComposingText::LAYER1, |
392 | pos: composingText.size(layer: ComposingText::LAYER1)); |
393 | commitText(learn: true); |
394 | } |
395 | } |
396 | |
397 | void commitConvertingText() |
398 | { |
399 | if (activeConvertType != CONVERT_TYPE_NONE) { |
400 | Q_Q(OpenWnnInputMethod); |
401 | int size = composingText.size(layer: ComposingText::LAYER2); |
402 | for (int i = 0; i < size; i++) { |
403 | learnWord(index: i); |
404 | } |
405 | |
406 | QString text = composingText.toString(layer: ComposingText::LAYER2); |
407 | disableUpdate = true; |
408 | q->inputContext()->commit(text); |
409 | disableUpdate = false; |
410 | |
411 | initializeScreen(); |
412 | } |
413 | } |
414 | |
415 | bool commitText(bool learn = false) |
416 | { |
417 | ComposingText::TextLayer layer = targetLayer; |
418 | int cursor = composingText.getCursor(layer); |
419 | if (cursor == 0) { |
420 | return false; |
421 | } |
422 | QString tmp = composingText.toString(layer, from: 0, to: cursor - 1); |
423 | |
424 | if (converter != nullptr) { |
425 | if (learn) { |
426 | if (activeConvertType == CONVERT_TYPE_RENBUN) { |
427 | learnWord(index: 0); /* select the top of the clauses */ |
428 | } else { |
429 | if (composingText.size(layer: ComposingText::LAYER1) != 0) { |
430 | QString stroke = composingText.toString(layer: ComposingText::LAYER1, from: 0, to: composingText.getCursor(layer) - 1); |
431 | WnnWord word(tmp, stroke); |
432 | learnWord(word); |
433 | } |
434 | } |
435 | } else { |
436 | breakSequence(); |
437 | } |
438 | } |
439 | return commitText(string: tmp); |
440 | } |
441 | |
442 | bool commitText(const WnnWord &word) |
443 | { |
444 | return commitText(string: word.candidate); |
445 | } |
446 | |
447 | bool commitText(const QString &string) |
448 | { |
449 | Q_Q(OpenWnnInputMethod); |
450 | ComposingText::TextLayer layer = targetLayer; |
451 | |
452 | disableUpdate = true; |
453 | q->inputContext()->commit(text: string); |
454 | disableUpdate = false; |
455 | |
456 | int cursor = composingText.getCursor(layer); |
457 | if (cursor > 0) { |
458 | composingText.deleteStrSegment(layer, from: 0, to: composingText.getCursor(layer) - 1); |
459 | composingText.setCursor(layer, pos: composingText.size(layer)); |
460 | } |
461 | exactMatchMode = false; |
462 | commitCount++; |
463 | |
464 | if ((layer == ComposingText::LAYER2) && (composingText.size(layer) == 0)) |
465 | layer = ComposingText::LAYER1; /* for connected prediction */ |
466 | |
467 | if (layer == ComposingText::LAYER2) { |
468 | activeConvertType = CONVERT_TYPE_RENBUN; |
469 | updateViewStatus(layer, updateCandidates: true, updateEmptyText: false); |
470 | focusNextCandidate(); |
471 | } else { |
472 | updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: false); |
473 | } |
474 | |
475 | return composingText.size(layer: ComposingText::LAYER0) > 0; |
476 | } |
477 | |
478 | bool isAlphabetLast(const QString &str) |
479 | { |
480 | if (str.isEmpty()) |
481 | return false; |
482 | ushort ch = str.at(i: str.size() - 1).unicode(); |
483 | return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); |
484 | } |
485 | |
486 | void commitTextWithoutLastAlphabet() |
487 | { |
488 | QString last = composingText.getStrSegment(layer: targetLayer, pos: -1).string; |
489 | |
490 | if (isAlphabetLast(str: last)) { |
491 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: -1); |
492 | commitText(learn: false); |
493 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: 1); |
494 | } else { |
495 | commitText(learn: false); |
496 | } |
497 | } |
498 | |
499 | bool processLeftKeyEvent() |
500 | { |
501 | if (composingText.size(layer: ComposingText::LAYER1) == 0) |
502 | return false; |
503 | |
504 | if (activeConvertType != CONVERT_TYPE_NONE) { |
505 | if (composingText.getCursor(layer: ComposingText::LAYER1) > 1) { |
506 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: -1); |
507 | } |
508 | } else if (exactMatchMode) { |
509 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: -1); |
510 | } else { |
511 | exactMatchMode = true; |
512 | } |
513 | |
514 | if (lcOpenWnn().isDebugEnabled()) |
515 | composingText.debugout(); |
516 | |
517 | commitCount = 0; /* retry consecutive clause conversion if necessary. */ |
518 | updateViewStatus(layer: targetLayer, updateCandidates: true, updateEmptyText: true); |
519 | |
520 | if (activeConvertType != CONVERT_TYPE_NONE) |
521 | focusNextCandidate(); |
522 | |
523 | return true; |
524 | } |
525 | |
526 | bool processRightKeyEvent() |
527 | { |
528 | if (composingText.size(layer: ComposingText::LAYER1) == 0) |
529 | return false; |
530 | |
531 | ComposingText::TextLayer layer = targetLayer; |
532 | if (exactMatchMode || activeConvertType != CONVERT_TYPE_NONE) { |
533 | int textSize = composingText.size(layer: ComposingText::LAYER1); |
534 | if (composingText.getCursor(layer: ComposingText::LAYER1) == textSize) { |
535 | exactMatchMode = false; |
536 | layer = ComposingText::LAYER1; /* convert -> prediction */ |
537 | activeConvertType = CONVERT_TYPE_NONE; |
538 | } else { |
539 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: 1); |
540 | } |
541 | } else { |
542 | if (composingText.getCursor(layer: ComposingText::LAYER1) < composingText.size(layer: ComposingText::LAYER1)) { |
543 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: 1); |
544 | } |
545 | } |
546 | |
547 | if (lcOpenWnn().isDebugEnabled()) |
548 | composingText.debugout(); |
549 | |
550 | commitCount = 0; /* retry consecutive clause conversion if necessary. */ |
551 | |
552 | updateViewStatus(layer, updateCandidates: true, updateEmptyText: true); |
553 | |
554 | if (activeConvertType != CONVERT_TYPE_NONE) |
555 | focusNextCandidate(); |
556 | |
557 | return true; |
558 | } |
559 | |
560 | OpenWnnInputMethod *q_ptr; |
561 | QVirtualKeyboardInputEngine::InputMode inputMode; |
562 | bool exactMatchMode; |
563 | QString displayText; |
564 | OpenWnnEngineJAJP *converter; |
565 | OpenWnnEngineJAJP converterJAJP; |
566 | ConvertType activeConvertType; |
567 | ComposingText composingText; |
568 | QScopedPointer<LetterConverter> preConverter; |
569 | bool enableLearning; |
570 | bool enablePrediction; |
571 | bool enableConverter; |
572 | bool disableUpdate; |
573 | int commitCount; |
574 | ComposingText::TextLayer targetLayer; |
575 | QList<QSharedPointer<WnnWord> > candidateList; |
576 | int activeWordIndex; |
577 | }; |
578 | |
579 | /*! |
580 | \class QtVirtualKeyboard::OpenWnnInputMethod |
581 | \internal |
582 | */ |
583 | |
584 | OpenWnnInputMethod::OpenWnnInputMethod(QObject *parent) : |
585 | QVirtualKeyboardAbstractInputMethod(parent), |
586 | d_ptr(new OpenWnnInputMethodPrivate(this)) |
587 | { |
588 | } |
589 | |
590 | OpenWnnInputMethod::~OpenWnnInputMethod() |
591 | { |
592 | } |
593 | |
594 | QList<QVirtualKeyboardInputEngine::InputMode> OpenWnnInputMethod::inputModes(const QString &locale) |
595 | { |
596 | Q_UNUSED(locale); |
597 | return QList<QVirtualKeyboardInputEngine::InputMode>() |
598 | << QVirtualKeyboardInputEngine::InputMode::Hiragana |
599 | << QVirtualKeyboardInputEngine::InputMode::Katakana |
600 | << QVirtualKeyboardInputEngine::InputMode::FullwidthLatin |
601 | << QVirtualKeyboardInputEngine::InputMode::Latin; |
602 | } |
603 | |
604 | bool OpenWnnInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) |
605 | { |
606 | Q_UNUSED(locale); |
607 | Q_D(OpenWnnInputMethod); |
608 | if (d->inputMode == inputMode) |
609 | return true; |
610 | update(); |
611 | switch (inputMode) { |
612 | case QVirtualKeyboardInputEngine::InputMode::Hiragana: |
613 | d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_DEFAULT); |
614 | break; |
615 | |
616 | case QVirtualKeyboardInputEngine::InputMode::Katakana: |
617 | d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_FULL_KATAKANA); |
618 | break; |
619 | |
620 | default: |
621 | d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_DIRECT); |
622 | break; |
623 | } |
624 | d->inputMode = inputMode; |
625 | d->fitInputType(); |
626 | return true; |
627 | } |
628 | |
629 | bool OpenWnnInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase) |
630 | { |
631 | Q_UNUSED(textCase); |
632 | return true; |
633 | } |
634 | |
635 | bool OpenWnnInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) |
636 | { |
637 | Q_UNUSED(key); |
638 | Q_UNUSED(text); |
639 | Q_UNUSED(modifiers); |
640 | Q_D(OpenWnnInputMethod); |
641 | |
642 | if (d->preConverter == nullptr && !d->isEnableL2Converter()) |
643 | return false; |
644 | |
645 | switch (key) { |
646 | case Qt::Key_Left: |
647 | if (d->isEnableL2Converter() && d->composingText.size(layer: ComposingText::LAYER1) > 0) |
648 | return d->processLeftKeyEvent(); |
649 | else |
650 | return d->commitText(learn: false); |
651 | break; |
652 | |
653 | case Qt::Key_Right: |
654 | if (d->isEnableL2Converter() && d->composingText.size(layer: ComposingText::LAYER1) > 0) |
655 | return d->processRightKeyEvent(); |
656 | else |
657 | return d->commitText(learn: false); |
658 | break; |
659 | |
660 | case Qt::Key_Backspace: |
661 | if (d->composingText.size(layer: ComposingText::LAYER1) > 0) { |
662 | if (d->activeConvertType == OpenWnnInputMethodPrivate::CONVERT_TYPE_RENBUN) { |
663 | d->composingText.setCursor(layer: ComposingText::LAYER1, |
664 | pos: d->composingText.toString(layer: ComposingText::LAYER1).size()); |
665 | d->exactMatchMode = false; |
666 | d->clearFocusCandidate(); |
667 | } else { |
668 | if ((d->composingText.size(layer: ComposingText::LAYER1) == 1) && |
669 | d->composingText.getCursor(layer: ComposingText::LAYER1) != 0) { |
670 | d->initializeScreen(); |
671 | return true; |
672 | } else { |
673 | d->composingText.deleteAt(layer: ComposingText::LAYER1, rightside: false); |
674 | } |
675 | } |
676 | if (lcOpenWnn().isDebugEnabled()) |
677 | d->composingText.debugout(); |
678 | d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true); |
679 | return true; |
680 | } |
681 | break; |
682 | |
683 | case Qt::Key_Space: |
684 | if (d->composingText.size(layer: ComposingText::LAYER0) == 0) { |
685 | d->clearCandidates(); |
686 | d->breakSequence(); |
687 | } else { |
688 | if (d->targetLayer == ComposingText::LAYER2) |
689 | d->changeL2Segment(word: d->focusNextCandidate()); |
690 | else if (d->isEnableL2Converter()) |
691 | d->startConvert(convertType: OpenWnnInputMethodPrivate::CONVERT_TYPE_RENBUN); |
692 | else |
693 | return d->commitText(learn: false); |
694 | return true; |
695 | } |
696 | break; |
697 | |
698 | case Qt::Key_Return: |
699 | case Qt::Key_Enter: |
700 | if (d->composingText.size(layer: ComposingText::LAYER0) > 0) { |
701 | d->commitText(learn: true); |
702 | return true; |
703 | } |
704 | break; |
705 | |
706 | default: |
707 | if (key < Qt::Key_Escape && !text.isEmpty() && text.at(i: 0).isPrint()) { |
708 | if (d->composingText.size(layer: ComposingText::LAYER1) + text.size() > OpenWnnInputMethodPrivate::MAX_COMPOSING_TEXT) |
709 | return true; |
710 | const int last = text.size() - 1; |
711 | for (int i = 0; i <= last; ++i) { |
712 | if (d->isEnableL2Converter()) { |
713 | d->commitConvertingText(); |
714 | d->composingText.insertStrSegment(layer1: ComposingText::LAYER0, layer2: ComposingText::LAYER1, str: text.mid(position: i, n: 1)); |
715 | if (d->preConverter != nullptr) |
716 | d->preConverter->convert(text&: d->composingText); |
717 | if (i == last) |
718 | d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true); |
719 | } else { |
720 | d->composingText.insertStrSegment(layer1: ComposingText::LAYER0, layer2: ComposingText::LAYER1, str: text.mid(position: i, n: 1)); |
721 | QString layer1 = d->composingText.toString(layer: ComposingText::LAYER1); |
722 | if (!d->isAlphabetLast(str: layer1)) { |
723 | d->commitText(learn: false); |
724 | } else { |
725 | bool completed = d->preConverter->convert(text&: d->composingText); |
726 | if (completed) { |
727 | d->commitTextWithoutLastAlphabet(); |
728 | } else { |
729 | if (i == last) |
730 | d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true); |
731 | } |
732 | } |
733 | } |
734 | } |
735 | if (lcOpenWnn().isDebugEnabled()) |
736 | d->composingText.debugout(); |
737 | return true; |
738 | } |
739 | break; |
740 | } |
741 | |
742 | return false; |
743 | } |
744 | |
745 | QList<QVirtualKeyboardSelectionListModel::Type> OpenWnnInputMethod::selectionLists() |
746 | { |
747 | Q_D(OpenWnnInputMethod); |
748 | if (!d->enablePrediction) |
749 | return QList<QVirtualKeyboardSelectionListModel::Type>(); |
750 | return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList; |
751 | } |
752 | |
753 | int OpenWnnInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type) |
754 | { |
755 | Q_UNUSED(type); |
756 | Q_D(OpenWnnInputMethod); |
757 | return d->candidateList.size(); |
758 | } |
759 | |
760 | QVariant OpenWnnInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role) |
761 | { |
762 | QVariant result; |
763 | Q_D(OpenWnnInputMethod); |
764 | switch (role) { |
765 | case QVirtualKeyboardSelectionListModel::Role::Display: |
766 | result = QVariant(d->candidateList.at(i: index)->candidate); |
767 | break; |
768 | case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength: |
769 | result.setValue(0); |
770 | break; |
771 | default: |
772 | result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role); |
773 | break; |
774 | } |
775 | return result; |
776 | } |
777 | |
778 | void OpenWnnInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index) |
779 | { |
780 | Q_UNUSED(type); |
781 | Q_D(OpenWnnInputMethod); |
782 | d->activeWordIndex = index; |
783 | // Set selected text as preeditText to place cursor at the end of selected text |
784 | inputContext()->setPreeditText(text: d->candidateList.at(i: index)->candidate); |
785 | d->commitText(word: *d->candidateList.at(i: index)); |
786 | } |
787 | |
788 | void OpenWnnInputMethod::reset() |
789 | { |
790 | Q_D(OpenWnnInputMethod); |
791 | d->composingText.clear(); |
792 | d->initializeScreen(); |
793 | d->fitInputType(); |
794 | } |
795 | |
796 | void OpenWnnInputMethod::update() |
797 | { |
798 | Q_D(OpenWnnInputMethod); |
799 | if (!d->disableUpdate) { |
800 | d->commitAll(); |
801 | reset(); |
802 | } |
803 | } |
804 | |
805 | } // namespace QtVirtualKeyboard |
806 | QT_END_NAMESPACE |
807 | |