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}
603
604bool 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
629bool OpenWnnInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase)
630{
631 Q_UNUSED(textCase);
632 return true;
633}
634
635bool 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
745QList<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
753int OpenWnnInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type)
754{
755 Q_UNUSED(type);
756 Q_D(OpenWnnInputMethod);
757 return d->candidateList.size();
758}
759
760QVariant 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
778void 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
788void OpenWnnInputMethod::reset()
789{
790 Q_D(OpenWnnInputMethod);
791 d->composingText.clear();
792 d->initializeScreen();
793 d->fitInputType();
794}
795
796void OpenWnnInputMethod::update()
797{
798 Q_D(OpenWnnInputMethod);
799 if (!d->disableUpdate) {
800 d->commitAll();
801 reset();
802 }
803}
804
805} // namespace QtVirtualKeyboard
806QT_END_NAMESPACE
807

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