Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "openwnninputmethod.h"
++ #include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
++ #include <QLoggingCategory>
++ #include <openwnnenginejajp.h>
++ #include <composingtext.h>
++ #include <romkan.h>
++ #include <romkanfullkatakana.h>
++ #include <romkanhalfkatakana.h>
++ #include <QTextFormat>
++
++ QT_BEGIN_NAMESPACE
++ namespace QtVirtualKeyboard {
++
++ Q_LOGGING_CATEGORY(lcOpenWnn, "qt.virtualkeyboard.openwnn")
++
++ class OpenWnnInputMethodPrivate
++ {
++ Q_DECLARE_PUBLIC(OpenWnnInputMethod)
++ public:
++ enum EngineMode {
++ ENGINE_MODE_DEFAULT,
++ ENGINE_MODE_DIRECT,
++ ENGINE_MODE_NO_LV2_CONV,
++ ENGINE_MODE_FULL_KATAKANA,
++ ENGINE_MODE_HALF_KATAKANA,
++ };
++
++ enum ConvertType {
++ CONVERT_TYPE_NONE = 0,
++ CONVERT_TYPE_RENBUN = 1,
++ };
++
++ enum {
++ MAX_COMPOSING_TEXT = 30
++ };
++
++ OpenWnnInputMethodPrivate(OpenWnnInputMethod *q_ptr) :
++ q_ptr(q_ptr),
++ inputMode(QVirtualKeyboardInputEngine::InputMode::Latin),
++ exactMatchMode(false),
++ converter(nullptr),
++ converterJAJP(),
++ activeConvertType(CONVERT_TYPE_NONE),
++ preConverter(nullptr),
++ enableLearning(true),
++ enablePrediction(true),
++ enableConverter(true),
++ disableUpdate(false),
++ commitCount(0),
++ targetLayer(ComposingText::LAYER1),
++ activeWordIndex(-1)
++ {
++ }
++
++ void changeEngineMode(EngineMode mode)
++ {
++ switch (mode) {
++ case ENGINE_MODE_DIRECT:
++ /* Full/Half-width number or Full-width alphabet */
++ converter = nullptr;
++ preConverter.reset();
++ break;
++
++ case ENGINE_MODE_NO_LV2_CONV:
++ converter = nullptr;
++ preConverter.reset(new Romkan());
++ break;
++
++ case ENGINE_MODE_FULL_KATAKANA:
++ converter = nullptr;
++ preConverter.reset(new RomkanFullKatakana());
++ break;
++
++ case ENGINE_MODE_HALF_KATAKANA:
++ converter = nullptr;
++ preConverter.reset(new RomkanHalfKatakana());
++ break;
++
++ default:
++ /* HIRAGANA input mode */
++ setDictionary(OpenWnnEngineJAJP::DIC_LANG_JP);
++ converter = &converterJAJP;
++ preConverter.reset(new Romkan());
++ break;
++ }
++ }
++
++ void setDictionary(OpenWnnEngineJAJP::DictionaryType mode)
++ {
++ converterJAJP.setDictionary(mode);
++ }
++
++ void breakSequence()
++ {
++ converterJAJP.breakSequence();
++ }
++
++ bool isEnableL2Converter()
++ {
++ return converter != nullptr && enableConverter;
++ }
++
++ void startConvert(ConvertType convertType)
++ {
++ if (!isEnableL2Converter())
++ return;
++
++ if (activeConvertType != convertType) {
++ if (!exactMatchMode) {
++ if (convertType == CONVERT_TYPE_RENBUN) {
++ /* not specify */
++ composingText.setCursor(ComposingText::LAYER1, 0);
++ } else {
++ if (activeConvertType == CONVERT_TYPE_RENBUN) {
++ exactMatchMode = true;
++ } else {
++ /* specify all range */
++ composingText.setCursor(ComposingText::LAYER1,
++ composingText.size(ComposingText::LAYER1));
++ }
++ }
++ }
++
++ if (convertType == CONVERT_TYPE_RENBUN)
++ /* clears variables for the prediction */
++ exactMatchMode = false;
++
++ /* clears variables for the convert */
++ commitCount = 0;
++
++ activeConvertType = convertType;
++
++ updateViewStatus(ComposingText::LAYER2, true, true);
++
++ focusNextCandidate();
++ }
++ }
++
++ void changeL2Segment(const QSharedPointer<WnnWord> &word)
++ {
++ if (word.isNull())
++ return;
++ QList<StrSegment> ss;
++ ss.append(composingText.getStrSegment(ComposingText::LAYER2, 0));
++ if (!ss[0].clause.isNull())
++ ss[0].clause->candidate = word->candidate;
++ ss[0].string = word->candidate;
++ composingText.replaceStrSegment(ComposingText::LAYER2, ss);
++ if (lcOpenWnn().isDebugEnabled())
++ composingText.debugout();
++ updateViewStatus(ComposingText::LAYER2, false, false);
++ }
++
++ void initializeScreen()
++ {
++ if (composingText.size(ComposingText::LAYER0) != 0) {
++ Q_Q(OpenWnnInputMethod);
++ q->inputContext()->commit(QString());
++ }
++ composingText.clear();
++ exactMatchMode = false;
++ activeConvertType = CONVERT_TYPE_NONE;
++ clearCandidates();
++ }
++
++ void updateViewStatusForPrediction(bool updateCandidates, bool updateEmptyText)
++ {
++ activeConvertType = CONVERT_TYPE_NONE;
++
++ updateViewStatus(ComposingText::LAYER1, updateCandidates, updateEmptyText);
++ }
++
++ void updateViewStatus(ComposingText::TextLayer layer, bool updateCandidates, bool updateEmptyText)
++ {
++ targetLayer = layer;
++
++ if (updateCandidates)
++ updateCandidateView();
++
++ /* set the text for displaying as the composing text */
++ displayText.clear();
++ displayText.insert(0, composingText.toString(layer));
++
++ /* add decoration to the text */
++ if (!displayText.isEmpty() || updateEmptyText) {
++
++ QList<QInputMethodEvent::Attribute> attributes;
++
++ int cursor = composingText.getCursor(layer);
++ if (cursor != 0) {
++ int highlightEnd = 0;
++
++ if (exactMatchMode) {
++
++ QTextCharFormat textFormat;
++ textFormat.setBackground(QBrush(QColor(0x66, 0xCD, 0xAA)));
++ textFormat.setForeground(QBrush(Qt::black));
++ attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, cursor, textFormat));
++ highlightEnd = cursor;
++
++ } else if (layer == ComposingText::LAYER2) {
++
++ highlightEnd = composingText.toString(layer, 0, 0).size();
++
++ /* highlights the first segment */
++ QTextCharFormat textFormat;
++ textFormat.setBackground(QBrush(QColor(0x88, 0x88, 0xFF)));
++ textFormat.setForeground(QBrush(Qt::black));
++ attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, highlightEnd, textFormat));
++ }
++
++ if (highlightEnd != 0 && highlightEnd < displayText.size()) {
++ /* highlights remaining text */
++ QTextCharFormat textFormat;
++ textFormat.setBackground(QBrush(QColor(0xF0, 0xFF, 0xFF)));
++ textFormat.setForeground(QBrush(Qt::black));
++ attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, highlightEnd, displayText.size() - highlightEnd, textFormat));
++ }
++ }
++
++ QTextCharFormat textFormat;
++ textFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
++ attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, displayText.size(), textFormat));
++
++ int displayCursor = composingText.toString(layer, 0, cursor - 1).size();
++ attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, displayCursor, 1, QVariant()));
++
++ Q_Q(OpenWnnInputMethod);
++ q->inputContext()->setPreeditText(displayText, attributes);
++ }
++ }
++
++ void updateCandidateView()
++ {
++ switch (targetLayer) {
++ case ComposingText::LAYER0:
++ case ComposingText::LAYER1: /* prediction */
++ if (enablePrediction)
++ /* update the candidates view */
++ updatePrediction();
++ break;
++ case ComposingText::LAYER2: /* convert */
++ if (commitCount == 0)
++ converter->convert(composingText);
++
++ if (converter->makeCandidateListOf(commitCount) != 0) {
++ composingText.setCursor(ComposingText::LAYER2, 1);
++ displayCandidates();
++ } else {
++ composingText.setCursor(ComposingText::LAYER1,
++ composingText.toString(ComposingText::LAYER1).size());
++ clearCandidates();
++ }
++ break;
++ default:
++ break;
++ }
++ }
++
++ void updatePrediction()
++ {
++ int candidates = 0;
++ int cursor = composingText.getCursor(ComposingText::LAYER1);
++ if (isEnableL2Converter()) {
++ if (exactMatchMode)
++ /* exact matching */
++ candidates = converter->predict(composingText, 0, cursor);
++ else
++ /* normal prediction */
++ candidates = converter->predict(composingText, 0, -1);
++ }
++
++ /* update the candidates view */
++ if (candidates > 0)
++ displayCandidates();
++ else
++ clearCandidates();
++ }
++
++ void displayCandidates()
++ {
++ int previousActiveWordIndex = activeWordIndex;
++ bool wasEmpty = candidateList.isEmpty();
++ clearCandidates(true);
++
++ QSharedPointer<WnnWord> result;
++ while ((result = converter->getNextCandidate()))
++ candidateList.append(result);
++
++ Q_Q(OpenWnnInputMethod);
++ if (!candidateList.isEmpty() || !wasEmpty)
++ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ if (previousActiveWordIndex != activeWordIndex)
++ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex);
++ }
++
++ void clearCandidates(bool deferUpdate = false)
++ {
++ if (!candidateList.isEmpty()) {
++ candidateList.clear();
++ if (!deferUpdate) {
++ Q_Q(OpenWnnInputMethod);
++ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ }
++ clearFocusCandidate(deferUpdate);
++ }
++ }
++
++ QSharedPointer<WnnWord> focusNextCandidate()
++ {
++ Q_Q(OpenWnnInputMethod);
++ if (candidateList.isEmpty())
++ return QSharedPointer<WnnWord>();
++ activeWordIndex++;
++ if (activeWordIndex >= candidateList.size())
++ activeWordIndex = 0;
++ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex);
++ return candidateList.at(activeWordIndex);
++ }
++
++ void clearFocusCandidate(bool deferUpdate = false)
++ {
++ Q_Q(OpenWnnInputMethod);
++ if (activeWordIndex != -1) {
++ activeWordIndex = -1;
++ if (!deferUpdate)
++ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex);
++ }
++ }
++
++ void fitInputType()
++ {
++ Q_Q(OpenWnnInputMethod);
++ enableConverter = true;
++
++ Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
++ if (inputMethodHints.testFlag(Qt::ImhDigitsOnly) ||
++ inputMethodHints.testFlag(Qt::ImhFormattedNumbersOnly) ||
++ inputMethodHints.testFlag(Qt::ImhDialableCharactersOnly)) {
++ enableConverter = false;
++ }
++
++ if (inputMethodHints.testFlag(Qt::ImhLatinOnly)) {
++ enableConverter = false;
++ }
++
++ const bool hiraganaInputMode =
++ inputMode == QVirtualKeyboardInputEngine::InputMode::Hiragana ||
++ inputMode == QVirtualKeyboardInputEngine::InputMode::HiraganaFlick;
++ if (!hiraganaInputMode) {
++ if (enablePrediction) {
++ enablePrediction = false;
++ emit q->selectionListsChanged();
++ }
++ } else if (hiraganaInputMode && !enablePrediction) {
++ enablePrediction = true;
++ emit q->selectionListsChanged();
++ }
++
++ activeConvertType = CONVERT_TYPE_NONE;
++
++ // Note: Learning is disabled for sensitive text. However, learning
++ // is not implemented in OpenWnnDictionary::learnWord, so this has
++ // no practical effect here.
++ enableLearning =
++ !inputMethodHints.testFlag(Qt::ImhHiddenText) &&
++ !inputMethodHints.testFlag(Qt::ImhSensitiveData);
++ }
++
++ void learnWord(WnnWord &word)
++ {
++ if (enableLearning)
++ converter->learn(word);
++ }
++
++ void learnWord(int index)
++ {
++ if (enableLearning && index < composingText.size(ComposingText::LAYER2)) {
++ StrSegment seg = composingText.getStrSegment(ComposingText::LAYER2, index);
++ if (!seg.clause.isNull()) {
++ converter->learn(*seg.clause);
++ } else {
++ QString stroke = composingText.toString(ComposingText::LAYER1, seg.from, seg.to);
++ WnnWord word(seg.string, stroke);
++ converter->learn(word);
++ }
++ }
++ }
++
++ void commitAll()
++ {
++ if (activeConvertType != CONVERT_TYPE_NONE) {
++ commitConvertingText();
++ } else {
++ composingText.setCursor(ComposingText::LAYER1,
++ composingText.size(ComposingText::LAYER1));
++ commitText(true);
++ }
++ }
++
++ void commitConvertingText()
++ {
++ if (activeConvertType != CONVERT_TYPE_NONE) {
++ Q_Q(OpenWnnInputMethod);
++ int size = composingText.size(ComposingText::LAYER2);
++ for (int i = 0; i < size; i++) {
++ learnWord(i);
++ }
++
++ QString text = composingText.toString(ComposingText::LAYER2);
++ disableUpdate = true;
++ q->inputContext()->commit(text);
++ disableUpdate = false;
++
++ initializeScreen();
++ }
++ }
++
++ bool commitText(bool learn = false)
++ {
++ ComposingText::TextLayer layer = targetLayer;
++ int cursor = composingText.getCursor(layer);
++ if (cursor == 0) {
++ return false;
++ }
++ QString tmp = composingText.toString(layer, 0, cursor - 1);
++
++ if (converter != nullptr) {
++ if (learn) {
++ if (activeConvertType == CONVERT_TYPE_RENBUN) {
++ learnWord(0); /* select the top of the clauses */
++ } else {
++ if (composingText.size(ComposingText::LAYER1) != 0) {
++ QString stroke = composingText.toString(ComposingText::LAYER1, 0, composingText.getCursor(layer) - 1);
++ WnnWord word(tmp, stroke);
++ learnWord(word);
++ }
++ }
++ } else {
++ breakSequence();
++ }
++ }
++ return commitText(tmp);
++ }
++
++ bool commitText(const WnnWord &word)
++ {
++ return commitText(word.candidate);
++ }
++
++ bool commitText(const QString &string)
++ {
++ Q_Q(OpenWnnInputMethod);
++ ComposingText::TextLayer layer = targetLayer;
++
++ disableUpdate = true;
++ q->inputContext()->commit(string);
++ disableUpdate = false;
++
++ int cursor = composingText.getCursor(layer);
++ if (cursor > 0) {
++ composingText.deleteStrSegment(layer, 0, composingText.getCursor(layer) - 1);
++ composingText.setCursor(layer, composingText.size(layer));
++ }
++ exactMatchMode = false;
++ commitCount++;
++
++ if ((layer == ComposingText::LAYER2) && (composingText.size(layer) == 0))
++ layer = ComposingText::LAYER1; /* for connected prediction */
++
++ if (layer == ComposingText::LAYER2) {
++ activeConvertType = CONVERT_TYPE_RENBUN;
++ updateViewStatus(layer, true, false);
++ focusNextCandidate();
++ } else {
++ updateViewStatusForPrediction(true, false);
++ }
++
++ return composingText.size(ComposingText::LAYER0) > 0;
++ }
++
++ bool isAlphabetLast(const QString &str)
++ {
++ if (str.isEmpty())
++ return false;
++ ushort ch = str.at(str.size() - 1).unicode();
++ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
++ }
++
++ void commitTextWithoutLastAlphabet()
++ {
++ QString last = composingText.getStrSegment(targetLayer, -1).string;
++
++ if (isAlphabetLast(last)) {
++ composingText.moveCursor(ComposingText::LAYER1, -1);
++ commitText(false);
++ composingText.moveCursor(ComposingText::LAYER1, 1);
++ } else {
++ commitText(false);
++ }
++ }
++
++ bool processLeftKeyEvent()
++ {
++ if (composingText.size(ComposingText::LAYER1) == 0)
++ return false;
++
++ if (activeConvertType != CONVERT_TYPE_NONE) {
++ if (composingText.getCursor(ComposingText::LAYER1) > 1) {
++ composingText.moveCursor(ComposingText::LAYER1, -1);
++ }
++ } else if (exactMatchMode) {
++ composingText.moveCursor(ComposingText::LAYER1, -1);
++ } else {
++ exactMatchMode = true;
++ }
++
++ if (lcOpenWnn().isDebugEnabled())
++ composingText.debugout();
++
++ commitCount = 0; /* retry consecutive clause conversion if necessary. */
++ updateViewStatus(targetLayer, true, true);
++
++ if (activeConvertType != CONVERT_TYPE_NONE)
++ focusNextCandidate();
++
++ return true;
++ }
++
++ bool processRightKeyEvent()
++ {
++ if (composingText.size(ComposingText::LAYER1) == 0)
++ return false;
++
++ ComposingText::TextLayer layer = targetLayer;
++ if (exactMatchMode || activeConvertType != CONVERT_TYPE_NONE) {
++ int textSize = composingText.size(ComposingText::LAYER1);
++ if (composingText.getCursor(ComposingText::LAYER1) == textSize) {
++ exactMatchMode = false;
++ layer = ComposingText::LAYER1; /* convert -> prediction */
++ activeConvertType = CONVERT_TYPE_NONE;
++ } else {
++ composingText.moveCursor(ComposingText::LAYER1, 1);
++ }
++ } else {
++ if (composingText.getCursor(ComposingText::LAYER1) < composingText.size(ComposingText::LAYER1)) {
++ composingText.moveCursor(ComposingText::LAYER1, 1);
++ }
++ }
++
++ if (lcOpenWnn().isDebugEnabled())
++ composingText.debugout();
++
++ commitCount = 0; /* retry consecutive clause conversion if necessary. */
++
++ updateViewStatus(layer, true, true);
++
++ if (activeConvertType != CONVERT_TYPE_NONE)
++ focusNextCandidate();
++
++ return true;
++ }
++
++ OpenWnnInputMethod *q_ptr;
++ QVirtualKeyboardInputEngine::InputMode inputMode;
++ bool exactMatchMode;
++ QString displayText;
++ OpenWnnEngineJAJP *converter;
++ OpenWnnEngineJAJP converterJAJP;
++ ConvertType activeConvertType;
++ ComposingText composingText;
++ QScopedPointer<LetterConverter> preConverter;
++ bool enableLearning;
++ bool enablePrediction;
++ bool enableConverter;
++ bool disableUpdate;
++ int commitCount;
++ ComposingText::TextLayer targetLayer;
++ QList<QSharedPointer<WnnWord> > candidateList;
++ int activeWordIndex;
++ };
++
++ /*!
++ \class QtVirtualKeyboard::OpenWnnInputMethod
++ \internal
++ */
++
++ OpenWnnInputMethod::OpenWnnInputMethod(QObject *parent) :
++ QVirtualKeyboardAbstractInputMethod(parent),
++ d_ptr(new OpenWnnInputMethodPrivate(this))
++ {
++ }
++
++ OpenWnnInputMethod::~OpenWnnInputMethod()
++ {
++ }
++
++ QList<QVirtualKeyboardInputEngine::InputMode> OpenWnnInputMethod::inputModes(const QString &locale)
++ {
++ Q_UNUSED(locale);
++ return QList<QVirtualKeyboardInputEngine::InputMode>()
++ << QVirtualKeyboardInputEngine::InputMode::Hiragana
++ << QVirtualKeyboardInputEngine::InputMode::Katakana
++ << QVirtualKeyboardInputEngine::InputMode::FullwidthLatin
++ << QVirtualKeyboardInputEngine::InputMode::Latin
++ << QVirtualKeyboardInputEngine::InputMode::HiraganaFlick;
++ }
++
++ bool OpenWnnInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
++ {
++ Q_UNUSED(locale);
++ Q_D(OpenWnnInputMethod);
++ if (d->inputMode == inputMode)
++ return true;
++ update();
++ switch (inputMode) {
++ case QVirtualKeyboardInputEngine::InputMode::Hiragana:
++ d->changeEngineMode(OpenWnnInputMethodPrivate::ENGINE_MODE_DEFAULT);
++ break;
++
++ case QVirtualKeyboardInputEngine::InputMode::HiraganaFlick:
++ d->changeEngineMode(OpenWnnInputMethodPrivate::ENGINE_MODE_DEFAULT);
++ d->preConverter.reset();
++ break;
++
++ case QVirtualKeyboardInputEngine::InputMode::Katakana:
++ d->changeEngineMode(OpenWnnInputMethodPrivate::ENGINE_MODE_FULL_KATAKANA);
++ break;
++
++ default:
++ d->changeEngineMode(OpenWnnInputMethodPrivate::ENGINE_MODE_DIRECT);
++ break;
++ }
++ d->inputMode = inputMode;
++ d->fitInputType();
++ return true;
++ }
++
++ bool OpenWnnInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase)
++ {
++ Q_UNUSED(textCase);
++ return true;
++ }
++
++ bool OpenWnnInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
++ {
++ Q_UNUSED(key);
++ Q_UNUSED(text);
++ Q_UNUSED(modifiers);
++ Q_D(OpenWnnInputMethod);
++
++ if (d->preConverter == nullptr && !d->isEnableL2Converter())
++ return false;
++
++ switch (key) {
++ case Qt::Key_Left:
++ if (d->isEnableL2Converter() && d->composingText.size(ComposingText::LAYER1) > 0)
++ return d->processLeftKeyEvent();
++ else
++ return d->commitText(false);
++ break;
++
++ case Qt::Key_Right:
++ if (d->isEnableL2Converter() && d->composingText.size(ComposingText::LAYER1) > 0)
++ return d->processRightKeyEvent();
++ else
++ return d->commitText(false);
++ break;
++
++ case Qt::Key_Backspace:
++ if (d->composingText.size(ComposingText::LAYER1) > 0) {
++ if (d->activeConvertType == OpenWnnInputMethodPrivate::CONVERT_TYPE_RENBUN) {
++ d->composingText.setCursor(ComposingText::LAYER1,
++ d->composingText.toString(ComposingText::LAYER1).size());
++ d->exactMatchMode = false;
++ d->clearFocusCandidate();
++ } else {
++ if ((d->composingText.size(ComposingText::LAYER1) == 1) &&
++ d->composingText.getCursor(ComposingText::LAYER1) != 0) {
++ d->initializeScreen();
++ return true;
++ } else {
++ d->composingText.deleteAt(ComposingText::LAYER1, false);
++ }
++ }
++ if (lcOpenWnn().isDebugEnabled())
++ d->composingText.debugout();
++ d->updateViewStatusForPrediction(true, true);
++ return true;
++ }
++ break;
++
++ case Qt::Key_Space:
++ if (d->composingText.size(ComposingText::LAYER0) == 0) {
++ d->clearCandidates();
++ d->breakSequence();
++ } else {
++ if (d->targetLayer == ComposingText::LAYER2)
++ d->changeL2Segment(d->focusNextCandidate());
++ else if (d->isEnableL2Converter())
++ d->startConvert(OpenWnnInputMethodPrivate::CONVERT_TYPE_RENBUN);
++ else
++ return d->commitText(false);
++ return true;
++ }
++ break;
++
++ case Qt::Key_Return:
++ case Qt::Key_Enter:
++ if (d->composingText.size(ComposingText::LAYER0) > 0) {
++ d->commitText(true);
++ return true;
++ }
++ break;
++
++ default:
++ if (key < Qt::Key_Escape && !text.isEmpty() && text.at(0).isPrint()) {
++ const QString normalizedText = text.normalized(QString::NormalizationForm_C);
++ if (d->composingText.size(ComposingText::LAYER1) + normalizedText.size() > OpenWnnInputMethodPrivate::MAX_COMPOSING_TEXT)
++ return true;
++ const int last = normalizedText.size() - 1;
++ for (int i = 0; i <= last; ++i) {
++ QChar ch = normalizedText.at(i);
++ // shrink big yo/yu/ya after yoon bases
++ if (ch.unicode() == 0x3088 || ch.unicode() == 0x3086 || ch.unicode() == 0x3084) {
++ const QString prev = d->composingText.getStrSegment(ComposingText::LAYER1, -1).string;
++ if (!prev.isEmpty()) {
++ const QChar p = prev.back();
++ static const QString yoonBases = QStringLiteral("\u304D\u3057\u3061\u306B\u3072\u307F\u308A\u304E\u3058\u3062\u3073\u3074");
++ if (yoonBases.contains(p))
++ ch = QChar::fromUcs2(ch.unicode() - 1);
++ }
++ } else if (ch.unicode() == 0x3099 || ch.unicode() == 0x309A) {
++ // if dakuten/handakuten, compose with previous kana
++ QString prev = d->composingText.getStrSegment(ComposingText::LAYER1, -1).string;
++ if (!prev.isEmpty()) {
++ QString composed = QString(prev + ch).normalized(QString::NormalizationForm_C);
++ if (composed.size() == 1) {
++ // replace previous char with composed kana
++ d->composingText.deleteAt(ComposingText::LAYER1, false);
++ d->composingText.insertStrSegment(ComposingText::LAYER0,
++ ComposingText::LAYER1, composed);
++ if (i == last)
++ d->updateViewStatusForPrediction(true, true);
++ continue; // handled this input char
++ }
++ }
++ // fall through if composing failed
++ }
++ if (d->isEnableL2Converter()) {
++ d->commitConvertingText();
++ d->composingText.insertStrSegment(ComposingText::LAYER0, ComposingText::LAYER1, QString(ch));
++ if (d->preConverter != nullptr && ch.unicode() <= 0x007F)
++ d->preConverter->convert(d->composingText);
++ if (i == last)
++ d->updateViewStatusForPrediction(true, true);
++ } else {
++ d->composingText.insertStrSegment(ComposingText::LAYER0, ComposingText::LAYER1, QString(ch));
++ QString layer1 = d->composingText.toString(ComposingText::LAYER1);
++ if (!d->isAlphabetLast(layer1)) {
++ d->commitText(false);
++ } else {
++ bool completed = d->preConverter->convert(d->composingText);
++ if (completed) {
++ d->commitTextWithoutLastAlphabet();
++ } else {
++ if (i == last)
++ d->updateViewStatusForPrediction(true, true);
++ }
++ }
++ }
++ }
++ if (lcOpenWnn().isDebugEnabled())
++ d->composingText.debugout();
++ return true;
++ }
++ break;
++ }
++
++ return false;
++ }
++
++ QList<QVirtualKeyboardSelectionListModel::Type> OpenWnnInputMethod::selectionLists()
++ {
++ Q_D(OpenWnnInputMethod);
++ if (!d->enablePrediction)
++ return QList<QVirtualKeyboardSelectionListModel::Type>();
++ return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList;
++ }
++
++ int OpenWnnInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type)
++ {
++ Q_UNUSED(type);
++ Q_D(OpenWnnInputMethod);
++ return d->candidateList.size();
++ }
++
++ QVariant OpenWnnInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role)
++ {
++ QVariant result;
++ Q_D(OpenWnnInputMethod);
++ switch (role) {
++ case QVirtualKeyboardSelectionListModel::Role::Display:
++ result = QVariant(d->candidateList.at(index)->candidate);
++ break;
++ case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength:
++ result.setValue(0);
++ break;
++ default:
++ result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role);
++ break;
++ }
++ return result;
++ }
++
++ void OpenWnnInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index)
++ {
++ Q_UNUSED(type);
++ Q_D(OpenWnnInputMethod);
++ d->activeWordIndex = index;
++ // Set selected text as preeditText to place cursor at the end of selected text
++ inputContext()->setPreeditText(d->candidateList.at(index)->candidate);
++ d->commitText(*d->candidateList.at(index));
++ }
++
++ void OpenWnnInputMethod::reset()
++ {
++ Q_D(OpenWnnInputMethod);
++ d->composingText.clear();
++ d->initializeScreen();
++ d->fitInputType();
++ }
++
++ void OpenWnnInputMethod::update()
++ {
++ Q_D(OpenWnnInputMethod);
++ if (!d->disableUpdate) {
++ d->commitAll();
++ reset();
++ }
++ }
++
++ } // namespace QtVirtualKeyboard
++ QT_END_NAMESPACE