aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/pinyin/pinyininputmethod.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/pinyin/pinyininputmethod.cpp')
-rw-r--r--src/plugins/pinyin/pinyininputmethod.cpp477
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