blob: cdd7d218b0e3d4cd5771de0bc4ba01239f7acc34 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "pinyininputmethod_p.h"
#include "pinyindecoderservice_p.h"
#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
#include <QLoggingCategory>
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) {
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;
QVector<int> splStart = pinyinDecoderService->spellingStartPositions();
isPosInSpl = (surface.length() <= splStart[fixedLen + 1]);
posDelSpl = isPosInSpl ? fixedLen - 1 : surface.length() - 1;
return true;
}
void chooseAndUpdate(int candId)
{
Q_Q(PinyinInputMethod);
if (state == Predict)
choosePredictChoice(candId);
else
chooseDecodingCandidate(candId);
if (composingStr.length() > 0) {
if ((candId >= 0 || finishSelection) && composingStr.length() == fixedLen) {
QString resultStr = getComposingStrActivePart();
tryPredict();
q->inputContext()->commit(resultStr);
} 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.length())
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.length() > 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.length() ? 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);
QVector<int> splStart = pinyinDecoderService->spellingStartPositions();
QString fullSent = pinyinDecoderService->candidateAt(0);
fixedLen = pinyinDecoderService->fixedLength();
composingStr = fullSent.mid(0, fixedLen) + surface.mid(splStart[fixedLen + 1]);
activeCmpsLen = composingStr.length();
// Prepare the display string.
QString composingStrDisplay;
int surfaceDecodedLen = pinyinDecoderService->pinyinStringLength(true);
if (!surfaceDecodedLen) {
composingStrDisplay = composingStr.toLower();
if (!totalChoicesNum)
totalChoicesNum = 1;
} else {
activeCmpsLen = activeCmpsLen - (surface.length() - 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]).toUpper();
if (splStart[pos + 1] < surfaceDecodedLen)
composingStrDisplay += QLatin1String(" ");
}
if (surfaceDecodedLen < surface.length())
composingStrDisplay += surface.mid(surfaceDecodedLen).toLower();
}
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.length();
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.length() == 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