diff options
Diffstat (limited to 'src/plugins/pinyin/pinyininputmethod.cpp')
-rw-r--r-- | src/plugins/pinyin/pinyininputmethod.cpp | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/src/plugins/pinyin/pinyininputmethod.cpp b/src/plugins/pinyin/pinyininputmethod.cpp new file mode 100644 index 00000000..04753dd7 --- /dev/null +++ b/src/plugins/pinyin/pinyininputmethod.cpp @@ -0,0 +1,477 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "pinyininputmethod_p.h" +#include "pinyindecoderservice_p.h" +#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h> + +#include <QLoggingCategory> +#include <QtCore/qpointer.h> + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +Q_LOGGING_CATEGORY(lcPinyin, "qt.virtualkeyboard.pinyin") + +class PinyinInputMethodPrivate +{ + Q_DECLARE_PUBLIC(PinyinInputMethod) + +public: + enum State + { + Idle, + Input, + Predict + }; + + PinyinInputMethodPrivate(PinyinInputMethod *q_ptr) : + q_ptr(q_ptr), + inputMode(QVirtualKeyboardInputEngine::InputMode::Pinyin), + pinyinDecoderService(PinyinDecoderService::getInstance()), + state(Idle), + surface(), + totalChoicesNum(0), + candidatesList(), + fixedLen(0), + composingStr(), + activeCmpsLen(0), + finishSelection(true), + posDelSpl(-1), + isPosInSpl(false) + { + } + + void resetToIdleState() + { + Q_Q(PinyinInputMethod); + + QVirtualKeyboardInputContext *inputContext = q->inputContext(); + + // Disable the user dictionary when entering sensitive data + if (inputContext && pinyinDecoderService) { + bool userDictionaryEnabled = !inputContext->inputMethodHints().testFlag(Qt::ImhSensitiveData); + if (userDictionaryEnabled != pinyinDecoderService->isUserDictionaryEnabled()) + pinyinDecoderService->setUserDictionary(userDictionaryEnabled); + } + + if (state == Idle) + return; + + state = Idle; + surface.clear(); + fixedLen = 0; + finishSelection = true; + composingStr.clear(); + if (inputContext) + inputContext->setPreeditText(QString()); + activeCmpsLen = 0; + posDelSpl = -1; + isPosInSpl = false; + + resetCandidates(); + } + + bool addSpellingChar(QChar ch, bool reset) + { + if (reset) { + surface.clear(); + pinyinDecoderService->resetSearch(); + } + if (ch == u'\'') { + if (surface.isEmpty()) + return false; + if (surface.endsWith(ch)) + return true; + } + surface.append(ch); + return true; + } + + bool removeSpellingChar() + { + if (surface.isEmpty()) + return false; + QList<int> splStart = pinyinDecoderService->spellingStartPositions(); + isPosInSpl = (surface.size() <= splStart[fixedLen + 1]); + posDelSpl = isPosInSpl ? fixedLen - 1 : surface.size() - 1; + return true; + } + + void chooseAndUpdate(int candId) + { + Q_Q(PinyinInputMethod); + + if (state == Predict) + choosePredictChoice(candId); + else + chooseDecodingCandidate(candId); + + if (composingStr.size() > 0) { + if ((candId >= 0 || finishSelection) && composingStr.size() == fixedLen) { + QString resultStr = getComposingStrActivePart(); + q->inputContext()->commit(resultStr); + tryPredict(); + } else if (state == Idle) { + state = Input; + } + } else { + tryPredict(); + } + } + + bool chooseAndFinish() + { + if (state == Predict || !totalChoicesNum) + return false; + + chooseAndUpdate(0); + if (state != Predict && totalChoicesNum > 0) + chooseAndUpdate(0); + + return true; + } + + int candidatesCount() + { + return totalChoicesNum; + } + + QString candidateAt(int index) + { + if (index < 0 || index >= totalChoicesNum) + return QString(); + if (index >= candidatesList.size()) { + int fetchMore = qMin(index + 20, totalChoicesNum - candidatesList.size()); + candidatesList.append(pinyinDecoderService->fetchCandidates(candidatesList.size(), fetchMore, fixedLen)); + if (index == 0 && totalChoicesNum == 1) { + int surfaceDecodedLen = pinyinDecoderService->pinyinStringLength(true); + if (surfaceDecodedLen < surface.size()) + candidatesList[0] = candidatesList[0] + surface.mid(surfaceDecodedLen).toLower(); + } + } + return index < candidatesList.size() ? candidatesList[index] : QString(); + } + + void chooseDecodingCandidate(int candId) + { + Q_Q(PinyinInputMethod); + Q_ASSERT(state != Predict); + + int result = 0; + if (candId < 0) { + if (surface.size() > 0) { + if (posDelSpl < 0) { + result = pinyinDecoderService->search(surface); + } else { + result = pinyinDecoderService->deleteSearch(posDelSpl, isPosInSpl, false); + posDelSpl = -1; + } + } + } else { + if (totalChoicesNum > 1) { + result = pinyinDecoderService->chooceCandidate(candId); + } else { + QString resultStr; + if (totalChoicesNum == 1) { + QString undecodedStr = candId < candidatesList.size() ? candidatesList.at(candId) : QString(); + resultStr = pinyinDecoderService->candidateAt(0).mid(0, fixedLen) + undecodedStr; + } + resetToIdleState(); + if (!resultStr.isEmpty()) + q->inputContext()->commit(resultStr); + return; + } + } + + resetCandidates(); + totalChoicesNum = result; + + surface = pinyinDecoderService->pinyinString(false); + QList<int> splStart = pinyinDecoderService->spellingStartPositions(); + QString fullSent = pinyinDecoderService->candidateAt(0); + fixedLen = pinyinDecoderService->fixedLength(); + composingStr = fullSent.mid(0, fixedLen) + surface.mid(splStart[fixedLen + 1]); + activeCmpsLen = composingStr.size(); + + // Prepare the display string. + QString composingStrDisplay; + int surfaceDecodedLen = pinyinDecoderService->pinyinStringLength(true); + if (!surfaceDecodedLen) { + composingStrDisplay = composingStr.toLower(); + if (!totalChoicesNum) + totalChoicesNum = 1; + } else { + activeCmpsLen = activeCmpsLen - (surface.size() - surfaceDecodedLen); + composingStrDisplay = fullSent.mid(0, fixedLen); + for (int pos = fixedLen + 1; pos < splStart.size() - 1; pos++) { + composingStrDisplay += surface.mid(splStart[pos], splStart[pos + 1] - splStart[pos]); + if (splStart[pos + 1] < surfaceDecodedLen) + composingStrDisplay += QLatin1String(" "); + } + if (surfaceDecodedLen < surface.size()) + composingStrDisplay += surface.mid(surfaceDecodedLen); + } + q->inputContext()->setPreeditText(composingStrDisplay); + + finishSelection = splStart.size() == (fixedLen + 2); + if (!finishSelection) + candidateAt(0); + } + + void choosePredictChoice(int choiceId) + { + Q_ASSERT(state == Predict); + + if (choiceId < 0 || choiceId >= totalChoicesNum) + return; + + QString tmp = candidatesList.at(choiceId); + + resetCandidates(); + + candidatesList.append(tmp); + totalChoicesNum = 1; + + surface.clear(); + fixedLen = tmp.size(); + composingStr = tmp; + activeCmpsLen = fixedLen; + + finishSelection = true; + } + + QString getComposingStrActivePart() + { + return composingStr.mid(0, activeCmpsLen); + } + + void resetCandidates() + { + candidatesList.clear(); + if (totalChoicesNum) { + totalChoicesNum = 0; + } + } + + void updateCandidateList() + { + Q_Q(PinyinInputMethod); + emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); + emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, + totalChoicesNum > 0 && state == PinyinInputMethodPrivate::Input ? 0 : -1); + } + + bool canDoPrediction() + { + Q_Q(PinyinInputMethod); + QVirtualKeyboardInputContext *inputContext = q->inputContext(); + return inputMode == QVirtualKeyboardInputEngine::InputMode::Pinyin && + composingStr.size() == fixedLen && + inputContext && + !inputContext->inputMethodHints().testFlag(Qt::ImhNoPredictiveText); + } + + void tryPredict() + { + // Try to get the prediction list. + if (canDoPrediction()) { + Q_Q(PinyinInputMethod); + if (state != Predict) + resetToIdleState(); + QVirtualKeyboardInputContext *inputContext = q->inputContext(); + int cursorPosition = inputContext->cursorPosition(); + int historyStart = qMax(0, cursorPosition - 3); + QString history = inputContext->surroundingText().mid(historyStart, cursorPosition - historyStart); + candidatesList = pinyinDecoderService->predictionList(history); + totalChoicesNum = candidatesList.size(); + finishSelection = false; + state = Predict; + } else { + resetCandidates(); + } + + if (!candidatesCount()) + resetToIdleState(); + } + + PinyinInputMethod *q_ptr; + QVirtualKeyboardInputEngine::InputMode inputMode; + QPointer<PinyinDecoderService> pinyinDecoderService; + State state; + QString surface; + int totalChoicesNum; + QList<QString> candidatesList; + int fixedLen; + QString composingStr; + int activeCmpsLen; + bool finishSelection; + int posDelSpl; + bool isPosInSpl; +}; + +class ScopedCandidateListUpdate +{ + Q_DISABLE_COPY(ScopedCandidateListUpdate) +public: + inline explicit ScopedCandidateListUpdate(PinyinInputMethodPrivate *d) : + d(d), + candidatesList(d->candidatesList), + totalChoicesNum(d->totalChoicesNum), + state(d->state) + { + } + + inline ~ScopedCandidateListUpdate() + { + if (totalChoicesNum != d->totalChoicesNum || state != d->state || candidatesList != d->candidatesList) + d->updateCandidateList(); + } + +private: + PinyinInputMethodPrivate *d; + QList<QString> candidatesList; + int totalChoicesNum; + PinyinInputMethodPrivate::State state; +}; + +/*! + \class QtVirtualKeyboard::PinyinInputMethod + \internal +*/ + +PinyinInputMethod::PinyinInputMethod(QObject *parent) : + QVirtualKeyboardAbstractInputMethod(parent), + d_ptr(new PinyinInputMethodPrivate(this)) +{ +} + +PinyinInputMethod::~PinyinInputMethod() +{ +} + +QList<QVirtualKeyboardInputEngine::InputMode> PinyinInputMethod::inputModes(const QString &locale) +{ + Q_UNUSED(locale); + Q_D(PinyinInputMethod); + QList<QVirtualKeyboardInputEngine::InputMode> result; + if (d->pinyinDecoderService) + result << QVirtualKeyboardInputEngine::InputMode::Pinyin; + result << QVirtualKeyboardInputEngine::InputMode::Latin; + return result; +} + +bool PinyinInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) +{ + Q_UNUSED(locale); + Q_D(PinyinInputMethod); + reset(); + if (inputMode == QVirtualKeyboardInputEngine::InputMode::Pinyin && !d->pinyinDecoderService) + return false; + d->inputMode = inputMode; + return true; +} + +bool PinyinInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase) +{ + Q_UNUSED(textCase); + return true; +} + +bool PinyinInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) +{ + Q_UNUSED(modifiers); + Q_D(PinyinInputMethod); + if (d->inputMode == QVirtualKeyboardInputEngine::InputMode::Pinyin) { + ScopedCandidateListUpdate scopedCandidateListUpdate(d); + Q_UNUSED(scopedCandidateListUpdate); + if ((key >= Qt::Key_A && key <= Qt::Key_Z) || (key == Qt::Key_Apostrophe)) { + if (d->state == PinyinInputMethodPrivate::Predict) + d->resetToIdleState(); + if (d->addSpellingChar(text.at(0), d->state == PinyinInputMethodPrivate::Idle)) { + d->chooseAndUpdate(-1); + return true; + } + } else if (key == Qt::Key_Space) { + if (d->state != PinyinInputMethodPrivate::Predict && d->candidatesCount() > 0) { + d->chooseAndUpdate(0); + return true; + } + } else if (key == Qt::Key_Return) { + if (d->state != PinyinInputMethodPrivate::Predict && d->candidatesCount() > 0) { + QString surface = d->surface; + d->resetToIdleState(); + inputContext()->commit(surface); + return true; + } + } else if (key == Qt::Key_Backspace) { + if (d->removeSpellingChar()) { + d->chooseAndUpdate(-1); + return true; + } + } else if (!text.isEmpty()) { + d->chooseAndFinish(); + } + } + return false; +} + +QList<QVirtualKeyboardSelectionListModel::Type> PinyinInputMethod::selectionLists() +{ + return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList; +} + +int PinyinInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type) +{ + Q_UNUSED(type); + Q_D(PinyinInputMethod); + return d->candidatesCount(); +} + +QVariant PinyinInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role) +{ + QVariant result; + Q_UNUSED(type); + Q_D(PinyinInputMethod); + switch (role) { + case QVirtualKeyboardSelectionListModel::Role::Display: + result = QVariant(d->candidateAt(index)); + break; + case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength: + result.setValue(0); + break; + default: + result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role); + break; + } + return result; +} + +void PinyinInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index) +{ + Q_UNUSED(type); + Q_D(PinyinInputMethod); + ScopedCandidateListUpdate scopedCandidateListUpdate(d); + Q_UNUSED(scopedCandidateListUpdate); + d->chooseAndUpdate(index); +} + +void PinyinInputMethod::reset() +{ + Q_D(PinyinInputMethod); + ScopedCandidateListUpdate scopedCandidateListUpdate(d); + Q_UNUSED(scopedCandidateListUpdate); + d->resetToIdleState(); +} + +void PinyinInputMethod::update() +{ + Q_D(PinyinInputMethod); + ScopedCandidateListUpdate scopedCandidateListUpdate(d); + Q_UNUSED(scopedCandidateListUpdate); + d->chooseAndFinish(); + d->tryPredict(); +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE |