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 "tcinputmethod.h"
++ #include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
++ #include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
++ #if QT_CONFIG(cangjie)
++ #include "cangjiedictionary.h"
++ #include "cangjietable.h"
++ #endif
++ #if QT_CONFIG(zhuyin)
++ #include "zhuyindictionary.h"
++ #include "zhuyintable.h"
++ #endif
++ #include "phrasedictionary.h"
++ #include <QLoggingCategory>
++
++ #include <QLibraryInfo>
++ #include <QFileInfo>
++
++ #include <array>
++
++ QT_BEGIN_NAMESPACE
++ namespace QtVirtualKeyboard {
++
++ Q_LOGGING_CATEGORY(lcTCIme, "qt.virtualkeyboard.tcime")
++
++ using namespace tcime;
++
++ class TCInputMethodPrivate
++ {
++ Q_DECLARE_PUBLIC(TCInputMethod)
++ public:
++
++ TCInputMethodPrivate(TCInputMethod *q_ptr) :
++ q_ptr(q_ptr),
++ inputMode(QVirtualKeyboardInputEngine::InputMode::Latin),
++ wordDictionary(nullptr),
++ highlightIndex(-1)
++ {}
++
++ bool setCandidates(const QStringList &values, bool highlightDefault)
++ {
++ bool candidatesChanged = candidates != values;
++ candidates = values;
++ highlightIndex = !candidates.isEmpty() && highlightDefault ? 0 : -1;
++ return candidatesChanged;
++ }
++
++ bool clearCandidates()
++ {
++ if (candidates.isEmpty())
++ return false;
++
++ candidates.clear();
++ highlightIndex = -1;
++ return true;
++ }
++
++ QString pickHighlighted() const
++ {
++ return (highlightIndex >= 0 && highlightIndex < candidates.size()) ? candidates[highlightIndex] : QString();
++ }
++
++ void reset()
++ {
++ if (clearCandidates()) {
++ Q_Q(TCInputMethod);
++ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, highlightIndex);
++ }
++ input.clear();
++ }
++
++ bool compose(const QChar &c)
++ {
++ bool accept;
++ Q_Q(TCInputMethod);
++ QVirtualKeyboardInputContext *ic = q->inputContext();
++ switch (inputMode)
++ {
++ #if QT_CONFIG(cangjie)
++ case QVirtualKeyboardInputEngine::InputMode::Cangjie:
++ accept = composeCangjie(ic, c);
++ break;
++ #endif
++ #if QT_CONFIG(zhuyin)
++ case QVirtualKeyboardInputEngine::InputMode::Zhuyin:
++ accept = composeZhuyin(ic, c);
++ break;
++ #endif
++ default:
++ accept = false;
++ break;
++ }
++ return accept;
++ }
++
++ #if QT_CONFIG(cangjie)
++ bool composeCangjie(QVirtualKeyboardInputContext *ic, const QChar &c)
++ {
++ bool accept = false;
++ if (!input.contains(QChar(0x91CD)) && CangjieTable::isLetter(c)) {
++ if (input.size() < (cangjieDictionary.simplified() ? CangjieTable::MAX_SIMPLIFIED_CODE_LENGTH : CangjieTable::MAX_CODE_LENGTH)) {
++ input.append(c);
++ ic->setPreeditText(input);
++ if (setCandidates(wordDictionary->getWords(input), true)) {
++ Q_Q(TCInputMethod);
++ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, highlightIndex);
++ }
++ }
++ accept = true;
++ } else if (c.unicode() == 0x91CD) {
++ if (input.isEmpty()) {
++ input.append(c);
++ ic->setPreeditText(input);
++ checkSpecialCharInput();
++ }
++ accept = true;
++ } else if (c.unicode() == 0x96E3) {
++ if (input.size() == 1) {
++ Q_ASSERT(input.at(0).unicode() == 0x91CD);
++ input.append(c);
++ ic->setPreeditText(input);
++ checkSpecialCharInput();
++ }
++ accept = true;
++ }
++ return accept;
++ }
++
++ bool checkSpecialCharInput()
++ {
++ if (input.size() == 1 && input.at(0).unicode() == 0x91CD) {
++ static const QStringList specialChars1 = QStringList()
++ << QChar(0xFF01) << QChar(0x2018) << QChar(0x3000) << QChar(0xFF0C)
++ << QChar(0x3001) << QChar(0x3002) << QChar(0xFF0E) << QChar(0xFF1B)
++ << QChar(0xFF1A) << QChar(0xFF1F) << QChar(0x300E) << QChar(0x300F)
++ << QChar(0x3010) << QChar(0x3011) << QChar(0xFE57) << QChar(0x2026)
++ << QChar(0x2025) << QChar(0xFE50) << QChar(0xFE51) << QChar(0xFE52)
++ << QChar(0x00B7) << QChar(0xFE54) << QChar(0x2574) << QChar(0x2027)
++ << QChar(0x2032) << QChar(0x2035) << QChar(0x301E) << QChar(0x301D)
++ << QChar(0x201D) << QChar(0x201C) << QChar(0x2019) << QChar(0xFE55)
++ << QChar(0xFE5D) << QChar(0xFE5E) << QChar(0xFE59) << QChar(0xFE5A)
++ << QChar(0xFE5B) << QChar(0xFE5C) << QChar(0xFE43) << QChar(0xFE44);
++ Q_Q(TCInputMethod);
++ if (setCandidates(specialChars1, true)) {
++ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, highlightIndex);
++ }
++ q->inputContext()->setPreeditText(candidates[highlightIndex]);
++ return true;
++ } else if (input.size() == 2 && input.at(0).unicode() == 0x91CD && input.at(1).unicode() == 0x96E3) {
++ static const QStringList specialChars2 = QStringList()
++ << QChar(0x3008) << QChar(0x3009) << QChar(0xFE31) << QChar(0x2013)
++ << QChar(0xFF5C) << QChar(0x300C) << QChar(0x300D) << QChar(0xFE40)
++ << QChar(0xFE3F) << QChar(0x2014) << QChar(0xFE3E) << QChar(0xFE3D)
++ << QChar(0x300A) << QChar(0x300B) << QChar(0xFE3B) << QChar(0xFE3C)
++ << QChar(0xFE56) << QChar(0xFE30) << QChar(0xFE39) << QChar(0xFE3A)
++ << QChar(0x3014) << QChar(0x3015) << QChar(0xFE37) << QChar(0xFE38)
++ << QChar(0xFE41) << QChar(0xFE42) << QChar(0xFF5B) << QChar(0xFF5D)
++ << QChar(0xFE35) << QChar(0xFE36) << QChar(0xFF08) << QChar(0xFF09)
++ << QChar(0xFE4F) << QChar(0xFE34) << QChar(0xFE33);
++ Q_Q(TCInputMethod);
++ if (setCandidates(specialChars2, true)) {
++ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, highlightIndex);
++ }
++ q->inputContext()->setPreeditText(candidates[highlightIndex]);
++ return true;
++ }
++ return false;
++ }
++ #endif
++
++ #if QT_CONFIG(zhuyin)
++ bool composeZhuyin(QVirtualKeyboardInputContext *ic, const QChar &c)
++ {
++ if (ZhuyinTable::isTone(c)) {
++ if (input.isEmpty())
++ // Tones are accepted only when there's text in composing.
++ return false;
++
++ auto strippedTones = ZhuyinTable::stripTones(input);
++ if (!strippedTones.ok)
++ // Tones cannot be composed if there's no syllables.
++ return false;
++
++ // Replace the original tone with the new tone, but the default tone
++ // character should not be composed into the composing text.
++ QChar tone = strippedTones.pair[1].at(0);
++ if (c == ZhuyinTable::DEFAULT_TONE) {
++ if (tone != ZhuyinTable::DEFAULT_TONE)
++ input.remove(input.size() - 1, 1);
++ } else {
++ if (tone == ZhuyinTable::DEFAULT_TONE)
++ input.append(c);
++ else
++ input.replace(input.size() - 1, 1, c);
++ }
++ } else if (ZhuyinTable::getInitials(c) > 0) {
++ // Insert the initial or replace the original initial.
++ if (input.isEmpty() || !ZhuyinTable::getInitials(input.at(0)))
++ input.insert(0, c);
++ else
++ input.replace(0, 1, c);
++ } else if (ZhuyinTable::getFinals(QStringView(&c, 1)) > 0) {
++ // Replace the finals in the decomposed of syllables and tones.
++ std::array<QChar, 4> decomposed = decomposeZhuyin();
++ if (ZhuyinTable::isYiWuYuFinals(c)) {
++ decomposed[1] = c;
++ } else {
++ decomposed[2] = c;
++ }
++
++ // Compose back the text after the finals replacement.
++ input.clear();
++ for (QChar ch : decomposed) {
++ if (!ch.isNull())
++ input.append(ch);
++ }
++ } else {
++ return false;
++ }
++
++ ic->setPreeditText(input);
++ if (setCandidates(wordDictionary->getWords(input), true)) {
++ Q_Q(TCInputMethod);
++ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, highlightIndex);
++ }
++
++ return true;
++ }
++
++ std::array<QChar, 4> decomposeZhuyin()
++ {
++ std::array<QChar, 4> results = {};
++ auto strippedTones = ZhuyinTable::stripTones(input);
++ if (strippedTones.ok) {
++ // Decompose tones.
++ QChar tone = strippedTones.pair[1].at(0);
++ if (tone != ZhuyinTable::DEFAULT_TONE)
++ results[3] = tone;
++
++ // Decompose initials.
++ QStringView syllables = strippedTones.pair[0];
++ if (ZhuyinTable::getInitials(syllables.at(0)) > 0) {
++ results[0] = syllables.at(0);
++ syllables = syllables.mid(1);
++ }
++
++ // Decompose finals.
++ if (!syllables.isEmpty()) {
++ if (ZhuyinTable::isYiWuYuFinals(syllables.at(0))) {
++ results[1] = syllables.at(0);
++ if (syllables.size() > 1)
++ results[2] = syllables.at(1);
++ } else {
++ results[2] = syllables.at(0);
++ }
++ }
++ }
++ return results;
++ }
++ #endif
++
++ TCInputMethod *q_ptr;
++ QVirtualKeyboardInputEngine::InputMode inputMode;
++ #if QT_CONFIG(cangjie)
++ CangjieDictionary cangjieDictionary;
++ #endif
++ #if QT_CONFIG(zhuyin)
++ ZhuyinDictionary zhuyinDictionary;
++ #endif
++ PhraseDictionary phraseDictionary;
++ WordDictionary *wordDictionary;
++ QString input;
++ QStringList candidates;
++ int highlightIndex;
++ };
++
++ /*!
++ \class QtVirtualKeyboard::TCInputMethod
++ \internal
++ */
++
++ TCInputMethod::TCInputMethod(QObject *parent) :
++ QVirtualKeyboardAbstractInputMethod(parent),
++ d_ptr(new TCInputMethodPrivate(this))
++ {
++ }
++
++ TCInputMethod::~TCInputMethod()
++ {
++ }
++
++ bool TCInputMethod::simplified() const
++ {
++ #if QT_CONFIG(cangjie)
++ Q_D(const TCInputMethod);
++ return d->cangjieDictionary.simplified();
++ #else
++ return false;
++ #endif
++ }
++
++ void TCInputMethod::setSimplified(bool simplified)
++ {
++ qCDebug(lcTCIme) << "TCInputMethod::setSimplified(): " << simplified;
++ #if QT_CONFIG(cangjie)
++ Q_D(TCInputMethod);
++ if (d->cangjieDictionary.simplified() != simplified) {
++ d->reset();
++ QVirtualKeyboardInputContext *ic = inputContext();
++ if (ic)
++ ic->clear();
++ d->cangjieDictionary.setSimplified(simplified);
++ emit simplifiedChanged();
++ }
++ #else
++ Q_UNUSED(simplified);
++ #endif
++ }
++
++ QList<QVirtualKeyboardInputEngine::InputMode> TCInputMethod::inputModes(const QString &locale)
++ {
++ Q_UNUSED(locale);
++ return QList<QVirtualKeyboardInputEngine::InputMode>()
++ #if QT_CONFIG(zhuyin)
++ << QVirtualKeyboardInputEngine::InputMode::Zhuyin
++ #endif
++ #if QT_CONFIG(cangjie)
++ << QVirtualKeyboardInputEngine::InputMode::Cangjie
++ #endif
++ ;
++ }
++
++ bool TCInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
++ {
++ Q_UNUSED(locale);
++ Q_D(TCInputMethod);
++ if (d->inputMode == inputMode)
++ return true;
++ update();
++ bool result = false;
++ d->inputMode = inputMode;
++ d->wordDictionary = nullptr;
++ #if QT_CONFIG(cangjie)
++ if (inputMode == QVirtualKeyboardInputEngine::InputMode::Cangjie) {
++ if (d->cangjieDictionary.isEmpty()) {
++ QString cangjieDictionary(qEnvironmentVariable("QT_VIRTUALKEYBOARD_CANGJIE_DICTIONARY"));
++ if (!QFileInfo::exists(cangjieDictionary)) {
++ cangjieDictionary = QLibraryInfo::path(QLibraryInfo::DataPath) + QLatin1String("/qtvirtualkeyboard/tcime/dict_cangjie.dat");
++ if (!QFileInfo::exists(cangjieDictionary))
++ cangjieDictionary = QLatin1String(":/qt-project.org/imports/QtQuick/VirtualKeyboard/3rdparty/tcime/data/qt/dict_cangjie.dat");
++ }
++ d->cangjieDictionary.load(cangjieDictionary);
++ }
++ d->wordDictionary = &d->cangjieDictionary;
++ }
++ #endif
++ #if QT_CONFIG(zhuyin)
++ if (inputMode == QVirtualKeyboardInputEngine::InputMode::Zhuyin) {
++ if (d->zhuyinDictionary.isEmpty()) {
++ QString zhuyinDictionary(qEnvironmentVariable("QT_VIRTUALKEYBOARD_ZHUYIN_DICTIONARY"));
++ if (!QFileInfo::exists(zhuyinDictionary)) {
++ zhuyinDictionary = QLibraryInfo::path(QLibraryInfo::DataPath) + QLatin1String("/qtvirtualkeyboard/tcime/dict_zhuyin.dat");
++ if (!QFileInfo::exists(zhuyinDictionary))
++ zhuyinDictionary = QLatin1String(":/qt-project.org/imports/QtQuick/VirtualKeyboard/3rdparty/tcime/data/qt/dict_zhuyin.dat");
++ }
++ d->zhuyinDictionary.load(zhuyinDictionary);
++ }
++ d->wordDictionary = &d->zhuyinDictionary;
++ }
++ #endif
++ result = d->wordDictionary && !d->wordDictionary->isEmpty();
++ if (result && d->phraseDictionary.isEmpty()) {
++ QString phraseDictionary(qEnvironmentVariable("QT_VIRTUALKEYBOARD_PHRASE_DICTIONARY"));
++ if (!QFileInfo::exists(phraseDictionary)) {
++ phraseDictionary = QLibraryInfo::path(QLibraryInfo::DataPath) + QLatin1String("/qtvirtualkeyboard/tcime/dict_phrases.dat");
++ if (!QFileInfo::exists(phraseDictionary))
++ phraseDictionary = QLatin1String(":/qt-project.org/imports/QtQuick/VirtualKeyboard/3rdparty/tcime/data/qt/dict_phrases.dat");
++ }
++ d->phraseDictionary.load(phraseDictionary);
++ }
++ if (!result)
++ inputMode = QVirtualKeyboardInputEngine::InputMode::Latin;
++ return result;
++ }
++
++ bool TCInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase)
++ {
++ Q_UNUSED(textCase);
++ return true;
++ }
++
++ bool TCInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
++ {
++ Q_UNUSED(key);
++ Q_UNUSED(text);
++ Q_UNUSED(modifiers);
++ Q_D(TCInputMethod);
++ QVirtualKeyboardInputContext *ic = inputContext();
++ bool accept = false;
++ switch (key) {
++ case Qt::Key_Context1:
++ // Do nothing on symbol mode switch
++ accept = true;
++ break;
++
++ case Qt::Key_Enter:
++ case Qt::Key_Return:
++ update();
++ break;
++
++ case Qt::Key_Tab:
++ case Qt::Key_Space:
++ if (!d->input.isEmpty()) {
++ accept = true;
++ if (d->highlightIndex >= 0) {
++ QString finalWord = d->pickHighlighted();
++ d->reset();
++ inputContext()->commit(finalWord);
++ if (d->setCandidates(d->phraseDictionary.getWords(finalWord.left(1)), false)) {
++ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->highlightIndex);
++ }
++ }
++ } else {
++ update();
++ }
++ break;
++
++ case Qt::Key_Backspace:
++ if (!d->input.isEmpty()) {
++ d->input.remove(d->input.size() - 1, 1);
++ ic->setPreeditText(d->input);
++ #if QT_CONFIG(cangjie)
++ if (!d->checkSpecialCharInput()) {
++ #endif
++ if (d->setCandidates(d->wordDictionary->getWords(d->input), true)) {
++ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->highlightIndex);
++ }
++ #if QT_CONFIG(cangjie)
++ }
++ #endif
++ accept = true;
++ } else if (d->clearCandidates()) {
++ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->highlightIndex);
++ }
++ break;
++
++ default:
++ if (text.size() == 1)
++ accept = d->compose(text.at(0));
++ if (!accept)
++ update();
++ break;
++ }
++ return accept;
++ }
++
++ QList<QVirtualKeyboardSelectionListModel::Type> TCInputMethod::selectionLists()
++ {
++ return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList;
++ }
++
++ int TCInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type)
++ {
++ Q_UNUSED(type);
++ Q_D(TCInputMethod);
++ return d->candidates.size();
++ }
++
++ QVariant TCInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role)
++ {
++ QVariant result;
++ Q_D(TCInputMethod);
++ switch (role) {
++ case QVirtualKeyboardSelectionListModel::Role::Display:
++ result = QVariant(d->candidates.at(index));
++ break;
++ case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength:
++ result.setValue(0);
++ break;
++ default:
++ result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role);
++ break;
++ }
++ return result;
++ }
++
++ void TCInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index)
++ {
++ Q_UNUSED(type);
++ Q_D(TCInputMethod);
++ QString finalWord = d->candidates.at(index);
++ reset();
++ inputContext()->commit(finalWord);
++ if (d->setCandidates(d->phraseDictionary.getWords(finalWord.left(1)), false)) {
++ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
++ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->highlightIndex);
++ }
++ }
++
++ void TCInputMethod::reset()
++ {
++ Q_D(TCInputMethod);
++ d->reset();
++ }
++
++ void TCInputMethod::update()
++ {
++ Q_D(TCInputMethod);
++ if (d->highlightIndex >= 0) {
++ QString finalWord = d->pickHighlighted();
++ d->reset();
++ inputContext()->commit(finalWord);
++ } else {
++ inputContext()->clear();
++ d->reset();
++ }
++ }
++
++ } // namespace QtVirtualKeyboard
++ QT_END_NAMESPACE