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
14QT_BEGIN_NAMESPACE
15namespace QtVirtualKeyboard {
16
17Q_LOGGING_CATEGORY(lcOpenWnn, "qt.virtualkeyboard.openwnn")
18
19class OpenWnnInputMethodPrivate
20{
21 Q_DECLARE_PUBLIC(OpenWnnInputMethod)
22public:
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
584OpenWnnInputMethod::OpenWnnInputMethod(QObject *parent) :
585 QVirtualKeyboardAbstractInputMethod(parent),
586 d_ptr(new OpenWnnInputMethodPrivate(this))
587{
588}
589
590OpenWnnInputMethod::~OpenWnnInputMethod()
591{
592}
593
594QList<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 << QVirtualKeyboardInputEngine::InputMode::HiraganaFlick;
603}
604
605bool OpenWnnInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
606{
607 Q_UNUSED(locale);
608 Q_D(OpenWnnInputMethod);
609 if (d->inputMode == inputMode)
610 return true;
611 update();
612 switch (inputMode) {
613 case QVirtualKeyboardInputEngine::InputMode::Hiragana:
614 d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_DEFAULT);
615 break;
616
617 case QVirtualKeyboardInputEngine::InputMode::Katakana:
618 d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_FULL_KATAKANA);
619 break;
620
621 default:
622 d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_DIRECT);
623 break;
624 }
625 d->inputMode = inputMode;
626 d->fitInputType();
627 return true;
628}
629
630bool OpenWnnInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase)
631{
632 Q_UNUSED(textCase);
633 return true;
634}
635
636bool OpenWnnInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
637{
638 Q_UNUSED(key);
639 Q_UNUSED(text);
640 Q_UNUSED(modifiers);
641 Q_D(OpenWnnInputMethod);
642
643 if (d->preConverter == nullptr && !d->isEnableL2Converter())
644 return false;
645
646 switch (key) {
647 case Qt::Key_Left:
648 if (d->isEnableL2Converter() && d->composingText.size(layer: ComposingText::LAYER1) > 0)
649 return d->processLeftKeyEvent();
650 else
651 return d->commitText(learn: false);
652 break;
653
654 case Qt::Key_Right:
655 if (d->isEnableL2Converter() && d->composingText.size(layer: ComposingText::LAYER1) > 0)
656 return d->processRightKeyEvent();
657 else
658 return d->commitText(learn: false);
659 break;
660
661 case Qt::Key_Backspace:
662 if (d->composingText.size(layer: ComposingText::LAYER1) > 0) {
663 if (d->activeConvertType == OpenWnnInputMethodPrivate::CONVERT_TYPE_RENBUN) {
664 d->composingText.setCursor(layer: ComposingText::LAYER1,
665 pos: d->composingText.toString(layer: ComposingText::LAYER1).size());
666 d->exactMatchMode = false;
667 d->clearFocusCandidate();
668 } else {
669 if ((d->composingText.size(layer: ComposingText::LAYER1) == 1) &&
670 d->composingText.getCursor(layer: ComposingText::LAYER1) != 0) {
671 d->initializeScreen();
672 return true;
673 } else {
674 d->composingText.deleteAt(layer: ComposingText::LAYER1, rightside: false);
675 }
676 }
677 if (lcOpenWnn().isDebugEnabled())
678 d->composingText.debugout();
679 d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true);
680 return true;
681 }
682 break;
683
684 case Qt::Key_Space:
685 if (d->composingText.size(layer: ComposingText::LAYER0) == 0) {
686 d->clearCandidates();
687 d->breakSequence();
688 } else {
689 if (d->targetLayer == ComposingText::LAYER2)
690 d->changeL2Segment(word: d->focusNextCandidate());
691 else if (d->isEnableL2Converter())
692 d->startConvert(convertType: OpenWnnInputMethodPrivate::CONVERT_TYPE_RENBUN);
693 else
694 return d->commitText(learn: false);
695 return true;
696 }
697 break;
698
699 case Qt::Key_Return:
700 case Qt::Key_Enter:
701 if (d->composingText.size(layer: ComposingText::LAYER0) > 0) {
702 d->commitText(learn: true);
703 return true;
704 }
705 break;
706
707 default:
708 if (key < Qt::Key_Escape && !text.isEmpty() && text.at(i: 0).isPrint()) {
709 if (d->composingText.size(layer: ComposingText::LAYER1) + text.size() > OpenWnnInputMethodPrivate::MAX_COMPOSING_TEXT)
710 return true;
711 const int last = text.size() - 1;
712 for (int i = 0; i <= last; ++i) {
713 if (d->isEnableL2Converter()) {
714 d->commitConvertingText();
715 d->composingText.insertStrSegment(layer1: ComposingText::LAYER0, layer2: ComposingText::LAYER1, str: text.mid(position: i, n: 1));
716 if (d->preConverter != nullptr)
717 d->preConverter->convert(text&: d->composingText);
718 if (i == last)
719 d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true);
720 } else {
721 d->composingText.insertStrSegment(layer1: ComposingText::LAYER0, layer2: ComposingText::LAYER1, str: text.mid(position: i, n: 1));
722 QString layer1 = d->composingText.toString(layer: ComposingText::LAYER1);
723 if (!d->isAlphabetLast(str: layer1)) {
724 d->commitText(learn: false);
725 } else {
726 bool completed = d->preConverter->convert(text&: d->composingText);
727 if (completed) {
728 d->commitTextWithoutLastAlphabet();
729 } else {
730 if (i == last)
731 d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true);
732 }
733 }
734 }
735 }
736 if (lcOpenWnn().isDebugEnabled())
737 d->composingText.debugout();
738 return true;
739 }
740 break;
741 }
742
743 return false;
744}
745
746QList<QVirtualKeyboardSelectionListModel::Type> OpenWnnInputMethod::selectionLists()
747{
748 Q_D(OpenWnnInputMethod);
749 if (!d->enablePrediction)
750 return QList<QVirtualKeyboardSelectionListModel::Type>();
751 return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList;
752}
753
754int OpenWnnInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type)
755{
756 Q_UNUSED(type);
757 Q_D(OpenWnnInputMethod);
758 return d->candidateList.size();
759}
760
761QVariant OpenWnnInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role)
762{
763 QVariant result;
764 Q_D(OpenWnnInputMethod);
765 switch (role) {
766 case QVirtualKeyboardSelectionListModel::Role::Display:
767 result = QVariant(d->candidateList.at(i: index)->candidate);
768 break;
769 case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength:
770 result.setValue(0);
771 break;
772 default:
773 result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role);
774 break;
775 }
776 return result;
777}
778
779void OpenWnnInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index)
780{
781 Q_UNUSED(type);
782 Q_D(OpenWnnInputMethod);
783 d->activeWordIndex = index;
784 // Set selected text as preeditText to place cursor at the end of selected text
785 inputContext()->setPreeditText(text: d->candidateList.at(i: index)->candidate);
786 d->commitText(word: *d->candidateList.at(i: index));
787}
788
789void OpenWnnInputMethod::reset()
790{
791 Q_D(OpenWnnInputMethod);
792 d->composingText.clear();
793 d->initializeScreen();
794 d->fitInputType();
795}
796
797void OpenWnnInputMethod::update()
798{
799 Q_D(OpenWnnInputMethod);
800 if (!d->disableUpdate) {
801 d->commitAll();
802 reset();
803 }
804}
805
806} // namespace QtVirtualKeyboard
807QT_END_NAMESPACE
808

source code of qtvirtualkeyboard/src/plugins/openwnn/openwnninputmethod.cpp