aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/cerence/hwr/plugin/t9writeinputmethod.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/cerence/hwr/plugin/t9writeinputmethod.cpp')
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writeinputmethod.cpp2824
1 files changed, 2824 insertions, 0 deletions
diff --git a/src/plugins/cerence/hwr/plugin/t9writeinputmethod.cpp b/src/plugins/cerence/hwr/plugin/t9writeinputmethod.cpp
new file mode 100644
index 00000000..bf0a0807
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writeinputmethod.cpp
@@ -0,0 +1,2824 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "t9writeinputmethod_p.h"
+#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
+#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
+#include <QtVirtualKeyboard/qvirtualkeyboardtrace.h>
+#include "t9writeworker_p.h"
+#include <QLoggingCategory>
+#include <QDirIterator>
+#include <QCryptographicHash>
+#include <QTime>
+#include <QMetaEnum>
+#include <QtCore/QLibraryInfo>
+#include <QtVirtualKeyboard/private/settings_p.h>
+#include <QtVirtualKeyboard/private/handwritinggesturerecognizer_p.h>
+#include <QtVirtualKeyboard/private/qvirtualkeyboardabstractinputmethod_p.h>
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+#include <QtVirtualKeyboard/private/unipentrace_p.h>
+#include <QStandardPaths>
+#endif
+#include <QtVirtualKeyboard/qvirtualkeyboarddictionary.h>
+#include <QtVirtualKeyboard/qvirtualkeyboarddictionarymanager.h>
+
+#include "decumaStatus.h"
+#include "decumaSymbolCategories.h"
+#include "decumaLanguages.h"
+#include "xxchOem.h"
+#include "xt9ldbmanager.h"
+#include "t9writewordcandidate_p.h"
+#ifdef HAVE_XT9
+#include <xt9awime.h>
+#include <xt9cpime.h>
+#include <xt9jime.h>
+#include <xt9kime.h>
+#include <xt9callbacks.h>
+#include <xt9languagemap.h>
+#endif
+
+/* Set to 1 to enable T9 Write log.
+
+ The log is routed to qDebug() and it can be enabled for troubleshooting
+ and when reporting issues. The log must not to be enabled in production
+ build.
+*/
+#define QT_VIRTUALKEYBOARD_CERENCE_HWR_LOG 0
+
+#define CERENCE_HWR_DLM_MAX_WORDS 10000
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Q_LOGGING_CATEGORY(lcT9Write, "qt.virtualkeyboard.cerence-hwr")
+
+class T9WriteCaseFormatter
+{
+public:
+ T9WriteCaseFormatter() :
+ preferLowercase(false)
+ {
+ }
+
+ void clear()
+ {
+ textCaseList.clear();
+ }
+
+ void ensureLength(int length, QVirtualKeyboardInputEngine::TextCase textCase)
+ {
+ if (length <= 0) {
+ textCaseList.clear();
+ return;
+ }
+ while (length < textCaseList.size())
+ textCaseList.removeLast();
+ while (length > textCaseList.size())
+ textCaseList.append(textCase);
+ }
+
+ QString formatString(const QString &str) const
+ {
+ QString result;
+ QVirtualKeyboardInputEngine::TextCase textCase = QVirtualKeyboardInputEngine::TextCase::Lower;
+ for (int i = 0; i < str.length(); ++i) {
+ if (i < textCaseList.size())
+ textCase = textCaseList.at(i);
+ result.append(textCase == QVirtualKeyboardInputEngine::TextCase::Upper ? str.at(i).toUpper() : (preferLowercase ? str.at(i).toLower() : str.at(i)));
+ }
+ return result;
+ }
+
+ bool preferLowercase;
+
+private:
+ QList<QVirtualKeyboardInputEngine::TextCase> textCaseList;
+};
+
+class T9WriteInputMethodPrivate : public QVirtualKeyboardAbstractInputMethodPrivate
+#ifdef HAVE_XT9
+ , public Xt9RequestCallback
+#endif
+{
+public:
+ Q_DECLARE_PUBLIC(T9WriteInputMethod)
+
+ T9WriteInputMethodPrivate(T9WriteInputMethod *q_ptr) :
+ QVirtualKeyboardAbstractInputMethodPrivate(),
+ q_ptr(q_ptr),
+ cjk(false),
+ engineMode(T9WriteInputMethod::EngineMode::Uninitialized),
+ traceListHardLimit(32),
+ attachedDictionary(nullptr),
+ ldbManager(new Xt9LdbManager()),
+ resultId(0),
+ lastResultId(0),
+ resultTimer(0),
+ decumaSession(nullptr),
+ activeWordIndex(-1),
+ arcAdditionStarted(false),
+ ignoreUpdate(false),
+ textCase(QVirtualKeyboardInputEngine::TextCase::Lower)
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ , unipenTrace()
+#endif
+ {
+#ifdef HAVE_CERENCE_HWR_RESOURCE
+ Q_INIT_RESOURCE(qmake_cerencecommondata_db);
+ Q_INIT_RESOURCE(qmake_cerencehwrdata_db);
+#endif
+ const QString pathListSep(
+#if defined(Q_OS_WIN32)
+ QStringLiteral(";")
+#else
+ QStringLiteral(":")
+#endif
+ );
+ const QString userHwrDbPath(qEnvironmentVariable("QT_VIRTUALKEYBOARD_CERENCE_HWR_DB_PATH"));
+ defaultHwrDbPaths = userHwrDbPath.split(pathListSep, Qt::SkipEmptyParts)
+ << QDir(QStringLiteral("%1/qtvirtualkeyboard/cerence/handwriting/").arg(QLibraryInfo::path(QLibraryInfo::DataPath))).absolutePath()
+ << QStringLiteral(":/qt-project.org/imports/QtQuick/VirtualKeyboard/Cerence/Handwriting/");
+ ldbManager->setPhdEnabled(true);
+ for (const QString &searchPath : defaultHwrDbPaths) {
+ ldbManager->addSearchPath(searchPath);
+ }
+ }
+
+ static void *decumaMalloc(size_t size, void *pPrivate)
+ {
+ Q_UNUSED(pPrivate);
+ return malloc(size);
+ }
+
+ static void *decumaCalloc(size_t elements, size_t size, void *pPrivate)
+ {
+ Q_UNUSED(pPrivate);
+ return calloc(elements, size);
+ }
+
+ static void decumaFree(void *ptr, void *pPrivate)
+ {
+ Q_UNUSED(pPrivate);
+ free(ptr);
+ }
+
+#if QT_VIRTUALKEYBOARD_CERENCE_HWR_LOG
+ static void decumaLogString(void *pUserData, const char *pLogString, DECUMA_UINT32 nLogStringLength)
+ {
+ static QMutex s_logMutex;
+ static QByteArray s_logString;
+ Q_UNUSED(pUserData);
+ QMutexLocker guard(&s_logMutex);
+ s_logString.append(pLogString, nLogStringLength);
+ if (s_logString.endsWith('\n')) {
+ while (s_logString.endsWith('\n'))
+ s_logString.chop(1);
+ qDebug() << (const char *)s_logString.constData();
+ s_logString.clear();
+ }
+ }
+#endif
+
+ static const char *engineModeToString(T9WriteInputMethod::EngineMode mode)
+ {
+ return QMetaEnum::fromType<T9WriteInputMethod::EngineMode>().key(static_cast<int>(mode));
+ }
+
+ bool initEngine(T9WriteInputMethod::EngineMode newEngineMode)
+ {
+ if (engineMode == newEngineMode)
+ return engineMode != T9WriteInputMethod::EngineMode::Uninitialized;
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::initEngine()" << engineModeToString(newEngineMode);
+
+ if (decumaSession)
+ exitEngine();
+
+ if (newEngineMode == T9WriteInputMethod::EngineMode::Uninitialized)
+ return false;
+
+ switch (newEngineMode) {
+ case T9WriteInputMethod::EngineMode::Alphabetic:
+ case T9WriteInputMethod::EngineMode::Arabic:
+ case T9WriteInputMethod::EngineMode::Hebrew:
+ case T9WriteInputMethod::EngineMode::Thai:
+ cjk = false;
+ break;
+ case T9WriteInputMethod::EngineMode::SimplifiedChinese:
+ case T9WriteInputMethod::EngineMode::TraditionalChinese:
+ case T9WriteInputMethod::EngineMode::HongKongChinese:
+ case T9WriteInputMethod::EngineMode::Japanese:
+ case T9WriteInputMethod::EngineMode::Korean:
+ cjk = true;
+ break;
+ default:
+ Q_ASSERT(0 && "Invalid T9WriteInputMethod::EngineMode!");
+ return false;
+ }
+ engineMode = newEngineMode;
+
+ memset(&sessionSettings, 0, sizeof(sessionSettings));
+
+ QString hwrDb = findHwrDb(engineMode);
+ hwrDbFile.setFileName(hwrDb);
+ if (!hwrDbFile.open(QIODevice::ReadOnly)) {
+ qCCritical(lcT9Write) << "Could not open HWR database" << hwrDb;
+ exitEngine();
+ return false;
+ }
+
+ sessionSettings.pStaticDB = reinterpret_cast<DECUMA_STATIC_DB_PTR>(hwrDbFile.map(0, hwrDbFile.size(), QFile::NoOptions));
+ if (!sessionSettings.pStaticDB) {
+ qCCritical(lcT9Write) << "Could not read HWR database" << hwrDb;
+ exitEngine();
+ return false;
+ }
+
+ symbolCategories.append(DECUMA_CATEGORY_ANSI);
+ languageCategories.append(DECUMA_LANG_EN);
+
+ sessionSettings.recognitionMode = mcrMode;
+ sessionSettings.writingDirection = unknownWriting;
+ sessionSettings.charSet.pSymbolCategories = symbolCategories.data();
+ sessionSettings.charSet.nSymbolCategories = static_cast<DECUMA_UINT8>(symbolCategories.size());
+ sessionSettings.charSet.pLanguages = languageCategories.data();
+ sessionSettings.charSet.nLanguages = static_cast<DECUMA_UINT8>(languageCategories.size());
+
+ session = QByteArray(static_cast<int>(DECUMA_API(GetSessionSize)()), 0);
+ decumaSession = reinterpret_cast<DECUMA_SESSION *>(!session.isEmpty() ? session.data() : nullptr);
+
+ DECUMA_STATUS status = DECUMA_API(BeginSession)(decumaSession, &sessionSettings, &memFuncs);
+ Q_ASSERT(status == decumaNoError);
+ if (status != decumaNoError) {
+ qCCritical(lcT9Write) << "Could not initialize engine" << status;
+ exitEngine();
+ return false;
+ }
+
+#if QT_VIRTUALKEYBOARD_CERENCE_HWR_LOG
+ DECUMA_API(StartLogging)(decumaSession, 0, decumaLogString);
+#endif
+
+ worker.reset(new T9WriteWorker(decumaSession, cjk));
+ worker->start();
+
+ Q_Q(T9WriteInputMethod);
+ resultListChangedConnection = QObjectPrivate::connect(
+ q, &T9WriteInputMethod::resultListChanged,
+ this, &T9WriteInputMethodPrivate::processResultCheckTimer,
+ Qt::QueuedConnection);
+ availableDictionariesChangedConnection = QObjectPrivate::connect(QVirtualKeyboardDictionaryManager::instance(),
+ &QVirtualKeyboardDictionaryManager::availableDictionariesChanged,
+ this, &T9WriteInputMethodPrivate::onAvailableDynamicDictionariesChanged);
+ activeDictionariesChangedConnection = QObjectPrivate::connect(QVirtualKeyboardDictionaryManager::instance(),
+ &QVirtualKeyboardDictionaryManager::activeDictionariesChanged,
+ this, &T9WriteInputMethodPrivate::onActiveDynamicDictionariesChanged);
+
+#ifdef HAVE_XT9
+ bindSettings();
+#endif
+
+ return true;
+ }
+
+ void exitEngine()
+ {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::exitEngine()";
+
+#ifdef HAVE_XT9
+ unbindSettings();
+#endif
+
+ QObject::disconnect(resultListChangedConnection);
+ QObject::disconnect(availableDictionariesChangedConnection);
+ QObject::disconnect(activeDictionariesChangedConnection);
+
+ worker.reset();
+
+#ifdef HAVE_XT9
+ xt9Exit();
+#endif
+
+ if (sessionSettings.pStaticDB) {
+ hwrDbFile.unmap(const_cast<uchar *>(reinterpret_cast<const uchar *>(sessionSettings.pStaticDB)));
+ hwrDbFile.close();
+ }
+
+ dlmDeactivate();
+
+ if (attachedDictionary) {
+ detachDictionary(attachedDictionary);
+ attachedDictionary.reset();
+ }
+ loadedDictionary.reset();
+
+ for (auto &dynamicDictionary : attachedDynamicDictionaries) {
+ detachDictionary(dynamicDictionary);
+ }
+ attachedDynamicDictionaries.clear();
+ dynamicDictionaries.clear();
+
+ if (decumaSession) {
+#if QT_VIRTUALKEYBOARD_CERENCE_HWR_LOG
+ DECUMA_API(StopLogging)(decumaSession);
+#endif
+ DECUMA_API(EndSession)(decumaSession);
+ decumaSession = nullptr;
+ session.clear();
+ }
+
+ memset(&sessionSettings, 0, sizeof(sessionSettings));
+
+ symbolCategories.clear();
+ languageCategories.clear();
+
+ ldbManager->closeAll();
+
+ engineMode = T9WriteInputMethod::EngineMode::Uninitialized;
+ cjk = false;
+ }
+
+#ifdef HAVE_XT9
+ void xt9Init(const QLocale &locale, QVirtualKeyboardInputEngine::InputMode inputMode, T9WriteInputMethod::EngineMode newEngineMode)
+ {
+ ET9U32 dwFirstLdbNum = Xt9LanguageMap::languageId(locale);
+ ET9U32 eInputMode = ET9AWInputMode_Default;
+
+ xt9DetachAllDynamicDictionaries();
+
+ switch (newEngineMode) {
+ case T9WriteInputMethod::EngineMode::SimplifiedChinese:
+ case T9WriteInputMethod::EngineMode::TraditionalChinese:
+ case T9WriteInputMethod::EngineMode::HongKongChinese:
+ if (inputMode == QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting) {
+ xt9Ime.reset(new Xt9CpIme(this));
+ switch (locale.script()) {
+ case QLocale::SimplifiedHanScript:
+ if (static_cast<Xt9CpIme *>(xt9Ime.data())) {
+ eInputMode = ET9CPMODE_PINYIN;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ if (xt9IsAlphabeticCoreDisabled()) {
+ xt9Exit();
+ return;
+ }
+ xt9Ime.reset(new Xt9AwIme(this));
+ dwFirstLdbNum = ET9LIDEnglish_UK;
+ }
+ break;
+
+ case T9WriteInputMethod::EngineMode::Japanese:
+ xt9Ime.reset(new Xt9JIme(this));
+ eInputMode = ET9AWInputMode_RomajiConversion;
+ break;
+
+ case T9WriteInputMethod::EngineMode::Korean:
+ xt9Ime.reset(new Xt9KIme(this));
+ break;
+
+ default:
+ if (xt9IsAlphabeticCoreDisabled()) {
+ xt9Exit();
+ return;
+ }
+ xt9Ime.reset(new Xt9AwIme(this));
+ break;
+ }
+
+ xt9Ime->ldbManager = ldbManager;
+ xt9Ime->sysInit();
+ xt9Ime->setWorkingDirectory(Settings::instance()->userDataPath());
+ if (xt9DynamicDictionaries.isEmpty())
+ xt9Ime->removeAllIndexes();
+
+ // PHD must be disabled during XT9 init to prevent finding
+ // T9 Write specific dictionary files.
+ ldbManager->setPhdEnabled(false);
+
+ xt9Ime->ldbInit(dwFirstLdbNum, ET9PLIDNone, eInputMode);
+
+ ldbManager->setPhdEnabled(true);
+
+ onDefaultDictionaryDisabledChanged();
+ onAvailableDynamicDictionariesChanged();
+ onActiveDynamicDictionariesChanged();
+ }
+
+ void xt9Exit()
+ {
+ if (xt9Ime)
+ xt9Ime->removeAllIndexes();
+
+ Q_Q(T9WriteInputMethod);
+ QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
+ const QStringList availableDictionaries = dictionaryManager->availableDictionaries();
+ for (const QString &dictionaryName : availableDictionaries) {
+ QVirtualKeyboardDictionary *dictionary = dictionaryManager->dictionary(dictionaryName);
+ dictionary->disconnect(q); // lambdas
+ }
+
+ xt9AttachedDynamicDictionaries.clear();
+ xt9DynamicDictionaries.clear();
+ xt9DynamicDictionaryNextId = 0;
+
+ xt9Ime.reset();
+ }
+
+ void bindSettings()
+ {
+ if (!defaultInputMethodDisabledChangedConnection)
+ defaultInputMethodDisabledChangedConnection = QObjectPrivate::connect(
+ Settings::instance(), &Settings::defaultInputMethodDisabledChanged,
+ this, &T9WriteInputMethodPrivate::onXt9AlphabeticCoreDisabledChanged);
+ if (!defaultDictionaryDisabledChangedConnection)
+ defaultDictionaryDisabledChangedConnection = QObjectPrivate::connect(
+ Settings::instance(), &Settings::defaultDictionaryDisabledChanged,
+ this, &T9WriteInputMethodPrivate::onDefaultDictionaryDisabledChanged);
+ if (!userDataResetConnection)
+ userDataResetConnection = QObjectPrivate::connect(
+ Settings::instance(), &Settings::userDataReset,
+ this, &T9WriteInputMethodPrivate::onUserDataReset);
+ }
+
+ void unbindSettings()
+ {
+ QObject::disconnect(defaultInputMethodDisabledChangedConnection);
+ QObject::disconnect(defaultDictionaryDisabledChangedConnection);
+ QObject::disconnect(userDataResetConnection);
+ }
+
+ void onXt9AlphabeticCoreDisabledChanged()
+ {
+ if (xt9IsAlphabeticCoreDisabled()) {
+ xt9Exit();
+ } else if (!xt9Ime) {
+ Q_Q(T9WriteInputMethod);
+ const QVirtualKeyboardInputEngine::InputMode inputMode = q->inputEngine()->inputMode();
+ const QLocale locale = QLocale(q->inputContext()->locale());
+ const DECUMA_UINT32 language = mapToDecumaLanguage(locale, inputMode);
+ const T9WriteInputMethod::EngineMode newEngineMode = mapLocaleToEngineMode(locale, language);
+ xt9Init(locale, inputMode, newEngineMode);
+ }
+ }
+
+ static bool xt9IsAlphabeticCoreDisabled()
+ {
+ Settings *settings = Settings::instance();
+ return settings->isDefaultInputMethodDisabled();
+ }
+
+ void onDefaultDictionaryDisabledChanged()
+ {
+ if (Xt9AwIme *xt9AwIme = dynamic_cast<Xt9AwIme *>(xt9Ime.data()))
+ xt9AwIme->setLdbEnabled(!Settings::instance()->isDefaultDictionaryDisabled());
+ }
+
+ void onUserDataReset()
+ {
+ dlmDeactivate();
+#ifdef HAVE_XT9
+ xt9Exit();
+#endif
+ }
+
+ ET9STATUS request(ET9_Request *const pRequest) override
+ {
+ Q_Q(T9WriteInputMethod);
+
+ switch (pRequest->eType) {
+ case ET9_REQ_BufferContext:
+ {
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (!ic)
+ break;
+
+ const ET9U32 dwContextLen = static_cast<ET9U32>(ic->cursorPosition());
+ const ET9U32 dwStartIndex =
+ dwContextLen <= pRequest->data.sBufferContextInfo.dwMaxBufLen ?
+ 0 : dwContextLen - pRequest->data.sBufferContextInfo.dwMaxBufLen;
+
+ pRequest->data.sBufferContextInfo.dwBufLen = dwContextLen - dwStartIndex;
+ const QString surroundingText = ic->surroundingText();
+
+ memcpy(pRequest->data.sBufferContextInfo.psBuf, surroundingText.utf16() + dwStartIndex,
+ pRequest->data.sBufferContextInfo.dwBufLen * sizeof(ET9SYMB));
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return ET9STATUS_NONE;
+ }
+
+ bool xt9AllSymbsArePinyin(const QString &symbs) const
+ {
+ bool allSymbsArePinyin = false;
+ for (const QChar &symb : symbs) {
+ const ushort ucs = symb.unicode();
+ allSymbsArePinyin = ET9CPIsPinyinSymbol(ucs);
+ if (!allSymbsArePinyin)
+ break;
+ }
+ return allSymbsArePinyin;
+ }
+
+ bool xt9AttachDictionary(const QString &dictionaryName)
+ {
+ if (!xt9DynamicDictionaries.contains(dictionaryName))
+ return false;
+
+ const quint16 id = xt9DynamicDictionaries.value(dictionaryName);
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::xt9AttachDictionary():" << dictionaryName << id;
+ xt9Ime->mountIndex(id);
+ xt9AttachedDynamicDictionaries[dictionaryName] = id;
+
+ return true;
+ }
+
+ void xt9DetachDictionary(const QString &dictionaryName)
+ {
+ if (!xt9AttachedDynamicDictionaries.contains(dictionaryName))
+ return;
+
+ const quint16 id = xt9AttachedDynamicDictionaries[dictionaryName];
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::xt9DetachDictionary():" << dictionaryName << id;
+ xt9Ime->unmountIndex(id);
+ xt9AttachedDynamicDictionaries.remove(dictionaryName);
+ }
+#endif
+
+#ifdef HAVE_XT9
+ void xt9DetachAllDynamicDictionaries()
+ {
+ if (xt9Ime) {
+ const QStringList dictionaryNames = xt9AttachedDynamicDictionaries.keys();
+ for (const QString &dictionaryName : dictionaryNames) {
+ xt9DetachDictionary(dictionaryName);
+ }
+ }
+ xt9AttachedDynamicDictionaries.clear();
+ }
+#endif
+
+ QStringList xt9BuildSelectionList(const QString &exactWord, int *defaultListIndex)
+ {
+#ifdef HAVE_XT9
+ if (!xt9Ime)
+ return QStringList();
+
+ xt9Ime->cursorMoved();
+
+ Q_Q(T9WriteInputMethod);
+ bool forceLowerCase =
+ engineMode == T9WriteInputMethod::EngineMode::SimplifiedChinese &&
+ q->inputEngine()->inputMode() == QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting &&
+ xt9AllSymbsArePinyin(exactWord);
+
+ ET9ClearAllSymbs(&xt9Ime->sWordSymbInfo);
+ for (const QChar &c : exactWord) {
+ ET9AddExplicitSymb(&xt9Ime->sWordSymbInfo, c.unicode(), 0, !forceLowerCase && c.isUpper() ? ET9SHIFT : ET9NOSHIFT, ET9_NO_ACTIVE_INDEX);
+ }
+
+ ET9SYMB gestureValue = 0;
+ return xt9Ime->buildSelectionList(defaultListIndex, &gestureValue);
+#else
+ return QStringList();
+#endif
+ }
+
+ void appendWordCandidates(QList<T9WriteWordCandidate> &aWordCandidates, int &activeWordIndex, const QString &exactWord, const QStringList &wordCandidatesToAppend, int defaultListIndex, T9WriteWordCandidate::Origin origin) const
+ {
+ for (int index = 0; index < wordCandidatesToAppend.size(); ++index) {
+ const T9WriteWordCandidate wordCandidate(wordCandidatesToAppend[index], index, origin);
+ if (!aWordCandidates.contains(wordCandidate)) {
+ bool isDefaultIndex = defaultListIndex > 0 &&
+ index == defaultListIndex && !cjk &&
+ origin == T9WriteWordCandidate::Origin::XT9;
+ if (isDefaultIndex) {
+ activeWordIndex = qMin(aWordCandidates.size(), 1);
+ aWordCandidates.insert(activeWordIndex, wordCandidate);
+ } else {
+ aWordCandidates.append(wordCandidate);
+ }
+ }
+ }
+
+ if (aWordCandidates.isEmpty()) {
+ aWordCandidates.append(T9WriteWordCandidate(exactWord));
+ }
+ }
+
+ QString findHwrDb(T9WriteInputMethod::EngineMode mode) const
+ {
+ for (const QString &defaultHwrDbPath : defaultHwrDbPaths) {
+ const QString result = findHwrDb(mode, defaultHwrDbPath);
+ if (!result.isEmpty())
+ return result;
+ }
+
+ qCCritical(lcT9Write) << "Could not find HWR database for" << engineModeToString(mode);
+ return QString();
+ }
+
+ QString findHwrDb(T9WriteInputMethod::EngineMode mode, const QString &dir) const
+ {
+ QString hwrDbPath(QDir::fromNativeSeparators(dir));
+ if (!hwrDbPath.endsWith(QLatin1Char('/')))
+ hwrDbPath.append(QLatin1Char('/'));
+ switch (mode) {
+ case T9WriteInputMethod::EngineMode::Alphabetic:
+ hwrDbPath.append(QLatin1String("hwrDB_le.bin"));
+ break;
+ case T9WriteInputMethod::EngineMode::Arabic:
+ hwrDbPath.append(QLatin1String("arabic/hwrDB_le.bin"));
+ break;
+ case T9WriteInputMethod::EngineMode::Hebrew:
+ hwrDbPath.append(QLatin1String("hebrew/hwrDB_le.bin"));
+ break;
+ case T9WriteInputMethod::EngineMode::Thai:
+ hwrDbPath.append(QLatin1String("thai/hwrDB_le.bin"));
+ break;
+ case T9WriteInputMethod::EngineMode::SimplifiedChinese:
+ hwrDbPath.append(QLatin1String("cjk_S_gb18030_le.hdb"));
+ break;
+ case T9WriteInputMethod::EngineMode::TraditionalChinese:
+ case T9WriteInputMethod::EngineMode::HongKongChinese:
+ hwrDbPath.append(QLatin1String("cjk_HK_std_le.hdb"));
+ break;
+ case T9WriteInputMethod::EngineMode::Japanese:
+ hwrDbPath.append(QLatin1String("cjk_J_std_le.hdb"));
+ break;
+ case T9WriteInputMethod::EngineMode::Korean:
+ hwrDbPath.append(QLatin1String("cjk_K_mkt_le.hdb"));
+ break;
+ default:
+ return QString();
+ }
+ if (!QFileInfo::exists(hwrDbPath)) {
+ return QString();
+ }
+ return hwrDbPath;
+ }
+
+ QString findDictionary(const QLocale &locale, DECUMA_SRC_DICTIONARY_TYPE &srcType)
+ {
+ srcType = numberOfSrcDictionaryTypes;
+
+ QString dictionary = ldbManager->findDictionary(locale);
+ if (!dictionary.isEmpty()) {
+ if (dictionary.endsWith(QLatin1String(".ldb"))) {
+ srcType = decumaPortableHWRDictionary;
+ } else if (dictionary.endsWith(QLatin1String(".phd"))) {
+ srcType = decumaPortableHWRDictionary;
+ } else {
+ qCCritical(lcT9Write) << "Incompatible dictionary" << dictionary;
+ dictionary.clear();
+ }
+ }
+
+ return dictionary;
+ }
+
+ bool attachDictionary(const QSharedPointer<T9WriteAbstractDictionary> &dictionary)
+ {
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+ Q_ASSERT(decumaSession != nullptr);
+ Q_ASSERT(dictionary != nullptr);
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::attachDictionary():" << dictionary->name();
+ DECUMA_STATUS status = DECUMA_API(AttachDictionary)(decumaSession, dictionary->data(), static_cast<DECUMA_UINT32>(dictionary->size()));
+ return status == decumaNoError;
+ }
+
+ void detachDictionary(const QSharedPointer<T9WriteAbstractDictionary> &dictionary)
+ {
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+ if (!dictionary)
+ return;
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::detachDictionary():" << dictionary->name();
+
+ Q_ASSERT(decumaSession != nullptr);
+ DECUMA_STATUS status = DECUMA_API(DetachDictionary)(decumaSession, dictionary->data());
+ Q_UNUSED(status);
+ Q_ASSERT(status == decumaNoError);
+ }
+
+ bool setInputMode(const QLocale &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
+ {
+ Q_Q(T9WriteInputMethod);
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::setInputMode():" << locale << inputMode;
+
+ finishRecognition();
+
+ // All tasks must be completed before changing the session settings
+ if (worker) {
+ worker->waitForAllTasks();
+ dictionaryTask.reset();
+ }
+
+ DECUMA_UINT32 language = mapToDecumaLanguage(locale, inputMode);
+ if (language == DECUMA_LANG_GSMDEFAULT) {
+ qCCritical(lcT9Write) << "Language is not supported" << locale.name();
+ return false;
+ }
+
+ T9WriteInputMethod::EngineMode newEngineMode = mapLocaleToEngineMode(locale, language);
+
+#ifdef HAVE_XT9
+ xt9Init(locale, inputMode, newEngineMode);
+#endif
+
+ if (!initEngine(newEngineMode)) {
+#ifdef HAVE_XT9
+ xt9Exit();
+#endif
+ return false;
+ }
+
+ onAvailableDynamicDictionariesChanged();
+ onActiveDynamicDictionariesChanged();
+
+ const Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
+ if (!inputMethodHints.testFlag(Qt::ImhHiddenText) && !inputMethodHints.testFlag(Qt::ImhNoPredictiveText) &&
+ !inputMethodHints.testFlag(Qt::ImhSensitiveData)) {
+ dlmActivate();
+ }
+
+ int isLanguageSupported = 0;
+ DECUMA_API(DatabaseIsLanguageSupported)(sessionSettings.pStaticDB, language, &isLanguageSupported);
+ if (!isLanguageSupported) {
+ qCCritical(lcT9Write) << "Language is not supported" << locale.name();
+ return false;
+ }
+
+ bool languageChanged = languageCategories.isEmpty() || languageCategories.first() != language;
+ languageCategories.clear();
+ languageCategories.append(language);
+
+ // Add English as secondary language for non-latin languages.
+ // T9 Write requires it for punctuation and latin symbols if
+ // included in the symbol categories.
+ if (locale.script() != QLocale::LatinScript)
+ languageCategories.append(DECUMA_LANG_EN);
+
+ if (!updateSymbolCategories(language, locale, inputMode))
+ return false;
+ updateRecognitionMode(language, locale, inputMode);
+ static const QList<DECUMA_UINT32> rtlLanguages = QList<DECUMA_UINT32>()
+ << DECUMA_LANG_AR << DECUMA_LANG_IW << DECUMA_LANG_FA << DECUMA_LANG_UR;
+ sessionSettings.writingDirection = rtlLanguages.contains(language) ? rightToLeft : leftToRight;
+
+ // Enable multi-threaded recognition if available.
+#ifdef DECUMA_USE_MULTI_THREAD
+ // Note: This feature requires T9 Write v8.0.0 or later,
+ // and feature enabled in the SDK.
+#if CERENCEHANDWRITINGAPIMAJORVERNUM > 28
+ sessionSettings.bUseThreads = 1;
+#else
+ sessionSettings.nMaxThreads = qMax(QThread::idealThreadCount(), 0);
+#endif
+#endif
+
+ qCDebug(lcT9Write) << " -> language categories:" << languageCategories;
+ qCDebug(lcT9Write) << " -> symbol categories:" << symbolCategories;
+ qCDebug(lcT9Write) << " -> recognition mode:" << sessionSettings.recognitionMode;
+
+ // Change session settings
+ sessionSettings.charSet.pSymbolCategories = symbolCategories.data();
+ sessionSettings.charSet.nSymbolCategories = static_cast<DECUMA_UINT8>(symbolCategories.size());
+ sessionSettings.charSet.pLanguages = languageCategories.data();
+ sessionSettings.charSet.nLanguages = static_cast<DECUMA_UINT8>(languageCategories.size());
+ DECUMA_STATUS status = DECUMA_API(ChangeSessionSettings)(decumaSession, &sessionSettings);
+
+ if (status == decumaUnsupportedLanguageInUcrMode || status == decumaUnsupportedSymbolCategoryInUcrMode) {
+ sessionSettings.recognitionMode = mcrMode;
+ status = DECUMA_API(ChangeSessionSettings)(decumaSession, &sessionSettings);
+ }
+
+ if (status != decumaNoError)
+ qCWarning(lcT9Write) << "Failed to configure HWR engine" << status;
+
+ caseFormatter.preferLowercase = q->inputContext()->inputMethodHints().testFlag(Qt::ImhPreferLowercase);
+
+ updateDictionary(language, locale, languageChanged);
+
+ return status == decumaNoError;
+ }
+
+ T9WriteInputMethod::EngineMode mapLocaleToEngineMode(const QLocale &locale, DECUMA_UINT32 language = 0)
+ {
+#ifdef HAVE_CERENCE_HWR_CJK
+ switch (locale.language()) {
+ case QLocale::Chinese: {
+ if (locale.script() == QLocale::TraditionalChineseScript)
+ return locale.territory() == QLocale::HongKong ? T9WriteInputMethod::EngineMode::HongKongChinese : T9WriteInputMethod::EngineMode::TraditionalChinese;
+ return T9WriteInputMethod::EngineMode::SimplifiedChinese;
+ }
+ case QLocale::Japanese:
+ return T9WriteInputMethod::EngineMode::Japanese;
+ case QLocale::Korean:
+ return T9WriteInputMethod::EngineMode::Korean;
+ default:
+ break;
+ }
+#else
+ Q_UNUSED(locale);
+ Q_UNUSED(language);
+#endif
+
+#ifdef HAVE_CERENCE_HWR_ALPHABETIC
+ switch (locale.script()) {
+ case QLocale::ArabicScript:
+ return T9WriteInputMethod::EngineMode::Arabic;
+ case QLocale::HebrewScript:
+ return T9WriteInputMethod::EngineMode::Hebrew;
+ case QLocale::ThaiScript:
+ return language == DECUMA_LANG_EN ? T9WriteInputMethod::EngineMode::Alphabetic
+ : T9WriteInputMethod::EngineMode::Thai;
+ default:
+ return T9WriteInputMethod::EngineMode::Alphabetic;
+ }
+#else
+ return T9WriteInputMethod::EngineMode::Uninitialized;
+#endif
+ }
+
+ DECUMA_UINT32 mapToDecumaLanguage(const QLocale &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
+ {
+ struct LanguageMap {
+ QLocale::Language localeLanguage;
+ DECUMA_UINT16 decumaLanguage;
+ };
+ static const LanguageMap languageMap[] = {
+ { QLocale::Language::Afrikaans, DECUMA_LANG_AF },
+ { QLocale::Language::Albanian, DECUMA_LANG_SQ },
+ { QLocale::Language::Arabic, DECUMA_LANG_AR },
+ { QLocale::Language::Azerbaijani, DECUMA_LANG_AZ },
+ { QLocale::Language::Basque, DECUMA_LANG_EU },
+ { QLocale::Language::Belarusian, DECUMA_LANG_BE },
+ { QLocale::Language::Bengali, DECUMA_LANG_BN },
+ { QLocale::Language::Bulgarian, DECUMA_LANG_BG },
+ { QLocale::Language::Catalan, DECUMA_LANG_CA },
+ { QLocale::Language::Chinese, DECUMA_LANG_PRC },
+ { QLocale::Language::Croatian, DECUMA_LANG_HR },
+ { QLocale::Language::Czech, DECUMA_LANG_CS },
+ { QLocale::Language::Danish, DECUMA_LANG_DA },
+ { QLocale::Language::Dutch, DECUMA_LANG_NL },
+ { QLocale::Language::English, DECUMA_LANG_EN },
+ { QLocale::Language::Estonian, DECUMA_LANG_ET },
+ { QLocale::Language::Finnish, DECUMA_LANG_FI },
+ { QLocale::Language::French, DECUMA_LANG_FR },
+ { QLocale::Language::Galician, DECUMA_LANG_GL },
+ { QLocale::Language::German, DECUMA_LANG_DE },
+ { QLocale::Language::Greek, DECUMA_LANG_EL },
+ { QLocale::Language::Gujarati, DECUMA_LANG_GU },
+ { QLocale::Language::Hausa, DECUMA_LANG_HA },
+ { QLocale::Language::Hebrew, DECUMA_LANG_IW },
+ { QLocale::Language::Hindi, DECUMA_LANG_HI },
+ { QLocale::Language::Hungarian, DECUMA_LANG_HU },
+ { QLocale::Language::Icelandic, DECUMA_LANG_IS },
+ { QLocale::Language::Indonesian, DECUMA_LANG_IN },
+ { QLocale::Language::Italian, DECUMA_LANG_IT },
+ { QLocale::Language::Japanese, DECUMA_LANG_JP },
+ { QLocale::Language::Kannada, DECUMA_LANG_KN },
+ { QLocale::Language::Kazakh, DECUMA_LANG_KK },
+ { QLocale::Language::Khmer, DECUMA_LANG_KM },
+ { QLocale::Language::Kirghiz, DECUMA_LANG_KY },
+ { QLocale::Language::Korean, DECUMA_LANG_KO },
+ { QLocale::Language::Latvian, DECUMA_LANG_LV },
+ { QLocale::Language::Lithuanian, DECUMA_LANG_LT },
+ { QLocale::Language::Macedonian, DECUMA_LANG_MK },
+ { QLocale::Language::Malay, DECUMA_LANG_MS },
+ { QLocale::Language::Malayalam, DECUMA_LANG_ML },
+ { QLocale::Language::Marathi, DECUMA_LANG_MR },
+ { QLocale::Language::Mongolian, DECUMA_LANG_MN },
+ { QLocale::Language::NorwegianBokmal, DECUMA_LANG_NO },
+ { QLocale::Language::Persian, DECUMA_LANG_FA },
+ { QLocale::Language::Polish, DECUMA_LANG_PL },
+ { QLocale::Language::Portuguese, DECUMA_LANG_PT },
+ { QLocale::Language::Punjabi, DECUMA_LANG_PA },
+ { QLocale::Language::Romanian, DECUMA_LANG_RO },
+ { QLocale::Language::Russian, DECUMA_LANG_RU },
+ { QLocale::Language::Serbian, DECUMA_LANG_SRCY },
+ { QLocale::Language::Sinhala, DECUMA_LANG_SI },
+ { QLocale::Language::Slovak, DECUMA_LANG_SK },
+ { QLocale::Language::Slovenian, DECUMA_LANG_SL },
+ { QLocale::Language::SouthernSotho, DECUMA_LANG_ST },
+ { QLocale::Language::Spanish, DECUMA_LANG_ES },
+ { QLocale::Language::Swahili, DECUMA_LANG_SW },
+ { QLocale::Language::Swedish, DECUMA_LANG_SV },
+ { QLocale::Language::Tajik, DECUMA_LANG_TG },
+ { QLocale::Language::Tamil, DECUMA_LANG_TA },
+ { QLocale::Language::Telugu, DECUMA_LANG_TE },
+ { QLocale::Language::Thai, DECUMA_LANG_TH },
+ { QLocale::Language::Turkish, DECUMA_LANG_TR },
+ { QLocale::Language::Ukrainian, DECUMA_LANG_UK },
+ { QLocale::Language::Urdu, DECUMA_LANG_UR },
+ { QLocale::Language::Uzbek, DECUMA_LANG_UZ },
+ { QLocale::Language::Vietnamese, DECUMA_LANG_VI },
+ // Last entry
+ { QLocale::Language::AnyLanguage, DECUMA_LANG_GSMDEFAULT }
+ };
+
+ const int localeLanguage = locale.language();
+ const LanguageMap *languageMapIterator = languageMap;
+ for (; languageMapIterator->localeLanguage != QLocale::Language::AnyLanguage &&
+ languageMapIterator->localeLanguage != localeLanguage; languageMapIterator++) {}
+
+ DECUMA_UINT32 language = languageMapIterator->decumaLanguage;
+ if (language == DECUMA_LANG_PRC) {
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting)
+ language = DECUMA_LANG_EN;
+ else if (locale.script() == QLocale::TraditionalChineseScript)
+ language = DECUMA_LANG_HK; // Common language for the traditional script
+ } else if (language == DECUMA_LANG_JP) {
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting)
+ language = DECUMA_LANG_EN;
+ } else if (language == DECUMA_LANG_KO) {
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting)
+ language = DECUMA_LANG_EN;
+ } else if (language == DECUMA_LANG_SRCY) {
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::Cyrillic)
+ language = DECUMA_LANG_SRLA;
+ } else if (language == DECUMA_LANG_AR || language == DECUMA_LANG_FA) {
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::Arabic && inputMode != QVirtualKeyboardInputEngine::InputMode::Numeric)
+ language = DECUMA_LANG_EN;
+ } else if (language == DECUMA_LANG_IW) {
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::Hebrew)
+ language = DECUMA_LANG_EN;
+ } else if (language == DECUMA_LANG_TH) {
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::Thai)
+ language = DECUMA_LANG_EN;
+ }
+
+ return language;
+ }
+
+ void updateRecognitionMode(DECUMA_UINT32 language, const QLocale &locale,
+ QVirtualKeyboardInputEngine::InputMode inputMode)
+ {
+ Q_Q(T9WriteInputMethod);
+ Q_UNUSED(language);
+ Q_UNUSED(locale);
+
+ // Select recognition mode
+ // Note: MCR mode is preferred, as it does not require recognition
+ // timer and provides better user experience.
+ sessionSettings.recognitionMode = mcrMode;
+
+ // T9 Write Alphabetic v8.0.0 supports UCR mode for specific languages
+ if (!cjk) {
+ switch (inputMode) {
+ case QVirtualKeyboardInputEngine::InputMode::Latin:
+ sessionSettings.recognitionMode = ucrMode;
+ break;
+ case QVirtualKeyboardInputEngine::InputMode::Arabic:
+ sessionSettings.recognitionMode = ucrMode;
+ break;
+ case QVirtualKeyboardInputEngine::InputMode::Cyrillic:
+ sessionSettings.recognitionMode = ucrMode;
+ break;
+ case QVirtualKeyboardInputEngine::InputMode::Hebrew:
+ case QVirtualKeyboardInputEngine::InputMode::Thai:
+ sessionSettings.recognitionMode = ucrMode;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Use scrMode with hidden text or with no predictive mode
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting) {
+ const Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
+ if (inputMethodHints.testFlag(Qt::ImhHiddenText) || inputMethodHints.testFlag(Qt::ImhNoPredictiveText))
+ sessionSettings.recognitionMode = scrMode;
+ }
+ }
+
+ bool updateSymbolCategories(DECUMA_UINT32 language, const QLocale &locale,
+ QVirtualKeyboardInputEngine::InputMode inputMode)
+ {
+ // Handle CJK in separate method
+ if (cjk)
+ return updateSymbolCategoriesCjk(language, locale, inputMode);
+
+ symbolCategories.clear();
+
+ // Choose the symbol categories by input mode, script and input method hints
+ bool leftToRightGestures = true;
+ Q_Q(T9WriteInputMethod);
+ const Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
+ switch (inputMode) {
+ case QVirtualKeyboardInputEngine::InputMode::Latin:
+ if (inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly)) {
+ symbolCategories.append(DECUMA_CATEGORY_EMAIL);
+ } else if (inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly)) {
+ symbolCategories.append(DECUMA_CATEGORY_URL);
+ } else {
+ if (language == DECUMA_LANG_EN || language == DECUMA_LANG_NL ||
+ language == DECUMA_LANG_MS || language == DECUMA_LANG_IN)
+ symbolCategories.append(DECUMA_CATEGORY_ANSI);
+ else
+ symbolCategories.append(DECUMA_CATEGORY_ISO8859_1);
+ symbolCategories.append(DECUMA_CATEGORY_DIGIT);
+ symbolCategories.append(DECUMA_CATEGORY_BASIC_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_CONTRACTION_MARK);
+ if (language == DECUMA_LANG_ES)
+ symbolCategories.append(DECUMA_CATEGORY_SPANISH_PUNCTUATIONS);
+ else if (language == DECUMA_LANG_VI)
+ symbolCategories.append(DECUMA_CATEGORY_VIETNAMESE_SUPPLEMENTS);
+ }
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Numeric:
+ if (language == DECUMA_LANG_AR || language == DECUMA_LANG_FA) {
+ symbolCategories.append(DECUMA_CATEGORY_ARABIC_NUM_MODE);
+ symbolCategories.append(DECUMA_CATEGORY_ARABIC_GESTURES);
+ leftToRightGestures = false;
+ break;
+ }
+ symbolCategories.append(DECUMA_CATEGORY_DIGIT);
+ if (!inputMethodHints.testFlag(Qt::ImhDigitsOnly))
+ symbolCategories.append(DECUMA_CATEGORY_NUM_SUP);
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Dialable:
+ symbolCategories.append(DECUMA_CATEGORY_PHONE_NUMBER);
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Greek:
+ symbolCategories.append(DECUMA_CATEGORY_GREEK);
+ symbolCategories.append(DECUMA_CATEGORY_QUEST_EXCL_MARK_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_PERIOD_COMMA_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_COLON_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_CONTRACTION_MARK);
+ symbolCategories.append(DECUMA_CATEGORY_CONTRACTION_MARK);
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Cyrillic:
+ symbolCategories.append(DECUMA_CATEGORY_CYRILLIC);
+ symbolCategories.append(DECUMA_CATEGORY_QUEST_EXCL_MARK_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_PERIOD_COMMA_PUNCTUATIONS);
+ // Ukrainian needs contraction mark, but not Russian or Bulgarian
+ if (language == DECUMA_LANG_UK)
+ symbolCategories.append(DECUMA_CATEGORY_CONTRACTION_MARK);
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Arabic:
+ symbolCategories.append(DECUMA_CATEGORY_ARABIC_ISOLATED_LETTER_MODE);
+ symbolCategories.append(DECUMA_CATEGORY_ARABIC_GESTURES);
+ leftToRightGestures = false;
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Hebrew:
+ symbolCategories.append(DECUMA_CATEGORY_HEBREW_GL_HEBREW_CURSIVE_MODE);
+ symbolCategories.append(DECUMA_CATEGORY_HEBREW_GL_HEBREW_LETTERSYMBOLS);
+ symbolCategories.append(DECUMA_CATEGORY_HEBREW_SHEQEL);
+ symbolCategories.append(DECUMA_CATEGORY_ARABIC_GESTURES);
+ leftToRightGestures = false;
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Thai:
+ symbolCategories.append(DECUMA_CATEGORY_THAI_BASE);
+ symbolCategories.append(DECUMA_CATEGORY_THAI_NON_BASE);
+ break;
+
+ default:
+ qCCritical(lcT9Write) << "Invalid input mode" << inputMode;
+ return false;
+ }
+
+ if (leftToRightGestures) {
+ symbolCategories.append(DECUMA_CATEGORY_BACKSPACE_STROKE);
+ symbolCategories.append(DECUMA_CATEGORY_RETURN_STROKE);
+ symbolCategories.append(DECUMA_CATEGORY_WHITESPACE_STROKE);
+ }
+
+ return true;
+ }
+
+ bool updateSymbolCategoriesCjk(DECUMA_UINT32 language, const QLocale &locale,
+ QVirtualKeyboardInputEngine::InputMode inputMode)
+ {
+ Q_ASSERT(cjk);
+
+ symbolCategories.clear();
+
+ switch (inputMode) {
+ case QVirtualKeyboardInputEngine::InputMode::Latin:
+ symbolCategories.append(DECUMA_CATEGORY_ANSI);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ symbolCategories.append(DECUMA_CATEGORY_PUNCTUATIONS);
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Numeric:
+ symbolCategories.append(DECUMA_CATEGORY_DIGIT);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ symbolCategories.append(DECUMA_CATEGORY_PUNCTUATIONS);
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::Dialable:
+ symbolCategories.append(DECUMA_CATEGORY_DIGIT);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting:
+ switch (locale.script()) {
+ case QLocale::SimplifiedChineseScript:
+ symbolCategories.append(DECUMA_CATEGORY_GB2312_A);
+ symbolCategories.append(DECUMA_CATEGORY_GB2312_B_CHARS_ONLY);
+ symbolCategories.append(DECUMA_CATEGORY_GBK_3);
+ symbolCategories.append(DECUMA_CATEGORY_GBK_4);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_GENERAL_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_PUNCTUATIONS);
+#ifdef HAVE_XT9
+ if (xt9Ime) {
+ symbolCategories.append(DECUMA_CATEGORY_ANSI);
+ }
+#endif
+ break;
+
+ case QLocale::TraditionalChineseScript:
+ symbolCategories.append(DECUMA_CATEGORY_BIGFIVE);
+ if (language == DECUMA_LANG_HK)
+ symbolCategories.append(DECUMA_CATEGORY_HKSCS_CHARS_ONLY);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_GENERAL_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_PUNCTUATIONS);
+ break;
+
+ default:
+ qCCritical(lcT9Write) << "Invalid locale" << locale << "for" << engineModeToString(engineMode);
+ return false;
+ }
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting:
+ symbolCategories.append(DECUMA_CATEGORY_JIS_LEVEL_1);
+ symbolCategories.append(DECUMA_CATEGORY_JIS_LEVEL_2);
+ symbolCategories.append(DECUMA_CATEGORY_HIRAGANA);
+ symbolCategories.append(DECUMA_CATEGORY_KATAKANA);
+ symbolCategories.append(DECUMA_CATEGORY_HIRAGANASMALL);
+ symbolCategories.append(DECUMA_CATEGORY_KATAKANASMALL);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_GENERAL_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_PUNCTUATIONS);
+#ifdef HAVE_XT9
+ if (xt9Ime) {
+ symbolCategories.append(DECUMA_CATEGORY_ANSI);
+ }
+#endif
+ break;
+
+ case QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting:
+ symbolCategories.append(DECUMA_CATEGORY_HANGUL_1001_A);
+ symbolCategories.append(DECUMA_CATEGORY_HANGUL_1001_B);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_GENERAL_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_PUNCTUATIONS);
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ void updateDictionary(DECUMA_UINT32 language, const QLocale &locale, bool languageChanged)
+ {
+ Q_Q(T9WriteInputMethod);
+
+ /* The dictionary is loaded in the background thread. Once the loading is
+ complete the dictionary will be attached to the current session. The
+ attachment happens in the worker thread context, thus the direct
+ connection for the signal handler and the mutex protecting the
+ converted dictionary for concurrent access.
+ The loading operation is blocking for the main thread only if the
+ user starts handwriting input before the operation is complete.
+ */
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+
+ // Detach previous dictionary if the language is being changed
+ // or the recognizer mode is single-character mode
+ const Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
+ if ((languageChanged || inputMethodHints.testFlag(Qt::ImhNoPredictiveText) || sessionSettings.recognitionMode == scrMode) && attachedDictionary) {
+ detachDictionary(attachedDictionary);
+ attachedDictionary.reset();
+ }
+
+ // Check if a dictionary needs to be loaded
+ if (languageChanged || !loadedDictionary) {
+ loadedDictionary.reset();
+
+ DECUMA_SRC_DICTIONARY_INFO dictionaryInfo;
+ memset(&dictionaryInfo, 0, sizeof(dictionaryInfo));
+
+ QList<QLocale> decumaLocales;
+ decumaLocales.append(locale);
+
+ // CJK: No dictionary for latin input
+ if (cjk && language == DECUMA_LANG_EN)
+ decumaLocales.clear();
+
+ dictionaryFileName.clear();
+ QLocale decumaLocale;
+ for (const QLocale &tryLocale : decumaLocales) {
+ dictionaryFileName = findDictionary(tryLocale, dictionaryInfo.srcType);
+ if (!dictionaryFileName.isEmpty()) {
+ decumaLocale = tryLocale;
+ break;
+ }
+ }
+ if (!dictionaryFileName.isEmpty()) {
+ if (dictionaryTask.isNull() || dictionaryTask->dictionaryFileName != dictionaryFileName) {
+ qCDebug(lcT9Write) << " -> load dictionary:" << dictionaryFileName;
+
+ bool convertDictionary = true;
+#if defined(HAVE_CERENCE_HWR_CJK)
+ // Chinese dictionary cannot be converted (PHD)
+ if (dictionaryInfo.srcType == decumaPortableHWRDictionary && decumaLocale.language() == QLocale::Chinese)
+ convertDictionary = false;
+#endif
+
+ QSharedPointer<T9WriteAbstractSource> sourceDictionary(new T9WriteFileSource(dictionaryInfo, dictionaryFileName));
+
+ QSharedPointer<T9WriteDictionary> newDictionary(new T9WriteDictionary(sourceDictionary, decumaSession, memFuncs, cjk));
+ dictionaryTask.reset(new T9WriteDictionaryTask(newDictionary, convertDictionary));
+
+ QObjectPrivate::connect(dictionaryTask.data(), &T9WriteDictionaryTask::completed,
+ this, &T9WriteInputMethodPrivate::dictionaryLoadCompleted, Qt::DirectConnection);
+ worker->addTask(dictionaryTask);
+ }
+ }
+ }
+
+ // Attach existing dictionary, if available
+ if (sessionSettings.recognitionMode != scrMode && !inputMethodHints.testFlag(Qt::ImhNoPredictiveText) &&
+ loadedDictionary && !attachedDictionary) {
+ if (attachDictionary(loadedDictionary))
+ attachedDictionary = loadedDictionary;
+ }
+ }
+
+ QByteArray getContext(QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode,
+ const QVariantMap &traceCaptureDeviceInfo,
+ const QVariantMap &traceScreenInfo) const
+ {
+ QCryptographicHash hash(QCryptographicHash::Md5);
+
+ hash.addData(QByteArrayView(reinterpret_cast<const char *>(&patternRecognitionMode), sizeof(patternRecognitionMode)));
+
+ QByteArray mapData;
+ QDataStream ds(&mapData, QIODevice::WriteOnly);
+ ds << traceCaptureDeviceInfo;
+ ds << traceScreenInfo;
+ hash.addData(mapData);
+
+ return hash.result();
+ }
+
+ void setContext(QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode,
+ const QVariantMap &traceCaptureDeviceInfo,
+ const QVariantMap &traceScreenInfo,
+ const QByteArray &context)
+ {
+ Q_UNUSED(patternRecognitionMode);
+ Q_UNUSED(traceScreenInfo);
+ if (context == currentContext)
+ return;
+ currentContext = context;
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::setContext():" << QLatin1String((context.toHex()));
+
+ // Finish recognition, but preserve current input
+ Q_Q(T9WriteInputMethod);
+ QString preeditText = q->inputContext()->preeditText();
+ bool preserveCurrentInput = !preeditText.isEmpty();
+ T9WriteCaseFormatter oldCaseFormatter(caseFormatter);
+ finishRecognition(!preserveCurrentInput);
+
+ if (preserveCurrentInput) {
+ caseFormatter = oldCaseFormatter;
+ stringStart = preeditText;
+ wordCandidates.append(T9WriteWordCandidate(preeditText));
+ activeWordIndex = 0;
+ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex);
+ }
+
+ const int dpi = traceCaptureDeviceInfo.value(QLatin1String("dpi"), 96).toInt();
+ static const int INSTANT_GESTURE_WIDTH_THRESHOLD_MM = 25;
+ gestureWidthThreshold = static_cast<DECUMA_UINT32>(INSTANT_GESTURE_WIDTH_THRESHOLD_MM / 25.4 * dpi);
+
+ gestureRecognizer.setDpi(dpi);
+
+ sessionSettings.baseline = 0;
+ sessionSettings.helpline = 0;
+ sessionSettings.topline = 0;
+ sessionSettings.supportLineSet = baselineAndHelpline;
+ sessionSettings.UIInputGuide = none;
+
+ DECUMA_STATUS status = DECUMA_API(ChangeSessionSettings)(decumaSession, &sessionSettings);
+ Q_ASSERT(status == decumaNoError);
+ }
+
+ QVirtualKeyboardTrace *traceBegin(
+ int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode,
+ const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo)
+ {
+ if (!worker)
+ return nullptr;
+
+ // The result id follows the trace id so that the (previous)
+ // results completed during the handwriting can be rejected.
+ resultId = traceId;
+
+ stopResultTimer();
+
+ // Dictionary must be completed before the arc addition can begin
+ worker->waitForAllTasksOfType<T9WriteDictionaryTask>();
+ dictionaryTask.reset();
+
+ // Cancel the current recognition task
+ worker->removeAllTasks<T9WriteRecognitionResultsTask>();
+ worker->removeAllTasks<T9WriteRecognitionTask>();
+ if (recognitionTask) {
+ recognitionTask->cancelRecognition();
+ recognitionTask.reset();
+ }
+
+ // Check for hard limit on the size the trace list
+ if (traceList.size() >= traceListHardLimit) {
+ worker->waitForAllTasksOfType<T9WriteAddArcTask>();
+ while (traceListHardLimit < traceList.size())
+ delete traceList.takeFirst();
+ }
+
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ if (!unipenTrace)
+ unipenTrace.reset(new UnipenTrace(traceCaptureDeviceInfo, traceScreenInfo));
+#endif
+
+ QByteArray context = getContext(patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo);
+ if (context != currentContext) {
+ worker->waitForAllTasks();
+ setContext(patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo, context);
+ }
+
+ DECUMA_STATUS status;
+
+ if (!arcAdditionStarted) {
+ worker->waitForAllTasks();
+
+ DECUMA_RECOGNITION_SETTINGS recSettings;
+ memset(&recSettings, 0, sizeof(recSettings));
+
+ // Boost dictionary words by default
+ recSettings.boostLevel = attachedDictionary || attachedDynamicDictionaries.size() > 0 ? boostDictWords : noBoost;
+
+ // Disable dictionary boost in UCR mode for URL and E-mail input
+ // Otherwise it will completely mess input
+ Q_Q(T9WriteInputMethod);
+ const Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
+ if (sessionSettings.recognitionMode == ucrMode && (inputMethodHints & (Qt::ImhUrlCharactersOnly | Qt::ImhEmailCharactersOnly)))
+ recSettings.boostLevel = noBoost;
+
+ recSettings.stringCompleteness = canBeContinued;
+ if (!stringStart.isEmpty())
+ recSettings.pStringStart = const_cast<DECUMA_UNICODE *>(stringStart.utf16());
+
+ status = DECUMA_API(BeginArcAddition)(decumaSession, &recSettings);
+ Q_ASSERT(status == decumaNoError);
+ arcAdditionStarted = true;
+ }
+
+ Q_Q(T9WriteInputMethod);
+ QVirtualKeyboardTrace *trace = new QVirtualKeyboardTrace(q);
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ trace->setChannels(QStringList(QLatin1String("t")));
+#endif
+ traceList.append(trace);
+
+ return trace;
+ }
+
+ void traceEnd(QVirtualKeyboardTrace *trace)
+ {
+ if (trace->isCanceled()) {
+ traceList.removeOne(trace);
+ delete trace;
+ } else {
+ if (cjk && countActiveTraces() == 0) {
+ // For some reason gestures don't seem to work in CJK mode
+ // Using our own gesture recognizer as fallback
+ if (handleGesture())
+ return;
+ }
+ worker->addTask(QSharedPointer<T9WriteAddArcTask>(new T9WriteAddArcTask(trace)));
+ }
+ if (!traceList.isEmpty()) {
+ Q_ASSERT(arcAdditionStarted);
+ if (countActiveTraces() == 0)
+ restartRecognition();
+ }
+ }
+
+ int countActiveTraces() const
+ {
+ int count = 0;
+ for (QVirtualKeyboardTrace *trace : std::as_const(traceList)) {
+ if (!trace->isFinal())
+ count++;
+ }
+ return count;
+ }
+
+ void clearTraces()
+ {
+ worker->waitForAllTasks();
+ qDeleteAll(traceList);
+ traceList.clear();
+ }
+
+ void noteSelected(int index)
+ {
+ if (index < 0 || index >= wordCandidates.size())
+ return;
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::noteSelected():" << index;
+ const T9WriteWordCandidate &wordCandidate = wordCandidates[index];
+ switch (wordCandidate.origin) {
+ case T9WriteWordCandidate::Origin::T9Write:
+ DECUMA_API(NoteSelectedCandidate)(decumaSession, wordCandidate.resultIndex);
+ break;
+#ifdef HAVE_XT9
+ case T9WriteWordCandidate::Origin::XT9:
+ {
+ Xt9AwIme *xt9AwIme = static_cast<Xt9AwIme *>(xt9Ime.data());
+ if (xt9AwIme) {
+ xt9AwIme->noteWordDone(wordCandidate.symbs);
+ xt9AwIme->selectWord(wordCandidate.resultIndex, true);
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+ }
+
+ void restartRecognition()
+ {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::restartRecognition()";
+
+ worker->removeAllTasks<T9WriteRecognitionResultsTask>();
+ if (recognitionTask) {
+ recognitionTask->cancelRecognition();
+ recognitionTask.reset();
+ }
+
+ QSharedPointer<T9WriteRecognitionResult> recognitionResult(new T9WriteRecognitionResult(resultId, 9, 64));
+ recognitionTask.reset(new T9WriteRecognitionTask(recognitionResult));
+ worker->addTask(recognitionTask);
+
+ QSharedPointer<T9WriteRecognitionResultsTask> resultsTask(new T9WriteRecognitionResultsTask(recognitionResult));
+ QObjectPrivate::connect(resultsTask.data(), &T9WriteRecognitionResultsTask::resultsAvailable, this, &T9WriteInputMethodPrivate::setResultList, Qt::DirectConnection);
+ worker->addTask(resultsTask);
+
+ resetResultTimer(cjk ? Settings::instance()->hwrTimeoutForCjk() : Settings::instance()->hwrTimeoutForAlphabetic());
+ }
+
+ void waitForRecognitionResults()
+ {
+ if (!worker)
+ return;
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::waitForRecognitionResults()";
+ worker->waitForAllTasks();
+ processResultCheckTimer();
+ }
+
+ bool finishRecognition(bool emitSelectionListChanged = true)
+ {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::finishRecognition()";
+ if (!worker)
+ return false;
+
+ bool result = !traceList.isEmpty();
+
+ Q_ASSERT(decumaSession != nullptr);
+
+ stopResultTimer();
+
+ worker->removeAllTasks<T9WriteAddArcTask>();
+ worker->removeAllTasks<T9WriteRecognitionResultsTask>();
+ if (recognitionTask) {
+ recognitionTask->cancelRecognition();
+ recognitionTask.reset();
+ result = true;
+ }
+ worker->waitForAllTasks();
+
+ clearTraces();
+
+ if (arcAdditionStarted) {
+ DECUMA_API(EndArcAddition)(decumaSession);
+ arcAdditionStarted = false;
+ }
+
+ if (!wordCandidates.isEmpty()) {
+ wordCandidates.clear();
+ activeWordIndex = -1;
+ if (emitSelectionListChanged) {
+ Q_Q(T9WriteInputMethod);
+ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex);
+ }
+ result = true;
+ }
+
+ stringStart.clear();
+ scrResult.clear();
+ caseFormatter.clear();
+
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ unipenTrace.reset();
+#endif
+
+ return result;
+ }
+
+ bool select(int index = -1)
+ {
+ if (!worker)
+ return false;
+
+ if (sessionSettings.recognitionMode != scrMode && wordCandidates.isEmpty()) {
+ finishRecognition();
+ return false;
+ }
+ if (sessionSettings.recognitionMode == scrMode && scrResult.isEmpty()) {
+ finishRecognition();
+ return false;
+ }
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::select():" << index;
+
+ Q_Q(T9WriteInputMethod);
+ if (sessionSettings.recognitionMode != scrMode) {
+ index = index >= 0 ? index : activeWordIndex;
+ noteSelected(index);
+ QString finalWord = wordCandidates.at(index).symbs;
+ dlmAddWord(finalWord);
+
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ // Record trace
+ if (unipenTrace) {
+ if (finalWord.length() == 1) {
+ // In recording mode, the text case must match with the current text case
+ QChar ch(finalWord.at(0));
+ if (!ch.isLetter() || (ch.isUpper() == (textCase == QVirtualKeyboardInputEngine::TextCase::Upper))) {
+ QStringList homeLocations = QStandardPaths::standardLocations(QStandardPaths::HomeLocation);
+ if (!homeLocations.isEmpty()) {
+ unipenTrace->setDirectory(QStringLiteral("%1/%2").arg(homeLocations.at(0)).arg(QLatin1String("VIRTUAL_KEYBOARD_TRACES")));
+ unipenTrace->record(traceList);
+ unipenTrace->save(ch.unicode(), 100);
+ }
+ }
+ }
+ }
+#endif
+
+ finishRecognition();
+ QChar gesture = T9WriteInputMethodPrivate::mapSymbolToGesture(finalWord.right(1).at(0));
+ if (!gesture.isNull())
+ finalWord.chop(1);
+ q->inputContext()->commit(finalWord);
+ applyGesture(gesture);
+ } else if (sessionSettings.recognitionMode == scrMode) {
+ QString finalWord = scrResult;
+ finishRecognition();
+ q->inputContext()->inputEngine()->virtualKeyClick(static_cast<Qt::Key>(finalWord.at(0).unicode()), finalWord, Qt::NoModifier);
+ }
+
+ return true;
+ }
+
+ void resetResultTimer(int interval = 500)
+ {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::resetResultTimer():" << interval;
+ Q_Q(T9WriteInputMethod);
+ stopResultTimer();
+ resultTimer = q->startTimer(interval);
+ }
+
+ void stopResultTimer()
+ {
+ if (resultTimer) {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::stopResultTimer()";
+ Q_Q(T9WriteInputMethod);
+ q->killTimer(resultTimer);
+ resultTimer = 0;
+ }
+ }
+
+ void dictionaryLoadCompleted(QSharedPointer<T9WriteAbstractDictionary> dictionary)
+ {
+ // Note: This method is called in worker thread context
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+
+ if (!dictionary)
+ return;
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::dictionaryLoadCompleted():"
+ << dictionary->name() << dictionary->data() << dictionary->size();
+
+ Q_Q(T9WriteInputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (ic && dictionary->name() == dictionaryFileName) {
+ loadedDictionary = dictionary;
+ if (sessionSettings.recognitionMode != scrMode &&
+ !ic->inputMethodHints().testFlag(Qt::ImhNoPredictiveText) &&
+ !attachedDictionary) {
+ if (attachDictionary(loadedDictionary))
+ attachedDictionary = loadedDictionary;
+ }
+ }
+ }
+
+ void recognitionError(int status)
+ {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::recognitionError():" << status;
+ Q_Q(T9WriteInputMethod);
+ q->reset();
+ }
+
+ // Note: Called from T9WriteWorker thread!
+ void setResultList(const QVariantList &resultList)
+ {
+ {
+ const std::lock_guard<QRecursiveMutex> ListGuard(resultListLock);
+ this->resultList = resultList;
+ }
+
+ Q_Q(T9WriteInputMethod);
+ emit q->resultListChanged();
+ }
+
+ void processResultCheckTimer()
+ {
+ bool resultTimerWasRunning = resultTimer != 0;
+
+ processResult();
+
+ // Restart the result timer now if it stopped before the results were completed
+ if (!resultTimerWasRunning && (!scrResult.isEmpty() || !wordCandidates.isEmpty()))
+ resetResultTimer(0);
+ }
+
+ void processResult()
+ {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::processResult()";
+
+#ifdef SENSITIVE_DEBUG
+ if (lcT9Write().isDebugEnabled()) {
+ const std::lock_guard<QRecursiveMutex> resultListGuard(resultListLock);
+ for (int i = 0; i < resultList.size(); i++) {
+ QVariantMap result = resultList.at(i).toMap();
+ QString resultPrint = QStringLiteral("%1: ").arg(i + 1);
+ QString resultChars = result.value(QLatin1String("chars")).toString();
+ if (!resultChars.isEmpty())
+ resultPrint.append(resultChars);
+ if (result.contains(QLatin1String("gesture"))) {
+ if (!resultChars.isEmpty())
+ resultPrint.append(QLatin1String(", "));
+ QString gesture = result[QLatin1String("gesture")].toString();
+ resultPrint.append(QLatin1String("gesture ="));
+ for (const QChar &chr : gesture) {
+ resultPrint.append(QString::fromLatin1(" 0x%1").arg(chr.unicode(), 0, 16));
+ }
+ }
+ qCDebug(lcT9Write) << resultPrint.toUtf8().constData();
+ }
+ }
+#endif
+
+ Q_Q(T9WriteInputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (!ic)
+ return;
+
+#ifdef HAVE_XT9
+ int xt9DefaultListIndex = 0;
+ QStringList xt9Candidates;
+#endif
+
+ QList<T9WriteWordCandidate> newWordCandidates;
+ int newActiveWordIndex = -1;
+ QString resultString;
+ QString gesture;
+ QVariantList symbolStrokes;
+ {
+ const std::lock_guard<QRecursiveMutex> resultListGuard(resultListLock);
+ if (resultList.isEmpty())
+ return;
+
+ if (resultList.first().toMap()[QLatin1String("resultId")] != resultId) {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::processResult(): resultId mismatch" << resultList.first().toMap()[QLatin1String("resultId")] << "(" << resultId << ")";
+ resultList.clear();
+ return;
+ }
+ lastResultId = resultId;
+
+ for (int i = 0; i < resultList.size(); i++) {
+ QVariantMap result = resultList.at(i).toMap();
+ QString resultChars = result[QLatin1String("chars")].toString();
+ if (i == 0) {
+ if (ic->isShiftActive()) {
+ caseFormatter.ensureLength(1, textCase);
+ caseFormatter.ensureLength(resultChars.length(), QVirtualKeyboardInputEngine::TextCase::Lower);
+ } else {
+ caseFormatter.ensureLength(resultChars.length(), textCase);
+ }
+ }
+ if (!resultChars.isEmpty()) {
+ resultChars = caseFormatter.formatString(resultChars);
+ if (sessionSettings.recognitionMode != scrMode && !newWordCandidates.contains(T9WriteWordCandidate(resultChars))) {
+ newWordCandidates.append(T9WriteWordCandidate(resultChars, i, T9WriteWordCandidate::Origin::T9Write));
+ }
+ }
+ if (i == 0) {
+ resultString = resultChars;
+ if (result.contains(QLatin1String("gesture")))
+ gesture = result[QLatin1String("gesture")].toString();
+ if (sessionSettings.recognitionMode != scrMode && result.contains(QLatin1String("symbolStrokes")))
+ symbolStrokes = result[QLatin1String("symbolStrokes")].toList();
+ if (sessionSettings.recognitionMode == scrMode)
+ break;
+#ifdef HAVE_XT9
+ xt9Candidates = xt9BuildSelectionList(resultString, &xt9DefaultListIndex);
+#endif
+ } else {
+ // Add a gesture symbol to the secondary candidate
+ if (sessionSettings.recognitionMode != scrMode && result.contains(QLatin1String("gesture"))) {
+ QString gesture2 = result[QLatin1String("gesture")].toString();
+ if (gesture2.length() == 1) {
+ QChar symbol = T9WriteInputMethodPrivate::mapGestureToSymbol(gesture2.at(0).unicode());
+ if (!symbol.isNull()) {
+ // Check for duplicates
+ bool duplicateFound = false;
+ for (const T9WriteWordCandidate &wordCandidate : newWordCandidates) {
+ duplicateFound = wordCandidate.symbs.size() == 1 && wordCandidate.symbs.at(0) == symbol;
+ if (duplicateFound)
+ break;
+ }
+ if (!duplicateFound) {
+ if (!resultChars.isEmpty()) {
+ newWordCandidates.last().symbs.append(symbol);
+ } else {
+ newWordCandidates.append(T9WriteWordCandidate(symbol, i, T9WriteWordCandidate::Origin::T9Write));
+ }
+ }
+ }
+ }
+ }
+#ifdef HAVE_XT9
+ if (i >= 2 && !xt9Candidates.isEmpty())
+ break;
+#endif
+ }
+ }
+
+ resultList.clear();
+
+ if (!newWordCandidates.isEmpty())
+ newActiveWordIndex = 0;
+#ifdef HAVE_XT9
+ if (!xt9Candidates.isEmpty())
+ appendWordCandidates(newWordCandidates, newActiveWordIndex, resultString, xt9Candidates, xt9DefaultListIndex, T9WriteWordCandidate::Origin::XT9);
+#endif
+ }
+
+ bool wordCandidatesChanged = true;
+
+#ifndef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ // Delete trace history
+ // Note: We have to be sure there are no background tasks
+ // running since the QVirtualKeyboardTrace objects consumed there.
+ if (worker->numberOfPendingTasks() == 0) {
+
+ const QVirtualKeyboardInputEngine::InputMode inputMode = q->inputEngine()->inputMode();
+ if (sessionSettings.recognitionMode != scrMode &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting) {
+ const int hwrTimeout = cjk ?
+ Settings::instance()->hwrTimeoutForCjk() :
+ Settings::instance()->hwrTimeoutForAlphabetic();
+ for (int traceIndex = 0; traceIndex < traceList.size();) {
+ QVirtualKeyboardTrace *trace = traceList.at(traceIndex);
+ if (trace->opacity() > 0) {
+ trace->startHideTimer(hwrTimeout);
+ ++traceIndex;
+ } else {
+ traceList.removeAt(traceIndex);
+ delete trace;
+ }
+ }
+ }
+
+ // Enforce hard limit for number of traces
+ if (traceList.size() >= traceListHardLimit) {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::processResult(): Clearing traces (hard limit):" << traceList.size();
+ clearTraces();
+ }
+ }
+#endif
+
+ // Find a gesture at the end of the first result
+ if (!gesture.isEmpty()) {
+
+ DECUMA_UNICODE gestureSymbol = gesture.at(0).unicode();
+ if (!applyGesture(gestureSymbol)) {
+ ic->commit(ic->preeditText());
+ finishRecognition();
+ }
+
+ return;
+ }
+
+ if (sessionSettings.recognitionMode != scrMode) {
+ ignoreUpdate = true;
+ ic->setPreeditText(resultString);
+ ignoreUpdate = false;
+ } else {
+ scrResult = resultString;
+ }
+
+ if (wordCandidatesChanged) {
+ wordCandidates = newWordCandidates;
+ activeWordIndex = wordCandidates.isEmpty() ? -1 : newActiveWordIndex;
+ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex);
+ }
+
+ if (arcAdditionStarted && traceList.isEmpty() && worker->numberOfPendingTasks() == 0) {
+ DECUMA_API(EndArcAddition)(decumaSession);
+ arcAdditionStarted = false;
+ }
+ }
+
+ static QChar mapGestureToSymbol(const QChar &gesture)
+ {
+ switch (gesture.unicode()) {
+ case '\r':
+ return QChar(0x23CE);
+ case ' ':
+ return QChar(0x2423);
+ default:
+ return QChar();
+ }
+ }
+
+ static QChar mapSymbolToGesture(const QChar &symbol)
+ {
+ switch (symbol.unicode()) {
+ case 0x23CE:
+ return QLatin1Char('\r');
+ case 0x2423:
+ return QLatin1Char(' ');
+ default:
+ return QChar();
+ }
+ }
+
+ bool applyGesture(const QChar &gesture)
+ {
+ Q_Q(T9WriteInputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ switch (gesture.unicode()) {
+ case '\b':
+ return ic->inputEngine()->virtualKeyClick(Qt::Key_Backspace, QString(), Qt::NoModifier);
+ case '\r':
+ return ic->inputEngine()->virtualKeyClick(Qt::Key_Return, QLatin1String("\n"), Qt::NoModifier);
+ case ' ':
+ return ic->inputEngine()->virtualKeyClick(Qt::Key_Space, QLatin1String(" "), Qt::NoModifier);
+ default:
+ return false;
+ }
+ }
+
+ bool handleGesture()
+ {
+ if (countActiveTraces() > 0)
+ return false;
+
+ QVariantMap gesture(gestureRecognizer.recognize(traceList.mid(traceList.size() - 1, 1)));
+ if (gesture.isEmpty())
+ return false;
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::handleGesture():" << gesture;
+
+ if (gesture[QLatin1String("type")].toString() == QLatin1String("swipe")) {
+
+ static const int SWIPE_ANGLE_THRESHOLD = 15; // degrees +-
+
+ qreal swipeLength = gesture[QLatin1String("length")].toReal();
+ if (swipeLength >= gestureWidthThreshold) {
+
+ Q_Q(T9WriteInputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (!ic)
+ return false;
+
+ qreal swipeAngle = gesture[QLatin1String("angle_degrees")].toReal();
+ int swipeTouchCount = gesture[QLatin1String("touch_count")].toInt();
+
+ // Swipe left
+ if (swipeAngle <= 180 + SWIPE_ANGLE_THRESHOLD && swipeAngle >= 180 - SWIPE_ANGLE_THRESHOLD) {
+ if (swipeTouchCount == 1) {
+ // Single swipe: backspace
+ ic->inputEngine()->virtualKeyClick(Qt::Key_Backspace, QString(), Qt::NoModifier);
+ return true;
+ }
+ return false;
+ }
+
+ // Swipe right
+ const QVirtualKeyboardInputEngine::InputMode inputMode = q->inputEngine()->inputMode();
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting) {
+ if (swipeAngle <= SWIPE_ANGLE_THRESHOLD || swipeAngle >= 360 - SWIPE_ANGLE_THRESHOLD) {
+ if (swipeTouchCount == 1) {
+ // Single swipe: space
+ ic->inputEngine()->virtualKeyClick(Qt::Key_Space, QLatin1String(" "), Qt::NoModifier);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ // Swipe up
+ if (swipeAngle <= 270 + SWIPE_ANGLE_THRESHOLD && swipeAngle >= 270 - SWIPE_ANGLE_THRESHOLD) {
+ if (swipeTouchCount == 1) {
+ // Single swipe: toggle input mode
+ select();
+ if (!(ic->inputMethodHints() & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly))) {
+ QList<int> inputModes = ic->inputEngine()->inputModes();
+ // Filter out duplicate numeric mode (in favor of Numeric)
+ int indexOfNumericInputMode = inputModes.indexOf(static_cast<const int>(QVirtualKeyboardInputEngine::InputMode::Numeric));
+ int indexOfDialableInputMode = inputModes.indexOf(static_cast<const int>(QVirtualKeyboardInputEngine::InputMode::Dialable));
+ if (indexOfNumericInputMode != -1 && indexOfDialableInputMode != -1)
+ inputModes.removeAt(inputMode != QVirtualKeyboardInputEngine::InputMode::Dialable ?
+ indexOfDialableInputMode :
+ indexOfNumericInputMode);
+ if (inputModes.size() > 1) {
+ int inputModeIndex = inputModes.indexOf(static_cast<const int>(inputMode)) + 1;
+ if (inputModeIndex >= inputModes.size())
+ inputModeIndex = 0;
+ ic->inputEngine()->setInputMode(static_cast<QVirtualKeyboardInputEngine::InputMode>(inputModes.at(inputModeIndex)));
+ }
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ bool isValidInputChar(const QChar &c) const
+ {
+ if (c.isLetterOrNumber())
+ return true;
+ if (isJoiner(c))
+ return true;
+ return false;
+ }
+
+ bool isJoiner(const QChar &c) const
+ {
+ if (c.isPunct() || c.isSymbol()) {
+ Q_Q(const T9WriteInputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (ic) {
+ Qt::InputMethodHints inputMethodHints = ic->inputMethodHints();
+ if (inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) || inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly))
+ return QString(QStringLiteral(":/?#[]@!$&'()*+,;=-_.%")).contains(c);
+ }
+ ushort unicode = c.unicode();
+ if (unicode == Qt::Key_Apostrophe || unicode == Qt::Key_Minus)
+ return true;
+ }
+ return false;
+ }
+
+ void onAvailableDynamicDictionariesChanged()
+ {
+ if (!worker)
+ return;
+
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+ QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
+
+ const QStringList availableDictionaries = dictionaryManager->availableDictionaries();
+ for (const QString &dictionaryName : availableDictionaries) {
+
+ if (!dynamicDictionaries.contains(dictionaryName)) {
+
+ DECUMA_SRC_DICTIONARY_INFO sourceInfo;
+ sourceInfo.srcType = decumaTextList;
+ sourceInfo.bNoFrequencyRanking = 1;
+ sourceInfo.multiStepWordSeparator = 0;
+
+ QSharedPointer<T9WriteAbstractSource> sourceDictionary(
+ new T9WriteStringSource(
+ sourceInfo,
+ dictionaryManager->dictionary(dictionaryName)->contents(),
+ dictionaryName));
+
+ QSharedPointer<T9WriteDictionary> dynamicDictionary(
+ new T9WriteDictionary(
+ sourceDictionary,
+ decumaSession,
+ memFuncs,
+ cjk));
+
+ dynamicDictionaries[dictionaryName] = dynamicDictionary;
+
+ QSharedPointer<T9WriteDictionaryTask> dynamicDictionaryTask(
+ new T9WriteDictionaryTask(
+ dynamicDictionary,
+ true));
+
+ Q_Q(T9WriteInputMethod);
+ q->connect(dynamicDictionaryTask.data(),
+ &T9WriteDictionaryTask::completed,
+ [=](QSharedPointer<T9WriteAbstractDictionary> dynamicDictionary) {
+
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+
+ if (dynamicDictionary->data()) {
+ if (dictionaryManager->activeDictionaries().contains(dictionaryName) &&
+ !attachedDynamicDictionaries.contains(dictionaryName) &&
+ attachDictionary(dynamicDictionary))
+ attachedDynamicDictionaries[dictionaryName] = dynamicDictionary;
+ } else {
+ dynamicDictionaries.remove(dictionaryName);
+ }
+ });
+
+ worker->addTask(dynamicDictionaryTask);
+ }
+
+#ifdef HAVE_XT9
+ if (!xt9DynamicDictionaries.contains(dictionaryName)) {
+ if (xt9Ime) {
+ QVirtualKeyboardDictionary *dictionary = dictionaryManager->dictionary(dictionaryName);
+ const quint16 id = static_cast<quint16>(xt9DynamicDictionaryNextId.fetchAndAddRelaxed(1));
+ xt9DynamicDictionaries[dictionaryName] = id;
+
+ xt9Ime->updateIndex(id, dictionary->contents());
+
+ Q_Q(T9WriteInputMethod);
+ q->connect(dictionary, &QVirtualKeyboardDictionary::contentsChanged, q, [=]() {
+ xt9Ime->updateIndex(id, dictionary->contents());
+ if (xt9AttachedDynamicDictionaries.contains(dictionaryName))
+ xt9Ime->mountIndex(id);
+ });
+ }
+ }
+#endif
+ }
+ }
+
+ void onActiveDynamicDictionariesChanged()
+ {
+ if (!worker)
+ return;
+
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+ QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
+
+ // Attach
+ const QStringList activeDictionaries = dictionaryManager->activeDictionaries();
+ for (const QString &dictionaryName : activeDictionaries) {
+
+ QSharedPointer<T9WriteAbstractDictionary> dynamicDictionary = dynamicDictionaries.value(dictionaryName);
+ if (dynamicDictionary && dynamicDictionary->data() &&
+ !attachedDynamicDictionaries.contains(dictionaryName) &&
+ dynamicDictionary->isCompleted() &&
+ attachDictionary(dynamicDictionary)) {
+ attachedDynamicDictionaries[dictionaryName] = dynamicDictionary;
+ }
+#ifdef HAVE_XT9
+ if (xt9Ime) {
+ if (!xt9AttachedDynamicDictionaries.contains(dictionaryName)) {
+ xt9AttachDictionary(dictionaryName);
+ }
+ }
+#endif
+ }
+
+ // Detach
+ const QStringList attachedDynamicDictionariesKeys = attachedDynamicDictionaries.keys();
+ for (const QString &dictionaryName : attachedDynamicDictionariesKeys) {
+ if (!activeDictionaries.contains(dictionaryName)) {
+ if (attachedDynamicDictionaries.contains(dictionaryName)) {
+ detachDictionary(attachedDynamicDictionaries[dictionaryName]);
+ attachedDynamicDictionaries.remove(dictionaryName);
+ }
+ }
+ }
+#ifdef HAVE_XT9
+ // Detach (XT9)
+ if (xt9Ime) {
+ const QStringList xt9AttachedDynamicDictionariesKeys = xt9AttachedDynamicDictionaries.keys();
+ for (const QString &dictionaryName : xt9AttachedDynamicDictionariesKeys) {
+ if (!activeDictionaries.contains(dictionaryName)) {
+ xt9DetachDictionary(dictionaryName);
+ }
+ }
+ }
+#endif
+ }
+
+ bool isDlmActive()
+ {
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+ return !loadedDlmDictionary.isNull();
+ }
+
+ QString dlmFileName() const
+ {
+ QString suffix;
+ switch (engineMode) {
+ case T9WriteInputMethod::EngineMode::Alphabetic:
+ case T9WriteInputMethod::EngineMode::Arabic:
+ case T9WriteInputMethod::EngineMode::Hebrew:
+ case T9WriteInputMethod::EngineMode::Thai:
+ suffix = QStringLiteral("aw");
+ break;
+ case T9WriteInputMethod::EngineMode::SimplifiedChinese:
+ case T9WriteInputMethod::EngineMode::TraditionalChinese:
+ case T9WriteInputMethod::EngineMode::HongKongChinese:
+ suffix = QStringLiteral("cp");
+ break;
+ case T9WriteInputMethod::EngineMode::Japanese:
+ suffix = QStringLiteral("j");
+ break;
+ case T9WriteInputMethod::EngineMode::Korean:
+ suffix = QStringLiteral("k");
+ break;
+ default:
+ break;
+ }
+ return QStringLiteral("cerence-hwr%1.dlm").arg(suffix);
+ }
+
+ void dlmActivate()
+ {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::dlmActivate()";
+
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+ if (loadedDlmDictionary)
+ return;
+
+ DECUMA_SRC_DICTIONARY_INFO sourceInfo;
+ sourceInfo.srcType = decumaPortableHWRDictionary;
+ sourceInfo.bNoFrequencyRanking = 1;
+ sourceInfo.multiStepWordSeparator = 0;
+
+ QString dictionaryName(QStringLiteral("%1/%2").arg(Settings::instance()->userDataPath(), dlmFileName()));
+ QSharedPointer<T9WriteAbstractSource> sourceDictionary(
+ new T9WriteFileSource(
+ sourceInfo,
+ dictionaryName));
+
+ loadedDlmDictionary.reset(new T9WriteDynamicDictionary(
+ sourceDictionary,
+ CERENCE_HWR_DLM_MAX_WORDS,
+ memFuncs,
+ cjk));
+
+ QSharedPointer<T9WriteDictionaryTask> dynamicDictionaryTask(
+ new T9WriteDictionaryTask(
+ loadedDlmDictionary,
+ false));
+
+ Q_Q(T9WriteInputMethod);
+ q->connect(dynamicDictionaryTask.data(),
+ &T9WriteDictionaryTask::completed,
+ [=](QSharedPointer<T9WriteAbstractDictionary> dynamicDictionary) {
+
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+
+ if (!loadedDlmDictionary || dynamicDictionary != loadedDlmDictionary)
+ return;
+
+ if (dynamicDictionary->data()) {
+ if (attachedDlmDictionary != dynamicDictionary &&
+ attachDictionary(dynamicDictionary)) {
+ attachedDlmDictionary = dynamicDictionary;
+ }
+ } else {
+ dlmDeactivate();
+ }
+ });
+
+ worker->addTask(dynamicDictionaryTask);
+ }
+
+ void dlmDeactivate()
+ {
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+
+ if (loadedDlmDictionary) {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::dlmDeactivate()";
+ loadedDlmDictionary.reset();
+ }
+
+ if (attachedDlmDictionary) {
+ detachDictionary(attachedDlmDictionary);
+ attachedDlmDictionary.reset();
+ }
+ }
+
+ void dlmAddWord(const QString &word)
+ {
+ if (!isDlmActive() || !worker)
+ return;
+
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::dlmAddWord()";
+
+ QSharedPointer<T9WriteDlmWordTask> learnWordTask(new T9WriteDlmWordTask(loadedDlmDictionary, word, stringStart));
+ worker->addTask(learnWordTask);
+ }
+
+ bool dlmHasWord(const QString &word)
+ {
+ if (!isDlmActive() || !worker)
+ return false;
+
+ worker->waitForAllTasks();
+
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(dictionaryLock);
+ T9WriteDynamicDictionary *dictionary = static_cast<T9WriteDynamicDictionary *>(loadedDlmDictionary.data());
+
+ return dictionary->hasWord(word);
+ }
+
+ void dlmRemoveWord(const QString &word)
+ {
+ qCDebug(lcT9Write) << "T9WriteInputMethodPrivate::dlmRemoveWord()";
+
+ QSharedPointer<T9WriteDlmRemoveWordTask> removeWordTask(new T9WriteDlmRemoveWordTask(loadedDlmDictionary, word));
+ worker->addTask(removeWordTask);
+ }
+
+ T9WriteInputMethod *q_ptr;
+ static const DECUMA_MEM_FUNCTIONS memFuncs;
+ bool cjk;
+ T9WriteInputMethod::EngineMode engineMode;
+ QByteArray currentContext;
+ DECUMA_SESSION_SETTINGS sessionSettings;
+ DECUMA_UINT32 gestureWidthThreshold;
+ QStringList defaultHwrDbPaths;
+ QFile hwrDbFile;
+ QList<DECUMA_UINT32> languageCategories;
+ QList<DECUMA_UINT32> symbolCategories;
+ QScopedPointer<T9WriteWorker> worker;
+ QList<QVirtualKeyboardTrace *> traceList;
+ int traceListHardLimit;
+ QRecursiveMutex dictionaryLock;
+ QString dictionaryFileName;
+ QSharedPointer<T9WriteAbstractDictionary> loadedDictionary;
+ QSharedPointer<T9WriteAbstractDictionary> attachedDictionary;
+ QSharedPointer<Xt9LdbManager> ldbManager;
+ QSharedPointer<T9WriteDictionaryTask> dictionaryTask;
+ QMap<QString, QSharedPointer<T9WriteAbstractDictionary>> dynamicDictionaries;
+ QMap<QString, QSharedPointer<T9WriteAbstractDictionary>> attachedDynamicDictionaries;
+ QSharedPointer<T9WriteAbstractDictionary> loadedDlmDictionary;
+ QSharedPointer<T9WriteAbstractDictionary> attachedDlmDictionary;
+ QMetaObject::Connection availableDictionariesChangedConnection;
+ QMetaObject::Connection activeDictionariesChangedConnection;
+ QSharedPointer<T9WriteRecognitionTask> recognitionTask;
+ QRecursiveMutex resultListLock;
+ QVariantList resultList;
+ QMetaObject::Connection resultListChangedConnection;
+ int resultId;
+ int lastResultId;
+ int resultTimer;
+ QByteArray session;
+ DECUMA_SESSION *decumaSession;
+ QList<T9WriteWordCandidate> wordCandidates;
+ QString stringStart;
+ QString scrResult;
+ int activeWordIndex;
+ bool arcAdditionStarted;
+ bool ignoreUpdate;
+ QVirtualKeyboardInputEngine::TextCase textCase;
+ T9WriteCaseFormatter caseFormatter;
+ HandwritingGestureRecognizer gestureRecognizer;
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ QScopedPointer<UnipenTrace> unipenTrace;
+#endif
+#ifdef HAVE_XT9
+ QScopedPointer<Xt9Ime> xt9Ime;
+ QMap<QString, quint16> xt9DynamicDictionaries;
+ QMap<QString, quint16> xt9AttachedDynamicDictionaries;
+ QAtomicInt xt9DynamicDictionaryNextId;
+ QMetaObject::Connection defaultInputMethodDisabledChangedConnection;
+ QMetaObject::Connection defaultDictionaryDisabledChangedConnection;
+#endif
+ QMetaObject::Connection userDataResetConnection;
+};
+
+const DECUMA_MEM_FUNCTIONS T9WriteInputMethodPrivate::memFuncs = {
+ T9WriteInputMethodPrivate::decumaMalloc,
+ T9WriteInputMethodPrivate::decumaCalloc,
+ T9WriteInputMethodPrivate::decumaFree,
+ nullptr
+};
+
+/*!
+ \class QtVirtualKeyboard::T9WriteInputMethod
+ \internal
+*/
+
+T9WriteInputMethod::T9WriteInputMethod(QObject *parent) :
+ QVirtualKeyboardAbstractInputMethod(*new T9WriteInputMethodPrivate(this), parent)
+{
+}
+
+T9WriteInputMethod::~T9WriteInputMethod()
+{
+}
+
+void T9WriteInputMethod::clearInputMode()
+{
+ Q_D(T9WriteInputMethod);
+ d->exitEngine();
+}
+
+QList<QVirtualKeyboardInputEngine::InputMode> T9WriteInputMethod::inputModes(const QString &locale)
+{
+ Q_D(T9WriteInputMethod);
+ QList<QVirtualKeyboardInputEngine::InputMode> availableInputModes;
+ const Qt::InputMethodHints inputMethodHints(inputContext()->inputMethodHints());
+ const QLocale loc(locale);
+ T9WriteInputMethod::EngineMode mode = d->mapLocaleToEngineMode(loc);
+
+ // Add primary input mode
+ switch (mode) {
+#ifdef HAVE_CERENCE_HWR_ALPHABETIC
+ case T9WriteInputMethod::EngineMode::Alphabetic:
+ if (d->findHwrDb(T9WriteInputMethod::EngineMode::Alphabetic).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly))) {
+ switch (loc.script()) {
+ case QLocale::GreekScript:
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Greek);
+ break;
+ case QLocale::CyrillicScript:
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Cyrillic);
+ break;
+ case QLocale::ThaiScript:
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Thai);
+ break;
+ default:
+ break;
+ }
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Latin);
+ }
+ break;
+ case T9WriteInputMethod::EngineMode::Arabic:
+ if (d->findHwrDb(T9WriteInputMethod::EngineMode::Arabic).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly)))
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Arabic);
+ break;
+ case T9WriteInputMethod::EngineMode::Hebrew:
+ if (d->findHwrDb(T9WriteInputMethod::EngineMode::Hebrew).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly)))
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Hebrew);
+ break;
+ case T9WriteInputMethod::EngineMode::Thai:
+ if (d->findHwrDb(T9WriteInputMethod::EngineMode::Thai).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly)))
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Thai);
+ break;
+#endif
+#ifdef HAVE_CERENCE_HWR_CJK
+ case T9WriteInputMethod::EngineMode::SimplifiedChinese:
+ case T9WriteInputMethod::EngineMode::TraditionalChinese:
+ case T9WriteInputMethod::EngineMode::HongKongChinese:
+ if (d->findHwrDb(mode).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly)))
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting);
+ break;
+ case T9WriteInputMethod::EngineMode::Japanese:
+ if (d->findHwrDb(T9WriteInputMethod::EngineMode::Japanese).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly)))
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting);
+ break;
+ case T9WriteInputMethod::EngineMode::Korean:
+ if (d->findHwrDb(T9WriteInputMethod::EngineMode::Korean).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly)))
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting);
+ break;
+#endif
+ default:
+ return availableInputModes;
+ }
+
+ // Add exclusive input modes
+ if (inputMethodHints.testFlag(Qt::ImhDialableCharactersOnly) || inputMethodHints.testFlag(Qt::ImhDigitsOnly)) {
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Dialable);
+ } else if (inputMethodHints.testFlag(Qt::ImhFormattedNumbersOnly)) {
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Numeric);
+ } else if (inputMethodHints.testFlag(Qt::ImhLatinOnly)) {
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Latin);
+ } else {
+ // Add other input modes
+ Q_ASSERT(!availableInputModes.isEmpty());
+ if (!availableInputModes.contains(QVirtualKeyboardInputEngine::InputMode::Latin))
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Latin);
+ availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Numeric);
+ }
+
+ return availableInputModes;
+}
+
+bool T9WriteInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
+{
+ Q_D(T9WriteInputMethod);
+ d->select();
+ return d->setInputMode(QLocale(locale), inputMode);
+}
+
+bool T9WriteInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase)
+{
+ Q_D(T9WriteInputMethod);
+ d->textCase = textCase;
+ return true;
+}
+
+bool T9WriteInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
+{
+ Q_UNUSED(modifiers);
+ Q_D(T9WriteInputMethod);
+ switch (key) {
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ case Qt::Key_Tab:
+ case Qt::Key_Space:
+ d->select();
+ update();
+ break;
+
+ case Qt::Key_Backspace:
+ {
+ QVirtualKeyboardInputContext *ic = inputContext();
+ QString preeditText = ic->preeditText();
+ if (preeditText.length() > 1) {
+ preeditText.chop(1);
+ ic->setPreeditText(preeditText);
+ d->caseFormatter.ensureLength(preeditText.length(), d->textCase);
+ T9WriteCaseFormatter caseFormatter(d->caseFormatter);
+ d->finishRecognition(false);
+ d->caseFormatter = caseFormatter;
+ d->stringStart = preeditText;
+ int xt9DefaultListIndex = 0;
+ d->activeWordIndex = 0;
+ d->appendWordCandidates(d->wordCandidates, d->activeWordIndex, d->stringStart, d->xt9BuildSelectionList(d->stringStart, &xt9DefaultListIndex), xt9DefaultListIndex, T9WriteWordCandidate::Origin::XT9);
+ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->activeWordIndex);
+ return true;
+ } else {
+ bool result = !preeditText.isEmpty();
+ if (result)
+ ic->clear();
+ else
+ result = !d->scrResult.isEmpty();
+ d->finishRecognition();
+ return result;
+ }
+ }
+
+ default:
+ if (d->sessionSettings.recognitionMode != scrMode && text.length() > 0) {
+ d->waitForRecognitionResults();
+ QVirtualKeyboardInputContext *ic = inputContext();
+ QString preeditText = ic->preeditText();
+ QChar c = text.at(0);
+ bool addToWord = d->isValidInputChar(c) && (!preeditText.isEmpty() || !d->isJoiner(c));
+ if (addToWord) {
+ preeditText.append(text);
+ ic->setPreeditText(preeditText);
+ d->caseFormatter.ensureLength(preeditText.length(), d->textCase);
+ T9WriteCaseFormatter caseFormatter(d->caseFormatter);
+ d->finishRecognition(false);
+ d->caseFormatter = caseFormatter;
+ d->stringStart = preeditText;
+ d->wordCandidates.append(preeditText);
+ d->activeWordIndex = 0;
+ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->activeWordIndex);
+ return true;
+ } else {
+ ic->commit();
+ d->finishRecognition();
+ }
+ break;
+ } else if (d->sessionSettings.recognitionMode == scrMode) {
+ d->finishRecognition();
+ }
+ }
+ return false;
+}
+
+void T9WriteInputMethod::reset()
+{
+ Q_D(T9WriteInputMethod);
+ d->finishRecognition();
+ d->setInputMode(QLocale(inputContext()->locale()), inputEngine()->inputMode());
+}
+
+void T9WriteInputMethod::update()
+{
+ Q_D(T9WriteInputMethod);
+ if (d->ignoreUpdate)
+ return;
+ d->select();
+}
+
+QList<QVirtualKeyboardSelectionListModel::Type> T9WriteInputMethod::selectionLists()
+{
+ return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList;
+}
+
+int T9WriteInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type)
+{
+ Q_UNUSED(type);
+ Q_D(T9WriteInputMethod);
+ return d->wordCandidates.size();
+}
+
+QVariant T9WriteInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role)
+{
+ QVariant result;
+ Q_D(T9WriteInputMethod);
+ switch (role) {
+ case QVirtualKeyboardSelectionListModel::Role::Display:
+ result = QVariant(d->wordCandidates.at(index).symbs);
+ break;
+ case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength:
+ result.setValue(0);
+ break;
+ case QVirtualKeyboardSelectionListModel::Role::Dictionary:
+ {
+ QVirtualKeyboardSelectionListModel::DictionaryType dictionaryType =
+ d->dlmHasWord(d->wordCandidates.at(index).symbs) ?
+ QVirtualKeyboardSelectionListModel::DictionaryType::User :
+ QVirtualKeyboardSelectionListModel::DictionaryType::Default;
+ result = QVariant(static_cast<int>(dictionaryType));
+ break;
+ }
+ case QVirtualKeyboardSelectionListModel::Role::CanRemoveSuggestion:
+ result = QVariant(d->dlmHasWord(d->wordCandidates.at(index).symbs));
+ break;
+ default:
+ result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role);
+ break;
+ }
+ return result;
+}
+
+void T9WriteInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index)
+{
+ Q_UNUSED(type);
+ Q_D(T9WriteInputMethod);
+
+#ifdef HAVE_XT9
+ switch (d->engineMode) {
+ case T9WriteInputMethod::EngineMode::SimplifiedChinese:
+ if (inputEngine()->inputMode() != QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting)
+ break;
+
+ [[fallthrough]]; case T9WriteInputMethod::EngineMode::Japanese:
+ if (d->wordCandidates[index].origin == T9WriteWordCandidate::Origin::T9Write) {
+ if (d->xt9AllSymbsArePinyin(d->wordCandidates[index].symbs)) {
+ int indexOfFirstXt9Candidate;
+ for (indexOfFirstXt9Candidate = 0; indexOfFirstXt9Candidate < d->wordCandidates.size(); ++indexOfFirstXt9Candidate) {
+ if (d->wordCandidates[indexOfFirstXt9Candidate].origin == T9WriteWordCandidate::Origin::XT9)
+ break;
+ }
+
+ while (indexOfFirstXt9Candidate < d->wordCandidates.size()) {
+ d->wordCandidates.removeAt(indexOfFirstXt9Candidate);
+ }
+
+ int xt9DefaultListIndex = 0;
+ d->appendWordCandidates(d->wordCandidates, d->activeWordIndex, d->wordCandidates[index].symbs, d->xt9BuildSelectionList(d->wordCandidates[index].symbs, &xt9DefaultListIndex), xt9DefaultListIndex, T9WriteWordCandidate::Origin::XT9);
+ d->activeWordIndex = index;
+
+ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->activeWordIndex);
+ return;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+#endif
+
+ d->select(index);
+}
+
+bool T9WriteInputMethod::selectionListRemoveItem(QVirtualKeyboardSelectionListModel::Type type, int index)
+{
+ Q_UNUSED(type)
+ Q_D(T9WriteInputMethod);
+ if (index < 0 || index >= d->wordCandidates.size())
+ return false;
+ d->dlmRemoveWord(d->wordCandidates.at(index).symbs);
+ if (d->wordCandidates.size() > 1) {
+ d->wordCandidates.removeAt(index);
+ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->activeWordIndex);
+ } else {
+ inputContext()->clear();
+ reset();
+ }
+ return true;
+}
+
+QList<QVirtualKeyboardInputEngine::PatternRecognitionMode> T9WriteInputMethod::patternRecognitionModes() const
+{
+ return QList<QVirtualKeyboardInputEngine::PatternRecognitionMode>()
+ << QVirtualKeyboardInputEngine::PatternRecognitionMode::Handwriting;
+}
+
+QVirtualKeyboardTrace *T9WriteInputMethod::traceBegin(
+ int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode,
+ const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo)
+{
+ Q_D(T9WriteInputMethod);
+ return d->traceBegin(traceId, patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo);
+}
+
+bool T9WriteInputMethod::traceEnd(QVirtualKeyboardTrace *trace)
+{
+ Q_D(T9WriteInputMethod);
+ d->traceEnd(trace);
+ return true;
+}
+
+bool T9WriteInputMethod::reselect(int cursorPosition, const QVirtualKeyboardInputEngine::ReselectFlags &reselectFlags)
+{
+ Q_D(T9WriteInputMethod);
+
+ if (d->sessionSettings.recognitionMode == scrMode)
+ return false;
+
+ QVirtualKeyboardInputContext *ic = inputContext();
+ if (!ic)
+ return false;
+
+ const QVirtualKeyboardInputEngine::InputMode inputMode = inputEngine()->inputMode();
+ const int maxLength = (inputMode == QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting ||
+ inputMode == QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting ||
+ inputMode == QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting) ? 16 : 32;
+ const QString surroundingText = ic->surroundingText();
+ int replaceFrom = 0;
+
+ if (cursorPosition > surroundingText.length())
+ return false;
+
+ if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordBeforeCursor)) {
+ for (int i = cursorPosition - 1; i >= 0 && d->stringStart.length() < maxLength; --i) {
+ QChar c = surroundingText.at(i);
+ if (!d->isValidInputChar(c))
+ break;
+ d->stringStart.insert(0, c);
+ --replaceFrom;
+ }
+
+ while (replaceFrom < 0 && d->isJoiner(d->stringStart.at(0))) {
+ d->stringStart.remove(0, 1);
+ ++replaceFrom;
+ }
+ }
+
+ if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor) && replaceFrom == 0) {
+ d->stringStart.clear();
+ return false;
+ }
+
+ if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAfterCursor)) {
+ for (int i = cursorPosition; i < surroundingText.length() && d->stringStart.length() < maxLength; ++i) {
+ QChar c = surroundingText.at(i);
+ if (!d->isValidInputChar(c))
+ break;
+ d->stringStart.append(c);
+ }
+
+ while (replaceFrom > -d->stringStart.length()) {
+ int lastPos = d->stringStart.length() - 1;
+ if (!d->isJoiner(d->stringStart.at(lastPos)))
+ break;
+ d->stringStart.remove(lastPos, 1);
+ }
+ }
+
+ if (d->stringStart.isEmpty())
+ return false;
+
+ if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor) && replaceFrom == -d->stringStart.length() && d->stringStart.length() < maxLength) {
+ d->stringStart.clear();
+ return false;
+ }
+
+ if (d->isJoiner(d->stringStart.at(0))) {
+ d->stringStart.clear();
+ return false;
+ }
+
+ if (d->isJoiner(d->stringStart.at(d->stringStart.length() - 1))) {
+ d->stringStart.clear();
+ return false;
+ }
+
+ ic->setPreeditText(d->stringStart, QList<QInputMethodEvent::Attribute>(), replaceFrom, d->stringStart.length());
+ for (int i = 0; i < d->stringStart.length(); ++i)
+ d->caseFormatter.ensureLength(i + 1, d->stringStart.at(i).isUpper() ? QVirtualKeyboardInputEngine::TextCase::Upper : QVirtualKeyboardInputEngine::TextCase::Lower);
+ int xt9DefaultListIndex = 0;
+ d->activeWordIndex = 0;
+ d->appendWordCandidates(d->wordCandidates, d->activeWordIndex, d->stringStart, d->xt9BuildSelectionList(d->stringStart, &xt9DefaultListIndex), xt9DefaultListIndex, T9WriteWordCandidate::Origin::XT9);
+ emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->activeWordIndex);
+
+ return true;
+}
+
+void T9WriteInputMethod::timerEvent(QTimerEvent *timerEvent)
+{
+ Q_D(T9WriteInputMethod);
+ int timerId = timerEvent->timerId();
+ qCDebug(lcT9Write) << "T9WriteInputMethod::timerEvent():" << timerId;
+ if (timerId == d->resultTimer) {
+ d->stopResultTimer();
+
+ // Ignore if the result is not yet available
+ if (d->resultId != d->lastResultId) {
+ qCDebug(lcT9Write) << "T9WriteInputMethod::timerEvent(): Result not yet available";
+ return;
+ }
+
+ if (d->sessionSettings.recognitionMode != scrMode) {
+#ifndef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ // Don't clear traces in UCR mode if dictionary is loaded.
+ // In UCR mode the whole purpose is to write the word with
+ // one or few strokes.
+ if (d->sessionSettings.recognitionMode == ucrMode) {
+ const std::lock_guard<QRecursiveMutex> dictionaryGuard(d->dictionaryLock);
+ if (d->attachedDictionary)
+ return;
+ }
+
+ const QVirtualKeyboardInputEngine::InputMode inputMode = inputEngine()->inputMode();
+ if (inputMode != QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting &&
+ inputMode != QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting) {
+ d->clearTraces();
+ }
+#endif
+ } else {
+ d->select();
+ }
+ }
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE