aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/cerence
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/cerence')
-rw-r--r--src/plugins/cerence/.gitignore1
-rw-r--r--src/plugins/cerence/CMakeLists.txt24
-rw-r--r--src/plugins/cerence/cerencecommon/CMakeLists.txt93
-rw-r--r--src/plugins/cerence/cerencecommon/dummy.txt0
-rw-r--r--src/plugins/cerence/cerencecommon/xt9dbfile.cpp72
-rw-r--r--src/plugins/cerence/cerencecommon/xt9dbfile.h32
-rw-r--r--src/plugins/cerence/cerencecommon/xt9ldbmanager.cpp167
-rw-r--r--src/plugins/cerence/cerencecommon/xt9ldbmanager.h41
-rw-r--r--src/plugins/cerence/hwr/CMakeLists.txt6
-rw-r--r--src/plugins/cerence/hwr/plugin/CMakeLists.txt419
-rw-r--r--src/plugins/cerence/hwr/plugin/cerence_hwr_p.h36
-rw-r--r--src/plugins/cerence/hwr/plugin/content/layouts/zh_HK/handwriting.qml73
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writeabstractdictionary_p.h50
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writedictionary.cpp281
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writedictionary_p.h122
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writeinputmethod.cpp2824
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writeinputmethod_p.h86
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writewordcandidate.cpp23
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writewordcandidate_p.h45
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writeworker.cpp472
-rw-r--r--src/plugins/cerence/hwr/plugin/t9writeworker_p.h230
-rw-r--r--src/plugins/cerence/unpack.py386
-rw-r--r--src/plugins/cerence/xt9/CMakeLists.txt2
-rw-r--r--src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/ja_JP/main.qml673
-rw-r--r--src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/zh_CN/main.qml165
-rw-r--r--src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/zh_TW/main.qml161
-rw-r--r--src/plugins/cerence/xt9/plugin/CMakeLists.txt172
-rw-r--r--src/plugins/cerence/xt9/plugin/content/layouts/ja_JP/main.qml927
-rw-r--r--src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/dialpad.fallback0
-rw-r--r--src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/digits.fallback0
-rw-r--r--src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/main.qml168
-rw-r--r--src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/numbers.fallback0
-rw-r--r--src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/symbols.qml476
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9awinputmethod.cpp303
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9awinputmethod_p.h48
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9awinputmethodprivate.cpp147
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9awinputmethodprivate_p.h56
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9cpinputmethod.cpp165
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9cpinputmethod_p.h58
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9cpinputmethodprivate.cpp134
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9cpinputmethodprivate_p.h44
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9inputmethod.cpp30
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9inputmethod_p.h28
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9inputmethodprivate.cpp603
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9inputmethodprivate_p.h119
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9jinputmethod.cpp104
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9jinputmethod_p.h39
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9jinputmethodprivate.cpp116
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9jinputmethodprivate_p.h44
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9kinputmethod.cpp44
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9kinputmethod_p.h32
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9kinputmethodprivate.cpp28
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9kinputmethodprivate_p.h37
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9thaiinputmethod.cpp32
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9thaiinputmethod_p.h30
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9thaiinputmethodprivate.cpp15
-rw-r--r--src/plugins/cerence/xt9/plugin/xt9thaiinputmethodprivate_p.h23
-rw-r--r--src/plugins/cerence/xt9/xt9common/CMakeLists.txt144
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9awime.cpp112
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9awime.h39
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9callbacks.h21
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9cpime.cpp284
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9cpime.h47
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9ime.cpp304
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9ime.h108
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9jime.cpp42
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9jime.h21
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdb.cpp25
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdb.h23
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdbarea.cpp21
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdbarea.h25
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdbelement.cpp15
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdbelement.h23
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdbkey.cpp100
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdbkey.h87
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdblayout.cpp40
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kdblayout.h31
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9keyboardgenerator.cpp141
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9keyboardgenerator.h58
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kime.cpp48
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9kime.h27
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9languagemap.cpp224
-rw-r--r--src/plugins/cerence/xt9/xt9common/xt9languagemap.h40
83 files changed, 12556 insertions, 0 deletions
diff --git a/src/plugins/cerence/.gitignore b/src/plugins/cerence/.gitignore
new file mode 100644
index 00000000..5a435361
--- /dev/null
+++ b/src/plugins/cerence/.gitignore
@@ -0,0 +1 @@
+sdk
diff --git a/src/plugins/cerence/CMakeLists.txt b/src/plugins/cerence/CMakeLists.txt
new file mode 100644
index 00000000..ba38d2cb
--- /dev/null
+++ b/src/plugins/cerence/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+set(cerence_imports)
+
+add_subdirectory(cerencecommon)
+if(QT_FEATURE_cerence_hwr)
+ add_subdirectory(hwr)
+ list(APPEND cerence_imports QtQuick.VirtualKeyboard.Plugins.Cerence.HWR/auto)
+endif()
+if(QT_FEATURE_cerence_xt9)
+ add_subdirectory(xt9)
+ list(APPEND cerence_imports QtQuick.VirtualKeyboard.Plugins.Cerence.XT9/auto)
+endif()
+
+qt_internal_add_qml_module(qtvkbcerenceplugin
+ URI "QtQuick.VirtualKeyboard.Plugins.Cerence"
+ VERSION "${PROJECT_VERSION}"
+ PAST_MAJOR_VERSIONS 2
+ PLUGIN_TARGET qtvkbcerenceplugin
+ IMPORTS
+ ${cerence_imports}
+ NO_GENERATE_CPP_EXPORTS
+)
diff --git a/src/plugins/cerence/cerencecommon/CMakeLists.txt b/src/plugins/cerence/cerencecommon/CMakeLists.txt
new file mode 100644
index 00000000..4ebbf4fb
--- /dev/null
+++ b/src/plugins/cerence/cerencecommon/CMakeLists.txt
@@ -0,0 +1,93 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## BundledCerencecommon Generic Library:
+#####################################################################
+
+qt_internal_add_3rdparty_library(BundledCerencecommon
+ QMAKE_LIB_NAME cerencecommon
+ STATIC
+ SOURCES
+ xt9dbfile.cpp xt9dbfile.h
+ xt9ldbmanager.cpp xt9ldbmanager.h
+ PUBLIC_INCLUDE_DIRECTORIES
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::VirtualKeyboardPrivate
+)
+
+qt_internal_extend_target(BundledCerencecommon CONDITION FEATURE_vkb_bundle_cerence OR FEATURE_vkb_bundle_cerence_xt9
+ PUBLIC_DEFINES
+ HAVE_XT9_RESOURCE
+)
+
+qt_internal_extend_target(BundledCerencecommon CONDITION FEATURE_vkb_cerence_xt9_debug
+ PUBLIC_DEFINES
+ XT9_DEBUG
+)
+
+if(FEATURE_vkb_bundle_cerence OR FEATURE_vkb_bundle_cerence_xt9)
+
+ set(qmake_cerencecommondata_db_resource_files)
+
+ foreach(lang IN LISTS valid_languages)
+ if(FEATURE_vkb_lang_${lang})
+ if(${lang} STREQUAL "en_GB")
+ set(ldb_glob "EN*UK*.ldb")
+ elseif(${lang} STREQUAL "en_US")
+ set(ldb_glob "EN*US*.ldb")
+ elseif(${lang} STREQUAL "es_ES")
+ set(ldb_glob "ESusUN_*.ldb")
+ elseif(${lang} STREQUAL "es_MX")
+ set(ldb_glob "ESusUNlatam_*.ldb")
+ elseif(${lang} STREQUAL "fr_FR")
+ set(ldb_glob "FRusUN_*.ldb")
+ elseif(${lang} STREQUAL "fr_CA")
+ set(ldb_glob "FRusUNCA_*.ldb")
+ elseif(${lang} STREQUAL "ko_KR")
+ set(ldb_glob "KOusUN_xt9_ALM3.ldb")
+ elseif(${lang} STREQUAL "pt_PT")
+ set(ldb_glob "PTusUN_*.ldb")
+ elseif(${lang} STREQUAL "pt_BR")
+ set(ldb_glob "PTusUNBR_*.ldb")
+ elseif(${lang} STREQUAL "zh_CN")
+ set(ldb_glob "ZHsbUNps_GB18030_*.ldb")
+ elseif(${lang} STREQUAL "zh_TW")
+ set(ldb_glob "ZHtbUNps_Big5_*.ldb")
+ elseif(${lang} STREQUAL "zh_HK")
+ set(ldb_glob "ZHtbUNps_Big5HKSCS_*.ldb")
+ else()
+ string(SUBSTRING ${lang} 0 2 lang_code)
+ string(TOUPPER lang_code ${lang_code})
+ set(ldb_glob "${lang_code}*.ldb")
+ endif()
+ file(GLOB resource_glob_0 RELATIVE "${CERENCE_XT9_DATAPATH}" "${CERENCE_XT9_DATAPATH}/${ldb_glob}")
+ foreach(file IN LISTS resource_glob_0)
+ set_source_files_properties("${CERENCE_XT9_DATAPATH}/${file}" PROPERTIES QT_RESOURCE_ALIAS "${file}")
+ endforeach()
+ list(APPEND qmake_cerencecommondata_db_resource_files ${resource_glob_0})
+ endif()
+ endforeach()
+
+ list(REMOVE_DUPLICATES qmake_cerencecommondata_db_resource_files)
+
+ qt_internal_add_resource(BundledCerencecommon "qmake_cerencecommondata_db"
+ PREFIX
+ "/qt-project.org/imports/QtQuick/VirtualKeyboard/Cerence/Xt9"
+ BASE
+ "${CERENCE_XT9_DATAPATH}"
+ FILES
+ ${qmake_cerencecommondata_db_resource_files}
+ OPTIONS
+ -no-compress
+ )
+else()
+ qt_copy_or_install(
+ DIRECTORY "${CERENCE_XT9_DATAPATH}/"
+ DESTINATION "${VKB_INSTALL_DATA}/cerence/xt9"
+ FILES_MATCHING
+ PATTERN "*.ldb"
+ )
+endif()
diff --git a/src/plugins/cerence/cerencecommon/dummy.txt b/src/plugins/cerence/cerencecommon/dummy.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/plugins/cerence/cerencecommon/dummy.txt
diff --git a/src/plugins/cerence/cerencecommon/xt9dbfile.cpp b/src/plugins/cerence/cerencecommon/xt9dbfile.cpp
new file mode 100644
index 00000000..a4080beb
--- /dev/null
+++ b/src/plugins/cerence/cerencecommon/xt9dbfile.cpp
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9dbfile.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9DbFile::Xt9DbFile(const QString &fileName) :
+ file(fileName),
+ _data(nullptr),
+ _size(0),
+ _rw(false)
+{
+
+}
+
+QString Xt9DbFile::fileName() const
+{
+ return file.fileName();
+}
+
+const void *Xt9DbFile::roData()
+{
+ if (_data == nullptr) {
+ if (file.open(QIODevice::ReadOnly)) {
+ _rw = false;
+ _size = file.size();
+ _data = file.map(0, _size, QFile::NoOptions);
+ if (!_data) {
+ _size = 0;
+ }
+ file.close();
+ }
+ }
+
+ return _data;
+}
+
+void *Xt9DbFile::rwData(qint64 size)
+{
+ if (_data == nullptr || !_rw) {
+ if (!_rw) {
+ file.unmap(static_cast<uchar *>(_data));
+ _data = nullptr;
+ _rw = false;
+ }
+ if (file.open(QIODevice::ReadWrite)) {
+ _rw = true;
+ _size = file.size();
+ if (_size == 0) {
+ _size = size;
+ file.resize(_size);
+ }
+ _data = file.map(0, _size, QFile::NoOptions);
+ if (!_data) {
+ _size = 0;
+ }
+ file.close();
+ }
+ }
+
+ return _data;
+}
+
+qint64 Xt9DbFile::size() const
+{
+ return _size;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/cerencecommon/xt9dbfile.h b/src/plugins/cerence/cerencecommon/xt9dbfile.h
new file mode 100644
index 00000000..a99aefac
--- /dev/null
+++ b/src/plugins/cerence/cerencecommon/xt9dbfile.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9DBFILE_H
+#define XT9DBFILE_H
+
+#include <QFile>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9DbFile
+{
+public:
+ Xt9DbFile(const QString &fileName);
+
+ QString fileName() const;
+ const void *roData();
+ void *rwData(qint64 size);
+ qint64 size() const;
+
+private:
+ QFile file;
+ void *_data;
+ qint64 _size;
+ bool _rw;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9DBFILE_H
diff --git a/src/plugins/cerence/cerencecommon/xt9ldbmanager.cpp b/src/plugins/cerence/cerencecommon/xt9ldbmanager.cpp
new file mode 100644
index 00000000..6aa7e29a
--- /dev/null
+++ b/src/plugins/cerence/cerencecommon/xt9ldbmanager.cpp
@@ -0,0 +1,167 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9ldbmanager.h"
+#include "xt9dbfile.h"
+#include <QDirIterator>
+#include <QLibraryInfo>
+#include <QMap>
+#include <QtVirtualKeyboard/private/settings_p.h>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+const QRegularExpression Xt9LdbManager::ldbRegex = QRegularExpression(QStringLiteral("([A-Z]{2})([a-z]{2})(?:UN)(.*?)(?:_xt9_?)?((?:CJI_|HC_|big_)?ALM3?)?(\\.ldb|\\.phd)"));
+
+Xt9LdbManager::Xt9LdbManager() :
+ _phdEnabled(false)
+{
+ QString xt9LdbPath(qEnvironmentVariable("QT_VIRTUALKEYBOARD_XT9_LDB_PATH"));
+ const QString pathListSep(
+#if defined(Q_OS_WIN32)
+ QStringLiteral(";")
+#else
+ QStringLiteral(":")
+#endif
+ );
+ QStringList userPaths(xt9LdbPath.split(pathListSep, Qt::SkipEmptyParts));
+ const QStringList defaultPaths = userPaths
+ << QDir(QStringLiteral("%1/qtvirtualkeyboard/cerence/xt9/").arg(QLibraryInfo::path(QLibraryInfo::DataPath))).absolutePath()
+ << QLatin1String(":/qt-project.org/imports/QtQuick/VirtualKeyboard/Cerence/Xt9")
+ ;
+ for (const QString &defaultPath : defaultPaths) {
+ addSearchPath(defaultPath);
+ }
+}
+
+void Xt9LdbManager::addSearchPath(const QString &searchPath)
+{
+ if (!searchPaths.contains(searchPath))
+ searchPaths.append(searchPath);
+}
+
+void Xt9LdbManager::setPhdEnabled(bool enabled)
+{
+ _phdEnabled = enabled;
+}
+
+bool Xt9LdbManager::phdEnabled() const
+{
+ return _phdEnabled;
+}
+
+bool Xt9LdbManager::loadDictionary(const QLocale &locale, const void *&data, qint64 &size)
+{
+ const QString language = locale.name();
+ if (ldbMap.contains(language)) {
+ QSharedPointer<Xt9DbFile> ldb = ldbMap[language];
+ if (_phdEnabled || !ldb->fileName().endsWith(QLatin1String(".phd"), Qt::CaseInsensitive)) {
+ data = ldb->roData();
+ size = ldb->size();
+ return true;
+ }
+ }
+
+ QString dictionaryFile = findDictionary(locale);
+ if (!dictionaryFile.isEmpty()) {
+ QSharedPointer<Xt9DbFile> ldb(new Xt9DbFile(dictionaryFile));
+ data = ldb->roData();
+ size = ldb->size();
+ if (data) {
+ ldbMap[language] = ldb;
+ return true;
+ }
+ }
+
+ data = nullptr;
+ size = 0;
+
+ return false;
+}
+
+void Xt9LdbManager::closeAll()
+{
+ ldbMap.clear();
+}
+
+QString Xt9LdbManager::findDictionary(const QLocale &locale) const
+{
+ QStringList languageCountry = locale.name().split(QLatin1String("_"));
+ if (languageCountry.size() != 2)
+ return QString();
+ const QString language_ISO_639_1 = languageCountry[0].toUpper();
+ const QString country = languageCountry[1].toUpper();
+ const QLocale::Script script = locale.script();
+
+ QMap<QString, int> matchedDictionaries;
+ for (const QString &ldbDirectory : searchPaths) {
+ QDirIterator it(ldbDirectory, QDirIterator::NoIteratorFlags);
+ while (it.hasNext()) {
+ QString fileEntry = it.next();
+ const QFileInfo fileInfo(fileEntry);
+
+ if (fileInfo.isDir())
+ continue;
+
+ QString fileName(fileInfo.fileName());
+ fileName.remove(QRegularExpression(QLatin1String("^zzEval_")));
+
+ QRegularExpressionMatch match = ldbRegex.match(fileName);
+ if (!match.hasMatch())
+ continue;
+
+ QString xt9Language = match.captured(1);
+
+ // Special case for language codes not following ISO 639-1
+ if (xt9Language == QLatin1String("NO"))
+ xt9Language = QStringLiteral("NB");
+
+ const QString ext(match.captured(5));
+ if (xt9Language == language_ISO_639_1 &&
+ (ext == QLatin1String(".ldb") || (_phdEnabled && ext == QLatin1String(".phd")))) {
+
+ int score = 1;
+
+ QString xt9CountryOrDetail = match.captured(3);
+ const QString charsetClassifier = match.captured(2);
+ const QString almClassifier = match.captured(4);
+
+ // Special case for country codes not following ISO 639-1
+ if (xt9CountryOrDetail == QLatin1String("UK"))
+ xt9CountryOrDetail = QStringLiteral("GB");
+ else if (xt9CountryOrDetail == QLatin1String("latam"))
+ xt9CountryOrDetail = QStringLiteral("MX");
+ else if (xt9CountryOrDetail.isEmpty() && country == language_ISO_639_1)
+ xt9CountryOrDetail = country;
+
+ if (xt9CountryOrDetail == country ||
+ ((script == QLocale::SimplifiedHanScript && charsetClassifier == QLatin1String("sb")) ||
+ (script == QLocale::TraditionalHanScript && charsetClassifier == QLatin1String("tb"))))
+ ++score;
+
+ if (locale.territory() == QLocale::Taiwan && xt9CountryOrDetail == QLatin1String("ps_Big5_bpmf_pinyin_CJ"))
+ ++score;
+ else if (locale.territory() == QLocale::HongKong && xt9CountryOrDetail == QLatin1String("ps_Big5HKSCS_bpmf_pinyin_CJ"))
+ ++score;
+
+ if (!almClassifier.isEmpty())
+ ++score;
+
+ matchedDictionaries.insert(fileEntry, score);
+ }
+ }
+ }
+
+ if (matchedDictionaries.isEmpty())
+ return QString();
+
+ QList<int> scoreList = matchedDictionaries.values();
+ std::sort(scoreList.begin(), scoreList.end());
+ const int highScore = scoreList.last();
+ const QString bestMatch = matchedDictionaries.key(highScore);
+
+ return bestMatch;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/cerencecommon/xt9ldbmanager.h b/src/plugins/cerence/cerencecommon/xt9ldbmanager.h
new file mode 100644
index 00000000..2d75fc33
--- /dev/null
+++ b/src/plugins/cerence/cerencecommon/xt9ldbmanager.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9LDBMANAGER_H
+#define XT9LDBMANAGER_H
+
+#include <QLocale>
+#include <QSharedPointer>
+#include <QRegularExpression>
+#include <QSet>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9DbFile;
+
+class Xt9LdbManager
+{
+public:
+ Xt9LdbManager();
+
+ void addSearchPath(const QString &ldbDirectory);
+ void setPhdEnabled(bool enabled);
+ bool phdEnabled() const;
+
+ bool loadDictionary(const QLocale &locale, const void *&data, qint64 &size);
+ void closeAll();
+
+ QString findDictionary(const QLocale &locale) const;
+
+private:
+ QStringList searchPaths;
+ QMap<QString, QSharedPointer<Xt9DbFile>> ldbMap;
+ bool _phdEnabled;
+ static const QRegularExpression ldbRegex;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9LDBMANAGER_H
diff --git a/src/plugins/cerence/hwr/CMakeLists.txt b/src/plugins/cerence/hwr/CMakeLists.txt
new file mode 100644
index 00000000..641a943f
--- /dev/null
+++ b/src/plugins/cerence/hwr/CMakeLists.txt
@@ -0,0 +1,6 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from cerence-hwr.pro.
+
+add_subdirectory(plugin)
diff --git a/src/plugins/cerence/hwr/plugin/CMakeLists.txt b/src/plugins/cerence/hwr/plugin/CMakeLists.txt
new file mode 100644
index 00000000..d566c1eb
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/CMakeLists.txt
@@ -0,0 +1,419 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## QtVirtualKeyboardCerenceHwrPlugin Plugin:
+#####################################################################
+
+qt_internal_add_qml_module(qtvkbcerencehwrplugin
+ URI "QtQuick.VirtualKeyboard.Plugins.Cerence.HWR"
+ VERSION "${PROJECT_VERSION}"
+ PAST_MAJOR_VERSIONS 2
+ PLUGIN_TARGET qtvkbcerencehwrplugin
+ NO_PLUGIN_OPTIONAL
+ DEPENDENCIES
+ QtQuick.VirtualKeyboard/auto
+ SOURCES
+ cerence_hwr_p.h
+ t9writeabstractdictionary_p.h
+ t9writedictionary.cpp t9writedictionary_p.h
+ t9writeinputmethod.cpp t9writeinputmethod_p.h
+ t9writewordcandidate.cpp t9writewordcandidate_p.h
+ t9writeworker.cpp t9writeworker_p.h
+ DEFINES
+ HAVE_CERENCE_HWR
+ QT_ASCII_CAST_WARNINGS
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_FROM_BYTEARRAY
+ QT_NO_CAST_TO_ASCII
+ LIBRARIES
+ Qt::BundledCerencecommon
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::VirtualKeyboardPrivate
+ NO_GENERATE_CPP_EXPORTS
+)
+
+# Resources:
+set(qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/fallback/handwriting.qml"
+)
+
+if (QT_FEATURE_vkb_lang_en_GB)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/en_GB/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_en_US)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/en_US/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_ar_AR)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/ar_AR/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_bg_BG)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/bg_BG/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_cs_CZ)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/cs_CZ/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_da_DK)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/da_DK/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_de_DE)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/de_DE/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_el_GR)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/el_GR/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_es_ES)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/es_ES/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_es_MX)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/es_MX/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_et_EE)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/et_EE/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_fa_FA)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/fa_FA/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_fi_FI)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/fi_FI/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_fr_FR)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/fr_FR/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_fr_CA)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/fr_CA/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_he_IL)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/he_IL/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_hr_HR)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/hr_HR/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_hu_HU)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/hu_HU/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_id_ID)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/id_ID/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_it_IT)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/it_IT/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_ms_MY)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/ms_MY/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_nb_NO)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/nb_NO/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_nl_NL)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/nl_NL/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_pl_PL)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/pl_PL/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_pt_BR)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/pt_BR/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_pt_PT)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/pt_PT/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_ro_RO)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/ro_RO/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_ru_RU)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/ru_RU/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_sk_SK)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/sk_SK/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_sl_SI)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/sl_SI/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_sq_AL)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/sq_AL/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_sr_SP)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/sr_SP/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_sv_SE)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/sv_SE/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_th_TH)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/th_TH/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_tr_TR)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/tr_TR/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_uk_UA)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/uk_UA/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_vi_VN)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/vi_VN/handwriting.fallback"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_ja_JP)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/ja_JP/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_ko_KR)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/ko_KR/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_zh_CN)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/zh_CN/handwriting.qml"
+ )
+endif()
+
+if (QT_FEATURE_vkb_lang_zh_TW)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/zh_TW/handwriting.qml"
+ )
+endif()
+
+qt_internal_add_resource(qtvkbcerencehwrplugin "qmake_virtualkeyboard_cerence_hwr_layouts"
+ PREFIX
+ "${VKB_LAYOUTS_PREFIX}"
+ BASE
+ "${VKB_LAYOUTS_BASE}"
+ FILES
+ ${qmake_virtualkeyboard_cerence_hwr_layouts_resource_files}
+)
+
+set(qmake_virtualkeyboard_cerence_hwr_custom_layouts_resource_files)
+if (QT_FEATURE_vkb_lang_zh_HK)
+ list(APPEND qmake_virtualkeyboard_cerence_hwr_custom_layouts_resource_files
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts/zh_HK/handwriting.qml"
+ )
+endif()
+
+qt_internal_add_resource(qtvkbcerencehwrplugin "qmake_virtualkeyboard_cerence_hwr_custom_layouts"
+ PREFIX
+ "${VKB_LAYOUTS_PREFIX}"
+ BASE
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts"
+ FILES
+ ${qmake_virtualkeyboard_cerence_hwr_custom_layouts_resource_files}
+)
+
+qt_internal_extend_target(qtvkbcerencehwrplugin CONDITION QT_FEATURE_cerence_xt9
+ DEFINES
+ HAVE_XT9
+ LIBRARIES
+ Qt::BundledXt9Common
+)
+
+qt_internal_extend_target(qtvkbcerencehwrplugin CONDITION QT_FEATURE_cerence_hwr_alphabetic
+ DEFINES
+ HAVE_CERENCE_HWR_ALPHABETIC
+ LIBRARIES
+ Cerence::HWR::Alphabetic
+)
+
+qt_internal_extend_target(qtvkbcerencehwrplugin CONDITION QT_FEATURE_cerence_hwr_cjk
+ DEFINES
+ HAVE_CERENCE_HWR_CJK
+ LIBRARIES
+ Cerence::HWR::CJK
+)
+
+qt_internal_extend_target(qtvkbcerencehwrplugin CONDITION FEATURE_vkb_bundle_cerence OR FEATURE_vkb_bundle_cerence_hwr
+ DEFINES
+ HAVE_CERENCE_HWR_RESOURCE
+)
+
+if(FEATURE_vkb_bundle_cerence OR FEATURE_vkb_bundle_cerence_hwr)
+ set(resource_glob_0)
+ if(FEATURE_vkb_lang_ar_AR OR FEATURE_vkb_lang_fa_FA)
+ file(GLOB resource_glob_0 RELATIVE "${CERENCE_HWR_DATAPATH}" "${CERENCE_HWR_DATAPATH}/arabic/*.bin")
+ foreach(file IN LISTS resource_glob_0)
+ set_source_files_properties("${CERENCE_HWR_DATAPATH}/${file}" PROPERTIES QT_RESOURCE_ALIAS "${file}")
+ endforeach()
+ endif()
+
+ set(resource_glob_1)
+ if(FEATURE_vkb_lang_he_IL)
+ file(GLOB resource_glob_1 RELATIVE "${CERENCE_HWR_DATAPATH}" "${CERENCE_HWR_DATAPATH}/hebrew/*.bin")
+ foreach(file IN LISTS resource_glob_1)
+ set_source_files_properties("${CERENCE_HWR_DATAPATH}/${file}" PROPERTIES QT_RESOURCE_ALIAS "${file}")
+ endforeach()
+ endif()
+
+ set(resource_glob_2)
+ if(FEATURE_vkb_lang_th_TH)
+ file(GLOB resource_glob_2 RELATIVE "${CERENCE_HWR_DATAPATH}" "${CERENCE_HWR_DATAPATH}/thai/*.bin")
+ foreach(file IN LISTS resource_glob_2)
+ set_source_files_properties("${CERENCE_HWR_DATAPATH}/${file}" PROPERTIES QT_RESOURCE_ALIAS "${file}")
+ endforeach()
+ endif()
+
+ file(GLOB resource_glob_3 RELATIVE "${CERENCE_HWR_DATAPATH}" "${CERENCE_HWR_DATAPATH}/*.bin")
+ foreach(file IN LISTS resource_glob_3)
+ set_source_files_properties("${CERENCE_HWR_DATAPATH}/${file}" PROPERTIES QT_RESOURCE_ALIAS "${file}")
+ endforeach()
+
+ file(GLOB resource_glob_4 RELATIVE "${CERENCE_HWR_DATAPATH}" "${CERENCE_HWR_DATAPATH}/*.hdb")
+ foreach(file IN LISTS resource_glob_4)
+ set_source_files_properties("${CERENCE_HWR_DATAPATH}/${file}" PROPERTIES QT_RESOURCE_ALIAS "${file}")
+ endforeach()
+
+ file(GLOB resource_glob_5 RELATIVE "${CERENCE_HWR_DATAPATH}" "${CERENCE_HWR_DATAPATH}/*.phd")
+ foreach(file IN LISTS resource_glob_5)
+ set_source_files_properties("${CERENCE_HWR_DATAPATH}/${file}" PROPERTIES QT_RESOURCE_ALIAS "${file}")
+ endforeach()
+
+ # Resources:
+ set(qmake_cerencehwrdata_db_resource_files
+ ${resource_glob_0}
+ ${resource_glob_1}
+ ${resource_glob_2}
+ ${resource_glob_3}
+ ${resource_glob_4}
+ ${resource_glob_5}
+ )
+
+ qt_internal_add_resource(qtvkbcerencehwrplugin "qmake_cerencehwrdata_db"
+ PREFIX
+ "/qt-project.org/imports/QtQuick/VirtualKeyboard/Cerence/Handwriting"
+ BASE
+ "${CERENCE_HWR_DATAPATH}"
+ FILES
+ ${qmake_cerencehwrdata_db_resource_files}
+ OPTIONS
+ -no-compress
+ )
+else()
+ qt_copy_or_install(
+ DIRECTORY "${CERENCE_HWR_DATAPATH}/"
+ DESTINATION "${VKB_INSTALL_DATA}/cerence/handwriting"
+ )
+endif()
+
+if(QT_FEATURE_cerence_hwr_alphabetic AND NOT FEATURE_vkb_cerence_static)
+ qt_copy_or_install(
+ FILES "${CERENCE_HWR_ALPHABETIC_BINARIES}"
+ DESTINATION "${QT_BUILD_DIR}/${INSTALL_BINDIR}"
+ )
+endif()
+
+if(QT_FEATURE_cerence_hwr_cjk AND NOT FEATURE_vkb_cerence_static)
+ qt_copy_or_install(
+ FILES "${CERENCE_HWR_CJK_BINARIES}"
+ DESTINATION "${QT_BUILD_DIR}/${INSTALL_BINDIR}"
+ )
+endif()
diff --git a/src/plugins/cerence/hwr/plugin/cerence_hwr_p.h b/src/plugins/cerence/hwr/plugin/cerence_hwr_p.h
new file mode 100644
index 00000000..020ce209
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/cerence_hwr_p.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CERENCE_HWR_P_H
+#define CERENCE_HWR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtVirtualKeyboard/private/qvirtualkeyboard_global_p.h>
+
+#include "cerence_handwriting_api_version.h"
+#if QT_CONFIG(cerence_hwr_alphabetic)
+#include "decuma_hwr.h"
+#endif
+#if QT_CONFIG(cerence_hwr_cjk)
+#include "decuma_hwr_cjk.h"
+#endif
+
+#if QT_CONFIG(cerence_hwr_cjk) && QT_CONFIG(cerence_hwr_alphabetic)
+#define DECUMA_API(FUNC_NAME) (cjk ? decumaCJK ## FUNC_NAME : decumaUcr ## FUNC_NAME)
+#elif QT_CONFIG(cerence_hwr_cjk)
+#define DECUMA_API(FUNC_NAME) (decumaCJK ## FUNC_NAME)
+#else // QT_CONFIG(cerence_hwr_alphabetic)
+#define DECUMA_API(FUNC_NAME) (decumaUcr ## FUNC_NAME)
+#endif
+
+#endif // CERENCE_HWR_P_H
diff --git a/src/plugins/cerence/hwr/plugin/content/layouts/zh_HK/handwriting.qml b/src/plugins/cerence/hwr/plugin/content/layouts/zh_HK/handwriting.qml
new file mode 100644
index 00000000..a2548b89
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/content/layouts/zh_HK/handwriting.qml
@@ -0,0 +1,73 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Components
+
+KeyboardLayout {
+ function createInputMethod() {
+ return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; HandwritingInputMethod {}', parent)
+ }
+ sharedLayouts: ['symbols']
+ inputMode: preferredInputMode()
+
+ Connections {
+ target: InputContext
+ function onInputMethodHintsChanged() {
+ var newInputMode = preferredInputMode()
+ if (InputContext.inputEngine.inputModes.indexOf(newInputMode) !== -1)
+ InputContext.inputEngine.inputMode = newInputMode
+ }
+ }
+
+ function preferredInputMode() {
+ return InputContext.inputMethodHints &
+ (Qt.ImhPreferLatin | Qt.ImhEmailCharactersOnly | Qt.ImhUrlCharactersOnly |
+ Qt.ImhLatinOnly) ? InputEngine.InputMode.Latin : InputEngine.InputMode.ChineseHandwriting
+ }
+
+ KeyboardRow {
+ KeyboardColumn {
+ Layout.preferredWidth: 1
+ InputModeKey {
+ }
+ ChangeLanguageKey {
+ visible: true
+ }
+ ShiftKey {
+ }
+ HandwritingModeKey {
+ }
+ }
+ KeyboardColumn {
+ Layout.preferredWidth: 8
+ TraceInputKey {
+ objectName: "hwrInputArea"
+ patternRecognitionMode: InputEngine.PatternRecognitionMode.Handwriting
+ horizontalRulers:
+ InputContext.inputEngine.inputMode !== InputEngine.InputMode.ChineseHandwriting ? [] :
+ [Math.round(boundingBox.height / 4), Math.round(boundingBox.height / 4) * 2, Math.round(boundingBox.height / 4) * 3]
+ }
+ }
+ KeyboardColumn {
+ Layout.preferredWidth: 1
+ Key {
+ key: Qt.Key_Period
+ text: "。"
+ alternativeKeys: "¥‘’“”~…—\",.:;、。?! "
+ smallText: "!?"
+ smallTextVisible: true
+ highlighted: true
+ }
+ HideKeyboardKey {
+ visible: true
+ }
+ BackspaceKey {
+ }
+ EnterKey {
+ }
+ }
+ }
+}
diff --git a/src/plugins/cerence/hwr/plugin/t9writeabstractdictionary_p.h b/src/plugins/cerence/hwr/plugin/t9writeabstractdictionary_p.h
new file mode 100644
index 00000000..757558b5
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writeabstractdictionary_p.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef T9WRITEABSTRACTDICTIONARY_P_H
+#define T9WRITEABSTRACTDICTIONARY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGlobal>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class T9WriteAbstractDictionary
+{
+ Q_DISABLE_COPY(T9WriteAbstractDictionary)
+
+protected:
+ T9WriteAbstractDictionary() {}
+
+public:
+ virtual ~T9WriteAbstractDictionary() {}
+
+ virtual bool load() = 0;
+ virtual bool create(qint64 createSize) { Q_UNUSED(createSize) return false; }
+ virtual void close() {}
+ virtual bool convert() { return false; }
+ virtual QString name() const = 0;
+ virtual const void *data() const = 0;
+ virtual qint64 size() const = 0;
+ bool isCompleted() const { return state > 0; }
+
+private:
+ friend class T9WriteDictionaryTask;
+ QAtomicInt state;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // T9WRITEABSTRACTDICTIONARY_P_H
diff --git a/src/plugins/cerence/hwr/plugin/t9writedictionary.cpp b/src/plugins/cerence/hwr/plugin/t9writedictionary.cpp
new file mode 100644
index 00000000..14c3ae2f
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writedictionary.cpp
@@ -0,0 +1,281 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "t9writedictionary_p.h"
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Q_DECLARE_LOGGING_CATEGORY(lcT9Write)
+
+// T9WriteAbstractSource
+
+T9WriteAbstractSource::T9WriteAbstractSource(const DECUMA_SRC_DICTIONARY_INFO &info) :
+ _info(info)
+{
+}
+
+T9WriteAbstractSource::~T9WriteAbstractSource()
+{
+}
+
+const DECUMA_SRC_DICTIONARY_INFO *T9WriteAbstractSource::info() const
+{
+ return &_info;
+}
+
+// T9WriteFileSource
+
+T9WriteFileSource::T9WriteFileSource(const DECUMA_SRC_DICTIONARY_INFO &info, const QString &fileName) :
+ T9WriteAbstractSource(info),
+ file(fileName),
+ _data(nullptr),
+ _size(0)
+{
+}
+
+bool T9WriteFileSource::load()
+{
+ if (!_data) {
+ if (file.open(QIODevice::ReadOnly)) {
+ _size = file.size();
+ _data = file.map(0, _size, QFile::NoOptions);
+ if (!_data) {
+ _size = 0;
+ qCWarning(lcT9Write) << "Could not read dictionary file" << file.fileName();
+ }
+ file.close();
+ }
+ }
+
+ return _data != nullptr;
+}
+
+bool T9WriteFileSource::create(qint64 createSize)
+{
+ close();
+
+ if (file.open(QIODevice::ReadWrite)) {
+ if (file.resize(createSize)) {
+ _size = file.size();
+ _data = file.map(0, _size, QFile::NoOptions);
+ if (!_data) {
+ _size = 0;
+ qCWarning(lcT9Write) << "Could not read dictionary file" << file.fileName();
+ }
+ } else {
+ qCWarning(lcT9Write) << "Could not resize dictionary file" << file.fileName();
+ }
+ file.close();
+ }
+
+ return _data != nullptr;
+}
+
+void T9WriteFileSource::close()
+{
+ if (_data) {
+ file.unmap(static_cast<uchar *>(_data));
+ _data = nullptr;
+ _size = 0;
+ }
+}
+
+QString T9WriteFileSource::name() const
+{
+ return file.fileName();
+}
+
+const void *T9WriteFileSource::data() const
+{
+ return _data;
+}
+
+qint64 T9WriteFileSource::size() const
+{
+ return _size;
+}
+
+// T9WriteStringSource
+
+T9WriteStringSource::T9WriteStringSource(const DECUMA_SRC_DICTIONARY_INFO &info, const QStringList &source, const QString &name) :
+ T9WriteAbstractSource(info),
+ source(source),
+ _name(name)
+{
+}
+
+bool T9WriteStringSource::load()
+{
+ _data = source.join(QLatin1Char('\r'));
+ return true;
+}
+
+QString T9WriteStringSource::name() const
+{
+ return _name;
+}
+
+const void *T9WriteStringSource::data() const
+{
+ return _data.utf16();
+}
+
+qint64 T9WriteStringSource::size() const
+{
+ return _data.size();
+}
+
+// T9WriteDictionary
+
+T9WriteDictionary::T9WriteDictionary(QSharedPointer<T9WriteAbstractSource> source,
+ DECUMA_SESSION *decumaSession,
+ const DECUMA_MEM_FUNCTIONS &memFuncs,
+ bool cjk) :
+ source(source),
+ decumaSession(decumaSession),
+ memFuncs(memFuncs),
+ cjk(cjk),
+ convertedData(nullptr),
+ convertedSize(0)
+{
+}
+
+T9WriteDictionary::~T9WriteDictionary()
+{
+ if (convertedData) {
+ DECUMA_STATUS status = DECUMA_API(DestroyConvertedDictionary)(&convertedData, &memFuncs);
+ Q_ASSERT(status == decumaNoError);
+ Q_ASSERT(convertedData == nullptr);
+ }
+}
+
+bool T9WriteDictionary::load()
+{
+ return source->load();
+}
+
+bool T9WriteDictionary::convert()
+{
+ if (!source->data() || convertedData)
+ return false;
+
+ DECUMA_STATUS status;
+ status = DECUMA_API(ConvertDictionary)(&convertedData, source->data(), static_cast<DECUMA_UINT32>(source->size()),
+ source->info(), &convertedSize, &memFuncs);
+
+ if (status != decumaNoError) {
+ qCWarning(lcT9Write) << "Could not convert dictionary";
+ }
+
+ return status == decumaNoError;
+}
+
+QString T9WriteDictionary::name() const
+{
+ return source->name();
+}
+
+const void *T9WriteDictionary::data() const
+{
+ return convertedData ? convertedData : source->data();
+}
+
+qint64 T9WriteDictionary::size() const
+{
+ return convertedData ? convertedSize : source->size();
+}
+
+T9WriteDynamicDictionary::T9WriteDynamicDictionary(QSharedPointer<T9WriteAbstractSource> source, int maxWords, const DECUMA_MEM_FUNCTIONS &memFuncs, bool cjk) :
+ source(source),
+ memFuncs(memFuncs),
+ cjk(cjk),
+ _data(nullptr)
+{
+ DECUMA_API(DynamicDictionaryCreate)(&_data, static_cast<DECUMA_UINT32>(maxWords), &memFuncs);
+}
+
+T9WriteDynamicDictionary::~T9WriteDynamicDictionary()
+{
+ if (_data) {
+ DECUMA_API(DynamicDictionaryDestroy)(&_data);
+ }
+}
+
+bool T9WriteDynamicDictionary::load()
+{
+ if (!_data)
+ return false;
+
+ DECUMA_STATUS status;
+ DECUMA_UINT32 nUnprocessedSize = 0;
+ if (source->load()) {
+ status = DECUMA_API(DynamicDictionaryAddWords)(_data, source->data(), static_cast<DECUMA_UINT32>(source->size()), &nUnprocessedSize);
+ if (status) {
+ qCWarning(lcT9Write) << "Could not load words to dynamic dictionary, error" << status;
+ }
+ source->close();
+ }
+
+ return true;
+}
+
+QString T9WriteDynamicDictionary::name() const
+{
+ return source->name();
+}
+
+const void *T9WriteDynamicDictionary::data() const
+{
+ return _data;
+}
+
+qint64 T9WriteDynamicDictionary::size() const
+{
+ return 0;
+}
+
+qint64 T9WriteDynamicDictionary::bufferSize() const
+{
+ DECUMA_UINT32 result = 0;
+ if (_data)
+ DECUMA_API(DynamicDictionaryGetWordsBufferSize)(_data, &result);
+ return result;
+}
+
+void T9WriteDynamicDictionary::save()
+{
+ if (_data) {
+ DECUMA_UINT32 nWordsWritten = 0;
+ DECUMA_UINT32 nBytesWritten = 0;
+ qint64 fileSize = bufferSize();
+ if (source->create(fileSize)) {
+ DECUMA_STATUS status = DECUMA_API(DynamicDictionaryGetWords)(_data, const_cast<void *>(source->data()), static_cast<DECUMA_UINT32>(fileSize), &nWordsWritten, &nBytesWritten);
+ source->close();
+ }
+ }
+}
+
+bool T9WriteDynamicDictionary::hasWord(const QString &word)
+{
+ if (!_data)
+ return false;
+
+ int found = 0;
+ DECUMA_API(DynamicDictionaryHasWord)(_data, word.utf16(), &found);
+
+ return found != 0;
+}
+
+bool T9WriteDynamicDictionary::removeWord(const QString &word)
+{
+ DECUMA_STATUS status;
+
+ status = DECUMA_API(DynamicDictionaryDeleteWord)(_data, word.utf16());
+
+ return !status;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/hwr/plugin/t9writedictionary_p.h b/src/plugins/cerence/hwr/plugin/t9writedictionary_p.h
new file mode 100644
index 00000000..3678d69d
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writedictionary_p.h
@@ -0,0 +1,122 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef T9WRITEDICTIONARY_P_H
+#define T9WRITEDICTIONARY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "t9writeabstractdictionary_p.h"
+#include <QFile>
+#include <QSharedPointer>
+#include "cerence_hwr_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class T9WriteAbstractSource : public T9WriteAbstractDictionary
+{
+public:
+ T9WriteAbstractSource(const DECUMA_SRC_DICTIONARY_INFO &info);
+ virtual ~T9WriteAbstractSource();
+
+ const DECUMA_SRC_DICTIONARY_INFO *info() const;
+
+private:
+ const DECUMA_SRC_DICTIONARY_INFO _info;
+};
+
+class T9WriteFileSource : public T9WriteAbstractSource
+{
+public:
+ T9WriteFileSource(const DECUMA_SRC_DICTIONARY_INFO &info, const QString &fileName);
+
+ bool load() override;
+ bool create(qint64 createSize) override;
+ void close() override;
+
+ QString name() const override;
+ const void *data() const override;
+ qint64 size() const override;
+
+private:
+ QFile file;
+ void *_data;
+ qint64 _size;
+};
+
+class T9WriteStringSource : public T9WriteAbstractSource
+{
+public:
+ T9WriteStringSource(const DECUMA_SRC_DICTIONARY_INFO &info, const QStringList &source, const QString &name);
+
+ bool load() override;
+ QString name() const override;
+ const void *data() const override;
+ qint64 size() const override;
+
+private:
+ QStringList source;
+ QString _data;
+ QString _name;
+};
+
+class T9WriteDictionary : public T9WriteAbstractDictionary
+{
+ Q_DISABLE_COPY(T9WriteDictionary)
+public:
+ explicit T9WriteDictionary(QSharedPointer<T9WriteAbstractSource> source, DECUMA_SESSION *decumaSession, const DECUMA_MEM_FUNCTIONS &memFuncs, bool cjk);
+ ~T9WriteDictionary() override;
+
+ bool load() override;
+ bool convert() override;
+ QString name() const override;
+ const void *data() const override;
+ qint64 size() const override;
+
+private:
+ QSharedPointer<T9WriteAbstractSource> source;
+ DECUMA_SESSION *decumaSession;
+ const DECUMA_MEM_FUNCTIONS &memFuncs;
+ bool cjk;
+ void *convertedData;
+ DECUMA_UINT32 convertedSize;
+};
+
+class T9WriteDynamicDictionary : public T9WriteAbstractDictionary
+{
+ Q_DISABLE_COPY(T9WriteDynamicDictionary)
+public:
+ explicit T9WriteDynamicDictionary(QSharedPointer<T9WriteAbstractSource> source, int maxWords, const DECUMA_MEM_FUNCTIONS &memFuncs, bool cjk);
+ ~T9WriteDynamicDictionary() override;
+
+ bool load() override;
+ QString name() const override;
+ const void *data() const override;
+ qint64 size() const override;
+ qint64 bufferSize() const;
+
+ void save();
+ bool hasWord(const QString &word);
+ bool removeWord(const QString &word);
+
+private:
+ QSharedPointer<T9WriteAbstractSource> source;
+ const DECUMA_MEM_FUNCTIONS &memFuncs;
+ const bool cjk;
+ DECUMA_DYNAMIC_DICTIONARY *_data;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // T9WRITEDICTIONARY_P_H
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
diff --git a/src/plugins/cerence/hwr/plugin/t9writeinputmethod_p.h b/src/plugins/cerence/hwr/plugin/t9writeinputmethod_p.h
new file mode 100644
index 00000000..b8abb8d1
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writeinputmethod_p.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef T9WRITEINPUTMETHOD_P_H
+#define T9WRITEINPUTMETHOD_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtVirtualKeyboard/qvirtualkeyboardabstractinputmethod.h>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class T9WriteInputMethodPrivate;
+class T9WriteAbstractDictionary;
+
+class T9WriteInputMethod : public QVirtualKeyboardAbstractInputMethod
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(T9WriteInputMethod)
+ QML_NAMED_ELEMENT(HandwritingInputMethod)
+ QML_ADDED_IN_VERSION(2, 0)
+
+public:
+ enum class EngineMode {
+ Uninitialized,
+ Alphabetic,
+ Arabic,
+ Hebrew,
+ Thai,
+ SimplifiedChinese,
+ TraditionalChinese,
+ HongKongChinese,
+ Japanese,
+ Korean
+ };
+ Q_ENUM(EngineMode)
+
+ explicit T9WriteInputMethod(QObject *parent = nullptr);
+ ~T9WriteInputMethod();
+
+ void clearInputMode() override;
+
+ QList<QVirtualKeyboardInputEngine::InputMode> inputModes(const QString &locale);
+ bool setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode);
+ bool setTextCase(QVirtualKeyboardInputEngine::TextCase textCase);
+
+ bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers);
+
+ void reset();
+ void update();
+
+ QList<QVirtualKeyboardSelectionListModel::Type> selectionLists();
+ int selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type);
+ QVariant selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role);
+ void selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index);
+ bool selectionListRemoveItem(QVirtualKeyboardSelectionListModel::Type type, int index) override;
+
+ QList<QVirtualKeyboardInputEngine::PatternRecognitionMode> patternRecognitionModes() const;
+ QVirtualKeyboardTrace *traceBegin(
+ int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode,
+ const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo);
+ bool traceEnd(QVirtualKeyboardTrace *trace);
+
+ bool reselect(int cursorPosition, const QVirtualKeyboardInputEngine::ReselectFlags &reselectFlags);
+
+signals:
+ void resultListChanged();
+
+protected:
+ void timerEvent(QTimerEvent *timerEvent);
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/cerence/hwr/plugin/t9writewordcandidate.cpp b/src/plugins/cerence/hwr/plugin/t9writewordcandidate.cpp
new file mode 100644
index 00000000..ebb35c07
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writewordcandidate.cpp
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "t9writewordcandidate_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+T9WriteWordCandidate::T9WriteWordCandidate(QString symbs, int resultIndex, T9WriteWordCandidate::Origin origin) :
+ symbs(symbs),
+ resultIndex(resultIndex),
+ origin(origin)
+{
+
+}
+
+bool operator==(const T9WriteWordCandidate &a, const T9WriteWordCandidate &b)
+{
+ return a.symbs == b.symbs;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/hwr/plugin/t9writewordcandidate_p.h b/src/plugins/cerence/hwr/plugin/t9writewordcandidate_p.h
new file mode 100644
index 00000000..72c8cf11
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writewordcandidate_p.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef T9WRITEWORDCANDIDATE_H
+#define T9WRITEWORDCANDIDATE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QString>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class T9WriteWordCandidate
+{
+public:
+ enum class Origin {
+ None,
+ T9Write,
+ XT9
+ };
+
+ T9WriteWordCandidate(QString symbs, int resultIndex = -1, Origin origin = Origin::None);
+
+public:
+ QString symbs;
+ int resultIndex;
+ Origin origin;
+};
+
+bool operator==(const T9WriteWordCandidate &a, const T9WriteWordCandidate &b);
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // T9WRITEWORDCANDIDATE_H
diff --git a/src/plugins/cerence/hwr/plugin/t9writeworker.cpp b/src/plugins/cerence/hwr/plugin/t9writeworker.cpp
new file mode 100644
index 00000000..1a127c71
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writeworker.cpp
@@ -0,0 +1,472 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "t9writeworker_p.h"
+#include <QLoggingCategory>
+
+#include <QFile>
+#include <QElapsedTimer>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Q_DECLARE_LOGGING_CATEGORY(lcT9Write)
+
+/*!
+ \class QtVirtualKeyboard::T9WriteTask
+ \internal
+*/
+
+T9WriteTask::T9WriteTask(QObject *parent) :
+ QObject(parent),
+ decumaSession(nullptr),
+ runSema()
+{
+}
+
+void T9WriteTask::wait()
+{
+ runSema.acquire();
+ runSema.release();
+}
+
+/*!
+ \class QtVirtualKeyboard::T9WriteDictionaryTask
+ \internal
+*/
+
+T9WriteDictionaryTask::T9WriteDictionaryTask(QSharedPointer<T9WriteAbstractDictionary> dictionary,
+ bool convertDictionary) :
+ dictionary(dictionary),
+ convertDictionary(convertDictionary)
+{
+}
+
+void T9WriteDictionaryTask::run()
+{
+ qCDebug(lcT9Write) << "T9WriteDictionaryTask::run()";
+
+ QElapsedTimer perf;
+ perf.start();
+
+ bool result = false;
+ if (dictionary) {
+ result = dictionary->load();
+ if (result && convertDictionary)
+ result = dictionary->convert();
+ }
+
+ qCDebug(lcT9Write) << "T9WriteDictionaryTask::run(): time:" << perf.elapsed() << "ms";
+
+ if (result) {
+ dictionary->state++;
+ emit completed(dictionary);
+ }
+}
+
+T9WriteAddArcTask::T9WriteAddArcTask(QVirtualKeyboardTrace *trace) :
+ trace(trace)
+{
+}
+
+void T9WriteAddArcTask::run()
+{
+ QElapsedTimer perf;
+ perf.start();
+ DECUMA_UINT32 arcID = (DECUMA_UINT32)trace->traceId();
+ DECUMA_STATUS status = DECUMA_API(StartNewArc)(decumaSession, arcID);
+ Q_ASSERT(status == decumaNoError);
+ if (status != decumaNoError) {
+ qCWarning(lcT9Write) << "T9WriteAddArcTask::run(): Failed to start new arc, status:" << status;
+ return;
+ }
+
+ const QVariantList points = trace->points();
+ Q_ASSERT(!points.isEmpty());
+
+ for (const QVariant &p : points) {
+ const QPoint pt(p.toPointF().toPoint());
+ status = DECUMA_API(AddPoint)(decumaSession, (DECUMA_COORD)pt.x(),(DECUMA_COORD)pt.y(), arcID);
+ if (status != decumaNoError) {
+ qCWarning(lcT9Write) << "T9WriteAddArcTask::run(): Failed to add point, status:" << status;
+ DECUMA_API(CancelArc)(decumaSession, arcID);
+ return;
+ }
+ }
+
+ status = DECUMA_API(CommitArc)(decumaSession, arcID);
+ if (status != decumaNoError)
+ qCWarning(lcT9Write) << "T9WriteAddArcTask::run(): Failed to commit arc, status:" << status;
+ else
+ qCDebug(lcT9Write) << "T9WriteAddArcTask::run(): time:" << perf.elapsed() << "ms";
+}
+
+/*!
+ \class QtVirtualKeyboard::T9WriteRecognitionResult
+ \internal
+*/
+
+T9WriteRecognitionResult::T9WriteRecognitionResult(int id, int maxResults, int maxCharsPerWord) :
+ status(decumaNoError),
+ numResults(0),
+ instantGesture(0),
+ id(id),
+ maxResults(maxResults),
+ maxCharsPerWord(maxCharsPerWord)
+{
+ Q_ASSERT(maxResults > 0);
+ Q_ASSERT(maxCharsPerWord > 0);
+ results.resize(maxResults);
+ int bufferLength = (maxCharsPerWord + 1);
+ _chars.resize(maxResults * bufferLength);
+ _symbolChars.resize(maxResults * bufferLength);
+ _symbolStrokes.resize(maxResults * bufferLength);
+ for (int i = 0; i < maxResults; i++) {
+ DECUMA_HWR_RESULT &hwrResult = results[i];
+ hwrResult.pChars = &_chars[i * bufferLength];
+ hwrResult.pSymbolChars = &_symbolChars[i * bufferLength];
+ hwrResult.pSymbolStrokes = &_symbolStrokes[i * bufferLength];
+ }
+}
+
+/*!
+ \class QtVirtualKeyboard::T9WriteRecognitionTask
+ \internal
+*/
+
+T9WriteRecognitionTask::T9WriteRecognitionTask(QSharedPointer<T9WriteRecognitionResult> result) :
+ T9WriteTask(),
+ result(result),
+ stateCancelled(false)
+{
+}
+
+void T9WriteRecognitionTask::run()
+{
+ if (!decumaSession)
+ return;
+
+ perf.start();
+
+ while (true) {
+ DECUMA_BG_REC_STATE bgRecState = bgRecIdle;
+ DECUMA_STATUS status = DECUMA_API(GetBackgroundRecognitionState(decumaSession, &bgRecState));
+ if (status) {
+ qCDebug(lcT9Write) << "T9WriteRecognitionTask::run(): GetBackgroundRecognitionState failed, status:" << status;
+ break;
+ }
+
+ if (bgRecState != bgRecStarted) {
+ qCDebug(lcT9Write) << "T9WriteRecognitionTask::run(): state:" << bgRecState << "time:" << perf.elapsed() << "ms";
+ break;
+ }
+
+ if (checkCancelled())
+ return;
+
+ QThread::msleep(25);
+
+ if (checkCancelled())
+ return;
+ }
+
+ result->status = DECUMA_API(Recognize)(decumaSession, result->results.data(), result->results.size(), &result->numResults, result->maxCharsPerWord, nullptr, nullptr);
+ if (result->status != decumaNoError)
+ qCWarning(lcT9Write) << "T9WriteRecognitionTask::run(): Recognition failed, status:" << result->status;
+
+ if (checkCancelled())
+ return;
+
+ qCDebug(lcT9Write) << "T9WriteRecognitionTask::run(): time:" << perf.elapsed() << "ms";
+}
+
+bool T9WriteRecognitionTask::cancelRecognition()
+{
+ QMutexLocker stateGuard(&stateLock);
+ Q_UNUSED(stateGuard)
+ stateCancelled = true;
+ return true;
+}
+
+bool T9WriteRecognitionTask::checkCancelled()
+{
+ QMutexLocker stateGuard(&stateLock);
+ Q_UNUSED(stateGuard)
+ if (stateCancelled) {
+ result.reset();
+ qCDebug(lcT9Write) << "T9WriteRecognitionTask cancelled, time:" << perf.elapsed() << "ms";
+ return true;
+ }
+
+ return false;
+}
+
+int T9WriteRecognitionTask::resultId() const
+{
+ return result != nullptr ? result->id : -1;
+}
+
+/*!
+ \class QtVirtualKeyboard::T9WriteRecognitionResultsTask
+ \internal
+*/
+
+T9WriteRecognitionResultsTask::T9WriteRecognitionResultsTask(QSharedPointer<T9WriteRecognitionResult> result) :
+ T9WriteTask(),
+ result(result)
+{
+}
+
+void T9WriteRecognitionResultsTask::run()
+{
+ if (!result)
+ return;
+
+ if (result->status != decumaNoError) {
+ emit recognitionError(result->status);
+ return;
+ }
+
+ QVariantList resultList;
+ for (int i = 0; i < result->numResults; i++)
+ {
+ QVariantMap resultMap;
+ QString resultString;
+ QString gesture;
+ const DECUMA_HWR_RESULT &hwrResult = result->results.at(i);
+ resultString.reserve(hwrResult.nChars);
+ QVariantList symbolStrokes;
+ int charPos = 0;
+ for (int symbolIndex = 0; symbolIndex < hwrResult.nSymbols; symbolIndex++) {
+ int symbolLength = hwrResult.pSymbolChars[symbolIndex];
+ QString symbol(QString::fromUtf16(reinterpret_cast<const char16_t *>(&hwrResult.pChars[charPos]), symbolLength));
+ // Do not append gesture symbol to result string
+ if (hwrResult.bGesture) {
+ gesture = symbol.right(1);
+ symbol.chop(1);
+ }
+ resultString.append(symbol);
+ charPos += symbolLength;
+ if (hwrResult.pSymbolStrokes)
+ symbolStrokes.append(QVariant((int)hwrResult.pSymbolStrokes[symbolIndex]));
+ }
+
+ resultMap[QLatin1String("resultId")] = result->id;
+ resultMap[QLatin1String("chars")] = resultString;
+ resultMap[QLatin1String("symbolStrokes")] = symbolStrokes;
+ if (!gesture.isEmpty())
+ resultMap[QLatin1String("gesture")] = gesture;
+
+ resultList.append(resultMap);
+ }
+
+ if (resultList.isEmpty()) {
+ qCDebug(lcT9Write) << "T9WriteRecognitionResultsTask::run(): no results available";
+ return;
+ }
+
+ qCDebug(lcT9Write) << "T9WriteRecognitionResultsTask::run():" << resultList.size() << "results available";
+ emit resultsAvailable(resultList);
+}
+
+/*!
+ \class QtVirtualKeyboard::T9WriteLearnWordTask
+ \internal
+*/
+
+T9WriteDlmWordTask::T9WriteDlmWordTask(QSharedPointer<T9WriteAbstractDictionary> dlmDictionary, const QString &word, const QString &stringStart) :
+ T9WriteTask(),
+ dlmDictionary(dlmDictionary),
+ word(word),
+ stringStart(stringStart)
+{
+
+}
+
+void T9WriteDlmWordTask::run()
+{
+ DECUMA_RECOGNITION_SETTINGS recSettings;
+ memset(&recSettings, 0, sizeof(recSettings));
+ recSettings.boostLevel = boostDictWords;
+ recSettings.stringCompleteness = canBeContinued;
+ if (!stringStart.isEmpty())
+ recSettings.pStringStart = const_cast<DECUMA_UNICODE *>(stringStart.utf16());
+
+ DECUMA_UINT16 nDictionaries = 0;
+ DECUMA_STATUS status = DECUMA_API(GetNAttachedDictionaries)(decumaSession, &nDictionaries);
+ if (status)
+ return;
+
+ bool wordFound = false;
+ if (nDictionaries != 0) {
+ QVector<DECUMA_MATCH_RESULT> matchResults;
+ matchResults.resize(nDictionaries);
+ status = DECUMA_API(MatchWord)(decumaSession, word.utf16(),
+ static_cast<DECUMA_UINT16>(word.length()),
+ &recSettings, matchResults.data());
+ if (!status) {
+ for (const auto &matchResult : std::as_const(matchResults)) {
+ qCDebug(lcT9Write) << "T9WriteDlmWordTask::run(): MatchWord string type" << matchResult.stringType;
+ if (matchResult.stringType != notFromDictionary) {
+ wordFound = true;
+ break;
+ }
+ }
+ } else {
+ qCDebug(lcT9Write) << "T9WriteDlmWordTask::run(): MatchWord failed" << status;
+ return;
+ }
+ }
+
+ if (!wordFound) {
+ qCDebug(lcT9Write) << "T9WriteDlmWordTask::run(): DynamicDictionaryAddWord";
+ status = DECUMA_API(DynamicDictionaryAddWord)(
+ const_cast<DECUMA_DYNAMIC_DICTIONARY *>(
+ reinterpret_cast<const DECUMA_DYNAMIC_DICTIONARY *>(dlmDictionary->data())),
+ word.utf16());
+
+ if (!status) {
+ persist();
+ }
+ }
+}
+
+void T9WriteDlmWordTask::persist()
+{
+ T9WriteDynamicDictionary *dictionary = static_cast<T9WriteDynamicDictionary *>(dlmDictionary.data());
+
+ QElapsedTimer perf;
+ perf.start();
+
+ dictionary->save();
+
+ qCDebug(lcT9Write) << "T9WriteDlmWordTask::persist(): time:" << perf.elapsed() << "ms";
+}
+
+/*!
+ \class QtVirtualKeyboard::T9WriteDlmRemoveWordTask
+ \internal
+*/
+
+T9WriteDlmRemoveWordTask::T9WriteDlmRemoveWordTask(QSharedPointer<T9WriteAbstractDictionary> dlmDictionary, const QString &word) :
+ T9WriteDlmWordTask(dlmDictionary, word, QString())
+{
+
+}
+
+void T9WriteDlmRemoveWordTask::run()
+{
+ T9WriteDynamicDictionary *dictionary = static_cast<T9WriteDynamicDictionary *>(dlmDictionary.data());
+ if (dictionary->removeWord(word)) {
+ persist();
+ }
+}
+
+/*!
+ \class QtVirtualKeyboard::T9WriteWorker
+ \internal
+*/
+
+T9WriteWorker::T9WriteWorker(DECUMA_SESSION *decumaSession, const bool cjk, QObject *parent) :
+ QThread(parent),
+ taskSema(),
+ taskLock(),
+ decumaSession(decumaSession),
+ cjk(cjk)
+{
+ abort = false;
+}
+
+T9WriteWorker::~T9WriteWorker()
+{
+ abort = true;
+ taskSema.release();
+ wait();
+}
+
+void T9WriteWorker::addTask(QSharedPointer<T9WriteTask> task)
+{
+ if (task) {
+ QMutexLocker guard(&taskLock);
+ task->moveToThread(this);
+ taskList.append(task);
+ taskSema.release();
+ }
+}
+
+int T9WriteWorker::removeTask(QSharedPointer<T9WriteTask> task)
+{
+ int count = 0;
+ if (task) {
+ QMutexLocker guard(&taskLock);
+ const bool isRunning = taskList.indexOf(task) == 0;
+ if (isRunning) {
+ task->wait();
+ } else if (taskList.removeOne(task)) {
+ ++count;
+ task->runSema.release();
+ }
+ }
+ return count;
+}
+
+int T9WriteWorker::removeAllTasks()
+{
+ idleSema.acquire();
+ QMutexLocker guard(&taskLock);
+ int count = taskList.size();
+ for (QSharedPointer<T9WriteTask> task : taskList) {
+ task->runSema.release();
+ }
+ taskList.clear();
+ idleSema.release();
+ return count;
+}
+
+void T9WriteWorker::waitForAllTasks()
+{
+ while (isRunning()) {
+ idleSema.acquire();
+ QMutexLocker guard(&taskLock);
+ if (taskList.isEmpty()) {
+ idleSema.release();
+ break;
+ }
+ idleSema.release();
+ }
+}
+
+int T9WriteWorker::numberOfPendingTasks()
+{
+ QMutexLocker guard(&taskLock);
+ return taskList.size();
+}
+
+void T9WriteWorker::run()
+{
+ while (!abort) {
+ idleSema.release();
+ taskSema.acquire();
+ if (abort)
+ break;
+ idleSema.acquire();
+ QSharedPointer<T9WriteTask> currentTask;
+ {
+ QMutexLocker guard(&taskLock);
+ if (!taskList.isEmpty()) {
+ currentTask = taskList.front();
+ }
+ }
+ if (currentTask) {
+ currentTask->decumaSession = decumaSession;
+ currentTask->cjk = cjk;
+ currentTask->run();
+ currentTask->runSema.release();
+ QMutexLocker guard(&taskLock);
+ taskList.removeOne(currentTask);
+ }
+ }
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/hwr/plugin/t9writeworker_p.h b/src/plugins/cerence/hwr/plugin/t9writeworker_p.h
new file mode 100644
index 00000000..34c29afe
--- /dev/null
+++ b/src/plugins/cerence/hwr/plugin/t9writeworker_p.h
@@ -0,0 +1,230 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef T9WRITEWORKER_H
+#define T9WRITEWORKER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtVirtualKeyboard/qvirtualkeyboardtrace.h>
+
+#include <QThread>
+#include <QSemaphore>
+#include <QMutex>
+#include <QStringList>
+#include <QSharedPointer>
+#include <QPointer>
+#include <QMap>
+#include <QList>
+#include <QElapsedTimer>
+
+#include "cerence_hwr_p.h"
+#include "t9writedictionary_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class T9WriteTask : public QObject
+{
+ Q_OBJECT
+public:
+ explicit T9WriteTask(QObject *parent = nullptr);
+
+ virtual void run() = 0;
+
+ void wait();
+
+ friend class T9WriteWorker;
+
+protected:
+ DECUMA_SESSION *decumaSession;
+ bool cjk;
+
+private:
+ QSemaphore runSema;
+};
+
+class T9WriteDictionaryTask : public T9WriteTask
+{
+ Q_OBJECT
+public:
+ explicit T9WriteDictionaryTask(QSharedPointer<T9WriteAbstractDictionary> dictionary,
+ bool convertDictionary);
+
+ void run();
+
+ QSharedPointer<T9WriteAbstractDictionary> dictionary;
+ QSharedPointer<T9WriteAbstractSource> source;
+ const QString dictionaryFileName;
+ bool convertDictionary;
+
+signals:
+ void completed(QSharedPointer<T9WriteAbstractDictionary> dictionary);
+};
+
+class T9WriteAddArcTask : public T9WriteTask
+{
+ Q_OBJECT
+public:
+ explicit T9WriteAddArcTask(QVirtualKeyboardTrace *trace);
+
+ void run();
+
+private:
+ QVirtualKeyboardTrace *trace;
+};
+
+class T9WriteRecognitionResult
+{
+ Q_DISABLE_COPY(T9WriteRecognitionResult)
+
+public:
+ explicit T9WriteRecognitionResult(int id, int maxResults, int maxCharsPerWord);
+
+ DECUMA_STATUS status;
+ QList<DECUMA_HWR_RESULT> results;
+ DECUMA_UINT16 numResults;
+ int instantGesture;
+ const int id;
+ const int maxResults;
+ const int maxCharsPerWord;
+
+private:
+ QList<DECUMA_UNICODE> _chars;
+ QList<DECUMA_INT16> _symbolChars;
+ QList<DECUMA_INT16> _symbolStrokes;
+};
+
+class T9WriteRecognitionTask : public T9WriteTask
+{
+ Q_OBJECT
+public:
+ explicit T9WriteRecognitionTask(QSharedPointer<T9WriteRecognitionResult> result);
+
+ void run();
+ bool cancelRecognition();
+ bool checkCancelled();
+ int resultId() const;
+
+private:
+ void waitForBackgroundRecognition();
+
+private:
+ QSharedPointer<T9WriteRecognitionResult> result;
+ QMutex stateLock;
+ bool stateCancelled;
+ QElapsedTimer perf;
+};
+
+class T9WriteRecognitionResultsTask : public T9WriteTask
+{
+ Q_OBJECT
+public:
+ explicit T9WriteRecognitionResultsTask(QSharedPointer<T9WriteRecognitionResult> result);
+
+ void run();
+
+signals:
+ void resultsAvailable(const QVariantList &resultList);
+ void recognitionError(int status);
+
+private:
+ QSharedPointer<T9WriteRecognitionResult> result;
+};
+
+class T9WriteDlmWordTask : public T9WriteTask
+{
+ Q_OBJECT
+public:
+ explicit T9WriteDlmWordTask(QSharedPointer<T9WriteAbstractDictionary> dlmDictionary, const QString &word, const QString &stringStart);
+
+ void run();
+
+protected:
+ void persist();
+
+protected:
+ QSharedPointer<T9WriteAbstractDictionary> dlmDictionary;
+ const QString word;
+ const QString stringStart;
+};
+
+class T9WriteDlmRemoveWordTask : public T9WriteDlmWordTask
+{
+ Q_OBJECT
+public:
+ explicit T9WriteDlmRemoveWordTask(QSharedPointer<T9WriteAbstractDictionary> dlmDictionary, const QString &word);
+
+ void run();
+};
+
+class T9WriteWorker : public QThread
+{
+ Q_OBJECT
+public:
+ explicit T9WriteWorker(DECUMA_SESSION *decumaSession, const bool cjk, QObject *parent = nullptr);
+ ~T9WriteWorker();
+
+ void addTask(QSharedPointer<T9WriteTask> task);
+ int removeTask(QSharedPointer<T9WriteTask> task);
+ int removeAllTasks();
+ void waitForAllTasks();
+ int numberOfPendingTasks();
+
+ template <class X>
+ int removeAllTasks() {
+ QMutexLocker guard(&taskLock);
+ int count = 0;
+ for (int i = 0; i < taskList.size();) {
+ QSharedPointer<X> task(taskList[i].objectCast<X>());
+ if (task) {
+ taskList.removeAt(i);
+ ++count;
+ } else {
+ ++i;
+ }
+ }
+ return count;
+ }
+
+ template <class X>
+ void waitForAllTasksOfType() {
+ QSharedPointer<X> task;
+ {
+ QMutexLocker guard(&taskLock);
+ for (int i = taskList.size() - 1; i >= 0; --i) {
+ task = taskList[i].objectCast<X>();
+ if (task)
+ break;
+ }
+ }
+ if (task)
+ task->wait();
+ }
+
+protected:
+ void run();
+
+private:
+ QList<QSharedPointer<T9WriteTask> > taskList;
+ QSemaphore idleSema;
+ QSemaphore taskSema;
+ QMutex taskLock;
+ DECUMA_SESSION *decumaSession;
+ QBasicAtomicInt abort;
+ const bool cjk;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // T9WRITEWORKER_H
diff --git a/src/plugins/cerence/unpack.py b/src/plugins/cerence/unpack.py
new file mode 100644
index 00000000..825d333e
--- /dev/null
+++ b/src/plugins/cerence/unpack.py
@@ -0,0 +1,386 @@
+#!/usr/bin/env python
+# Copyright (C) 2021 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import os
+import sys
+import zipfile
+import tempfile
+import shutil
+import fnmatch
+
+#
+# This utility script unpacks the Cerence SDK to appropriate directory
+# structure for Qt Virtual Keyboard.
+#
+# Usage: unpack.py <filename.zip> [<target dir>]
+#
+# - <filename.zip> The Cerence SDK zip.
+# - <target dir> Target directory to unpack files to. The
+# directory can be located out of tree.
+# The default directory is src/plugins/cerence/sdk.
+#
+# The script will overwrite existing files, so be careful.
+#
+
+#
+# Unpack rule list
+#
+# Each list entry is a dictionary consisting of target directory as
+# key and matching pattern as value. The dictionary can be defined in
+# the following ways:
+#
+# Note: The rules within the dictionary are executed in arbitrary order.
+# Add a new list entry if the order is significant.
+#
+# Format:
+# 1. { 'target dir 1': [ 'pattern1', 'pattern2', ... ], 'target dir 2': ... }
+# - Each pattern is matched against the zip file contents. The file is
+# copied to target dir if the pattern matches. Each pattern is handled
+# independent of each other.
+#
+# 2. { 'target dir 1': [ [ 'file group pattern', 'sub pattern1', ... ] ], 'target dir 2': ... }
+# - First the file group pattern is matched against the zip file contents.
+# Then all the sub patterns are matched in the sub directory specified by
+# the first match. If all the sub patterns match, then first match from
+# file group pattern and all the matching files from sub pattterns are copied.
+# The purpose of this option is to copy coupled files, e.g. DLL and LIB
+# files found in the same directory.
+#
+
+T9WRITE_DIR = 't9write/'
+T9WRITE_DATA_DIR = 't9write/data/'
+XT9_DIR = 'xt9/'
+XT9_DATA_DIR = 'xt9/data/'
+
+UNPACK_RULES = [
+
+##############################################################################
+#
+# T9 Write
+#
+##############################################################################
+
+{ # Header files
+T9WRITE_DIR + 'api': [
+ '*/decuma_hwr.h',
+ '*/decuma_hwr_cjk.h',
+ '*/decuma_hwr_types.h',
+ '*/decuma_point.h',
+ '*/decumaBasicTypes.h',
+ '*/decumaBasicTypesMinMax.h',
+ '*/decumaCharacterSetType.h',
+ '*/decumaCurve.h',
+ '*/decumaFunctionalSupport.h',
+ '*/decumaFunctionalSupportCheck.h',
+ '*/decumaLanguages.h',
+ '*/decumaLiteFunctionalSupport.h',
+ '*/decumaPlusFunctionalSupport.h',
+ '*/decumaRuntimeMallocData.h',
+ '*/decumaStatus.h',
+ '*/decumaStorageSpecifiers.h',
+ '*/decumaSymbolCategories.h',
+ '*/decumaUnicodeTypes.h',
+ '*/cerence_handwriting_alpha_version.h',
+ '*/cerence_handwriting_api_version.h',
+ '*/cerence_handwriting_cjk_version.h',
+ '*/xxchApiOem.h',
+ '*/xxchOem.h',
+],
+}, { # Data
+T9WRITE_DATA_DIR + 'arabic': [
+ '*/Arabic/*_le.bin',
+],
+T9WRITE_DATA_DIR + 'hebrew': [
+ '*/Hebrew/*_le.bin',
+],
+T9WRITE_DATA_DIR + 'thai': [
+ '*/*Thai*/*_le.bin',
+],
+T9WRITE_DATA_DIR + '': [
+ '*/*_le.bin',
+ '*/*.hdb',
+ '*/*.phd',
+],
+}, { # Libraries
+# T9 Write ARM Linux 32-bit
+T9WRITE_DIR + 'lib/linux/arm/static/alphabetic': [
+ '*T9Write_Alphabetic*/arm_linux*_32bit/*.a',
+ '*T9Write_Alphabetic*/arm_linux*_32bit/*.o',
+],
+T9WRITE_DIR + 'lib/linux/arm/shared/alphabetic': [
+ '*T9Write_Alphabetic*/arm_linux*_32bit/*.so',
+],
+T9WRITE_DIR + 'lib/linux/arm/static/cjk': [
+ '*T9Write_CJK*/arm_linux*_32bit/*.a',
+ '*T9Write_CJK*/arm_linux*_32bit/*.o',
+],
+T9WRITE_DIR + 'lib/linux/arm/shared/cjk': [
+ '*T9Write_CJK*/arm_linux*_32bit/*.so',
+],
+# T9 Write ARM Linux 64-bit
+T9WRITE_DIR + 'lib/linux/arm64/static/alphabetic': [
+ '*T9Write_Alphabetic*/arm_linux*_64bit/*.a',
+ '*T9Write_Alphabetic*/arm_linux*_64bit/*.o',
+],
+T9WRITE_DIR + 'lib/linux/arm64/shared/alphabetic': [
+ '*T9Write_Alphabetic*/arm_linux*_64bit/*.so',
+],
+T9WRITE_DIR + 'lib/linux/arm64/static/cjk': [
+ '*T9Write_CJK*/arm_linux*_64bit/*.a',
+ '*T9Write_CJK*/arm_linux*_64bit/*.o',
+],
+T9WRITE_DIR + 'lib/linux/arm64/shared/cjk': [
+ '*T9Write_CJK*/arm_linux*_64bit/*.so',
+],
+# T9 Write x86 Linux 32-bit
+T9WRITE_DIR + 'lib/linux/x86/static/alphabetic': [
+ '*T9Write_Alphabetic*/i86_linux*_32bit/*.a',
+ '*T9Write_Alphabetic*/i86_linux*_32bit/*.o',
+],
+T9WRITE_DIR + 'lib/linux/x86/shared/alphabetic': [
+ '*T9Write_Alphabetic*/i86_linux*_32bit/*.so',
+],
+T9WRITE_DIR + 'lib/linux/x86/static/cjk': [
+ '*T9Write_CJK*/i86_linux*_32bit/*.a',
+ '*T9Write_CJK*/i86_linux*_32bit/*.o',
+],
+T9WRITE_DIR + 'lib/linux/x86/shared/cjk': [
+ '*T9Write_CJK*/i86_linux*_32bit/*.so',
+],
+# T9 Write x86 Linux 64-bit
+T9WRITE_DIR + 'lib/linux/x86_64/static/alphabetic': [
+ '*T9Write_Alphabetic*/i86_linux*_64bit/*.a',
+ '*T9Write_Alphabetic*/i86_linux*_64bit/*.o',
+],
+T9WRITE_DIR + 'lib/linux/x86_64/shared/alphabetic': [
+ '*T9Write_Alphabetic*/i86_linux*_64bit/*.so',
+],
+T9WRITE_DIR + 'lib/linux/x86_64/static/cjk': [
+ '*T9Write_CJK*/i86_linux*_64bit/*.a',
+ '*T9Write_CJK*/i86_linux*_64bit/*.o',
+],
+T9WRITE_DIR + 'lib/linux/x86_64/shared/cjk': [
+ '*T9Write_CJK*/i86_linux*_64bit/*.so',
+],
+# T9 Write x86 Win32 32-bit
+T9WRITE_DIR + 'lib/win32/x86/static/alphabetic': [
+ '*T9Write_Alphabetic*/i86_win32_32bit/libt9write*.lib',
+],
+T9WRITE_DIR + 'lib/win32/x86/shared/alphabetic': [
+ [ '*T9Write_Alphabetic*/i86_win32_32bit/t9write*.dll', 't9write*.lib' ],
+],
+T9WRITE_DIR + 'lib/win32/x86/static/cjk': [
+ '*T9Write_CJK*/i86_win32_32bit/libt9write*.lib',
+],
+T9WRITE_DIR + 'lib/win32/x86/shared/cjk': [
+ [ '*T9Write_CJK*/i86_win32_32bit/t9write*.dll', 't9write*.lib' ],
+],
+# T9 Write x86 Win32 64-bit
+T9WRITE_DIR + 'lib/win32/amd64/static/alphabetic': [
+ '*T9Write_Alphabetic*/i86_win32_64bit/libt9write*.lib',
+],
+T9WRITE_DIR + 'lib/win32/amd64/shared/alphabetic': [
+ [ '*T9Write_Alphabetic*/i86_win32_64bit/t9write*.dll', 't9write*.lib' ],
+],
+T9WRITE_DIR + 'lib/win32/amd64/static/cjk': [
+ '*T9Write_CJK*/i86_win32_64bit/libt9write*.lib',
+],
+T9WRITE_DIR + 'lib/win32/amd64/shared/cjk': [
+ [ '*T9Write_CJK*/i86_win32_64bit/t9write*.dll', 't9write*.lib' ],
+],
+},
+
+##############################################################################
+#
+# XT9
+#
+##############################################################################
+
+{ # Header files
+XT9_DIR + 'api': [
+ '*/et9api.h',
+ '*/et9awapi.h',
+ '*/et9cpapi.h',
+ '*/et9kapi.h',
+ '*/et9kbdef.h',
+ '*/et9navapi.h',
+ '*/xxet9oem.h',
+],
+}, { # Libraries
+# XT9 ARM Linux 32-bit
+XT9_DIR + 'lib/linux/arm/static': [
+ '*/Xt9/*/arm_linux*_32bit/*.a',
+ '*/Xt9/*/arm_linux*_32bit/*.o',
+],
+XT9_DIR + 'lib/linux/arm/shared': [
+ '*/Xt9/*/arm_linux*_32bit/*.so',
+],
+# XT9 ARM Linux 64-bit
+XT9_DIR + 'lib/linux/arm64/static': [
+ '*/Xt9/*/arm_linux*_64bit/*.a',
+ '*/Xt9/*/arm_linux*_64bit/*.o',
+],
+XT9_DIR + 'lib/linux/arm64/shared': [
+ '*/Xt9/*/arm_linux*_64bit/*.so',
+],
+# XT9 x86 Linux 32-bit
+XT9_DIR + 'lib/linux/x86/static': [
+ '*/Xt9/*/i86_linux*_32bit/*.a',
+ '*/Xt9/*/i86_linux*_32bit/*.o',
+],
+XT9_DIR + 'lib/linux/x86/shared': [
+ '*/Xt9/*/i86_linux*_32bit/*.so',
+],
+# XT9 x86 Linux 64-bit
+XT9_DIR + 'lib/linux/x86_64/static': [
+ '*/Xt9/*/i86_linux*_64bit/*.a',
+ '*/Xt9/*/i86_linux*_64bit/*.o',
+],
+XT9_DIR + 'lib/linux/x86_64/shared': [
+ '*/Xt9/*/i86_linux*_64bit/*.so',
+],
+# XT9 x86 Win32 32-bit
+XT9_DIR + 'lib/win32/x86/static': [
+ '*/Xt9/*/i86_win32_32bit/libxt9*.lib',
+],
+XT9_DIR + 'lib/win32/x86/shared': [
+ [ '*/Xt9/*/i86_win32_32bit/xt9*.dll', 'xt9*.lib' ],
+],
+# XT9 x86 Win32 64-bit
+XT9_DIR + 'lib/win32/amd64/static': [
+ '*/Xt9/*/i86_win32_64bit/libxt9*.lib',
+],
+XT9_DIR + 'lib/win32/amd64/shared': [
+ [ '*/Xt9/*/i86_win32_64bit/xt9*.dll', 'xt9*.lib' ],
+],
+},
+
+##############################################################################
+#
+# XT9 Data
+#
+##############################################################################
+
+{
+XT9_DATA_DIR: [
+ '*/*.ldb',
+],
+},
+
+]
+
+#
+# Blacklist
+#
+# File matching rules for blacklisted items. Matched before UNPACK_RULES.
+#
+
+BLACKLIST_RULES = [
+'*__MACOSX*',
+'*/.DS_Store',
+]
+
+def blacklist(file_list):
+ result = []
+ for file_name in file_list:
+ match = False
+ for blacklist_rule in BLACKLIST_RULES:
+ match = fnmatch.fnmatch(file_name, blacklist_rule)
+ if match:
+ break
+ if not match:
+ result.append(file_name)
+ return result
+
+def unzip(zip_file, target_dir):
+ zip_list = []
+ if os.path.isdir(zip_file):
+ base_dir, sdk_dir = os.path.split(zip_file.replace('\\', '/').rstrip('/'))
+ base_dir_length = len(base_dir) + 1 if base_dir else 0
+ if not 'T9Write' in sdk_dir:
+ print("Error: The input directory name '" + sdk_dir + "' does not contain 'T9Write'.")
+ print("Please unzip the file to a directory named after the zip file and try again.")
+ return zip_list
+ for root, dirs, files in os.walk(zip_file):
+ for file_name in files:
+ sub_dir = root[base_dir_length:]
+ dst_dir = os.path.join(target_dir, sub_dir)
+ if not os.path.exists(dst_dir):
+ os.makedirs(dst_dir)
+ shutil.copy2(os.path.join(root, file_name), dst_dir)
+ os.chmod(os.path.join(dst_dir, file_name), 0o644)
+ zip_list.append(os.path.join(sub_dir, file_name).replace('\\', '/'))
+ return zip_list
+ with zipfile.ZipFile(zip_file, 'r') as z:
+ zip_list = sorted(blacklist(z.namelist()))
+ zip_basename = os.path.splitext(os.path.basename(zip_file))[0]
+ if zip_list and zip_basename in zip_list[0]:
+ zip_basename = ''
+ zip_list = [os.path.join(zip_basename, zip_name).replace('\\', '/') for zip_name in zip_list]
+ z.extractall(os.path.join(target_dir, zip_basename))
+ return zip_list
+
+def match_file_list(file_list, base_dir, fnpattern):
+ result_list = [file_name for file_name in file_list \
+ if fnmatch.fnmatch(file_name, fnpattern) and \
+ os.path.isfile(os.path.join(base_dir, file_name))]
+ for file_name in result_list:
+ file_list.remove(file_name)
+ return result_list
+
+def unpack(zip_list, zip_dir, out_dir):
+ if not zip_list:
+ return
+ for unpack_rules in UNPACK_RULES:
+ process_unpack_rules(zip_list, zip_dir, out_dir, unpack_rules)
+
+def process_unpack_rules(zip_list, zip_dir, out_dir, unpack_rules):
+ for (target_dir, match_rules) in unpack_rules.items():
+ for match_rule in match_rules:
+ # Match
+ match_rule_group = match_rule if isinstance(match_rule, list) else [match_rule]
+ match_group_candidates = [match_file_list(zip_list, zip_dir, match_rule_group[0])]
+ if len(match_rule_group) > 1:
+ while len(match_group_candidates[0]) > 0:
+ match_group0_candidate = match_group_candidates[0][0]
+ all_sub_groups_match = True
+ for sub_group_rule in match_rule_group[1:]:
+ fnpattern = os.path.join(os.path.dirname(match_group0_candidate), sub_group_rule).replace('\\', '/')
+ sub_group_candidates = match_file_list(zip_list, zip_dir, fnpattern)
+ if not sub_group_candidates:
+ all_sub_groups_match = False
+ break
+ match_group_candidates.append(sub_group_candidates)
+ if all_sub_groups_match:
+ match_group_candidates[0] = [match_group0_candidate]
+ break
+ else:
+ match_group_candidates = [match_group_candidates[0][1:]]
+
+ # Copy
+ if match_group_candidates:
+ for match_group_candidate in match_group_candidates:
+ for zip_name in match_group_candidate:
+ dst_dir = os.path.join(out_dir, target_dir)
+ if not os.path.exists(dst_dir):
+ os.makedirs(dst_dir)
+ src = os.path.join(zip_dir, zip_name).replace('\\', '/')
+ dst = os.path.join(dst_dir, os.path.basename(zip_name)).replace('\\', '/')
+ print(zip_name + ' -> ' + dst)
+ shutil.copy2(src, dst)
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Usage: %s <filename.zip> [<target dir>]" % os.path.basename(__file__))
+ exit()
+
+ out_dir = os.path.join(sys.path[0], 'sdk') if len(sys.argv) == 2 else sys.argv[2]
+ zip_dir = tempfile.mkdtemp()
+
+ try:
+ unpack(unzip(sys.argv[1], zip_dir), zip_dir, out_dir)
+ except Exception as e:
+ print(e)
+ finally:
+ shutil.rmtree(zip_dir)
diff --git a/src/plugins/cerence/xt9/CMakeLists.txt b/src/plugins/cerence/xt9/CMakeLists.txt
new file mode 100644
index 00000000..7f4bad45
--- /dev/null
+++ b/src/plugins/cerence/xt9/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(plugin)
+add_subdirectory(xt9common)
diff --git a/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/ja_JP/main.qml b/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/ja_JP/main.qml
new file mode 100644
index 00000000..155d801e
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/ja_JP/main.qml
@@ -0,0 +1,673 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Components
+
+KeyboardLayoutLoader {
+ function createInputMethod() {
+ return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; JapaneseInputMethod {}', parent, "japaneseInputMethod")
+ }
+ sourceComponent: {
+ switch (InputContext.inputEngine.inputMode) {
+ case InputEngine.Katakana:
+ return katakana9key
+ case InputEngine.Hiragana:
+ return hiragana9key
+ case InputEngine.FullwidthLatin:
+ return fullWidthQwerty
+ default:
+ return qwerty
+ }
+ }
+ Component {
+ id: hiragana9key
+ KeyboardLayout {
+ KeyboardRow {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignHCenter
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 1
+ Key {
+ text: "~"
+ alternativeKeys: "~『』「」()〔〕〈〉《》【】"
+ highlighted: true
+ }
+ Key {
+ text: "@"
+ alternativeKeys: "@#$%^&*()=<>,.:;!?~"
+ highlighted: true
+ }
+ InputModeKey {}
+ SymbolModeKey {}
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 30
+ KeyboardRow {
+ FillerKey {}
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 3
+ KeyboardRow {
+ FlickKey {
+ text: "あ"
+ alternativeKeys: "あいうえお"
+ }
+ FlickKey {
+ text: "か"
+ alternativeKeys: "かきくけこ"
+ }
+ FlickKey {
+ text: "さ"
+ alternativeKeys: "さしすせそ"
+ }
+ }
+ KeyboardRow {
+ FlickKey {
+ text: "た"
+ alternativeKeys: "たちつてと"
+ }
+ FlickKey {
+ text: "な"
+ alternativeKeys: "なにぬねの"
+ }
+ FlickKey {
+ text: "は"
+ alternativeKeys: "はひふへほ"
+ }
+ }
+ KeyboardRow {
+ FlickKey {
+ text: "ま"
+ alternativeKeys: "まみむめも"
+ }
+ FlickKey {
+ text: "や"
+ alternativeKeys: "や(ゆ)よ"
+ }
+ FlickKey {
+ text: "ら"
+ alternativeKeys: "らりるれろ"
+ }
+ }
+ KeyboardRow {
+ readonly property bool modifyKeyEnabled: InputContext.inputEngine.inputMethod !== null &&
+ InputContext.inputEngine.inputMethod.hasOwnProperty("modifyKeyEnabled") &&
+ InputContext.inputEngine.inputMethod.modifyKeyEnabled
+ FlickKey {
+ visible: parent.modifyKeyEnabled
+ text: "小"
+ alternativeKeys: "小\u3099\u309A"
+ }
+ Key {
+ visible: !parent.modifyKeyEnabled
+ key: Qt.Key_Comma
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ }
+ FlickKey {
+ text: "わ"
+ alternativeKeys: "わをんー〜"
+ }
+ FlickKey {
+ text: "。"
+ alternativeKeys: "。,!:?"
+ }
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 30
+ KeyboardRow {
+ FillerKey {}
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 1
+ BackspaceKey {}
+ Key {
+ text: "\u3000"
+ displayText: "\u2423"
+ repeat: true
+ showPreview: false
+ key: Qt.Key_Space
+ highlighted: true
+ }
+ HideKeyboardKey {
+ visible: true
+ }
+ EnterKey {}
+ }
+ }
+ }
+ }
+ Component {
+ id: katakana9key
+ KeyboardLayout {
+ KeyboardRow {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignHCenter
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 1
+ Key {
+ text: "~"
+ alternativeKeys: "~『』「」()〔〕〈〉《》【】"
+ highlighted: true
+ }
+ Key {
+ text: "@"
+ alternativeKeys: "@#$%^&*()=<>,.:;!?~"
+ highlighted: true
+ }
+ InputModeKey {}
+ SymbolModeKey {}
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 30
+ KeyboardRow {
+ FillerKey {}
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 3
+ KeyboardRow {
+ FlickKey {
+ text: "ア"
+ alternativeKeys: "アイウエオ"
+ }
+ FlickKey {
+ text: "カ"
+ alternativeKeys: "カキクケコ"
+ }
+ FlickKey {
+ text: "サ"
+ alternativeKeys: "サシスセソ"
+ }
+ }
+ KeyboardRow {
+ FlickKey {
+ text: "タ"
+ alternativeKeys: "タチツテト"
+ }
+ FlickKey {
+ text: "ナ"
+ alternativeKeys: "ナニヌネノ"
+ }
+ FlickKey {
+ text: "ハ"
+ alternativeKeys: "ハヒフヘホ"
+ }
+ }
+ KeyboardRow {
+ FlickKey {
+ text: "マ"
+ alternativeKeys: "マミムメモ"
+ }
+ FlickKey {
+ text: "ヤ"
+ alternativeKeys: "ヤ(ユ)ヨ"
+ }
+ FlickKey {
+ text: "ラ"
+ alternativeKeys: "ラリルレロ"
+ }
+ }
+ KeyboardRow {
+ readonly property bool modifyKeyEnabled: InputContext.inputEngine.inputMethod !== null &&
+ InputContext.inputEngine.inputMethod.hasOwnProperty("modifyKeyEnabled") &&
+ InputContext.inputEngine.inputMethod.modifyKeyEnabled
+ FlickKey {
+ visible: parent.modifyKeyEnabled
+ text: "小"
+ alternativeKeys: "小\u3099\u309A"
+ }
+ Key {
+ visible: !parent.modifyKeyEnabled
+ key: Qt.Key_Comma
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ }
+ FlickKey {
+ text: "ワ"
+ alternativeKeys: "ワヲンー〜"
+ }
+ FlickKey {
+ text: "。"
+ alternativeKeys: "。,!:?"
+ }
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 30
+ KeyboardRow {
+ FillerKey {}
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 1
+ BackspaceKey {}
+ Key {
+ text: "\u3000"
+ displayText: "\u2423"
+ repeat: true
+ showPreview: false
+ key: Qt.Key_Space
+ highlighted: true
+ }
+ HideKeyboardKey {
+ visible: true
+ }
+ EnterKey {}
+ }
+ }
+ }
+ }
+ Component {
+ id: qwerty
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Key {
+ key: Qt.Key_Q
+ text: "q"
+ }
+ Key {
+ id: normalKey
+ key: Qt.Key_W
+ text: "w"
+ }
+ Key {
+ key: Qt.Key_E
+ text: "e"
+ }
+ Key {
+ key: Qt.Key_R
+ text: "r"
+ }
+ Key {
+ key: Qt.Key_T
+ text: "t"
+ }
+ Key {
+ key: Qt.Key_Y
+ text: "y"
+ }
+ Key {
+ key: Qt.Key_U
+ text: "u"
+ }
+ Key {
+ key: Qt.Key_I
+ text: "i"
+ }
+ Key {
+ key: Qt.Key_O
+ text: "o"
+ }
+ Key {
+ key: Qt.Key_P
+ text: "p"
+ }
+ }
+ KeyboardRow {
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ FillerKey {
+ }
+ Key {
+ key: Qt.Key_A
+ text: "a"
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ Key {
+ key: Qt.Key_S
+ text: "s"
+ }
+ Key {
+ key: Qt.Key_D
+ text: "d"
+ }
+ Key {
+ key: Qt.Key_F
+ text: "f"
+ }
+ Key {
+ key: Qt.Key_G
+ text: "g"
+ }
+ Key {
+ key: Qt.Key_H
+ text: "h"
+ }
+ Key {
+ key: Qt.Key_J
+ text: "j"
+ }
+ Key {
+ key: Qt.Key_K
+ text: "k"
+ }
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ Key {
+ key: Qt.Key_L
+ text: "l"
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ FillerKey {
+ }
+ }
+ }
+ KeyboardRow {
+ ShiftKey {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Z
+ text: "z"
+ }
+ Key {
+ key: Qt.Key_X
+ text: "x"
+ }
+ Key {
+ key: Qt.Key_C
+ text: "c"
+ }
+ Key {
+ key: Qt.Key_V
+ text: "v"
+ }
+ Key {
+ key: Qt.Key_B
+ text: "b"
+ }
+ Key {
+ key: Qt.Key_N
+ text: "n"
+ }
+ Key {
+ key: Qt.Key_M
+ text: "m"
+ }
+ BackspaceKey {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ InputModeKey {
+ enabled: !(InputContext.inputMethodHints & Qt.ImhLatinOnly) && inputModeCount > 1
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ inputModeNameList: [
+ "半角", // InputEngine.InputMode.Latin
+ "", // InputEngine.InputMode.Numeric
+ "", // InputEngine.InputMode.Dialable
+ "", // InputEngine.InputMode.Pinyin
+ "", // InputEngine.InputMode.Cangjie
+ "", // InputEngine.InputMode.Zhuyin
+ "", // InputEngine.InputMode.Hangul
+ "あ", // InputEngine.InputMode.Hiragana
+ "カ", // InputEngine.InputMode.Katakana
+ "全角", // InputEngine.InputMode.FullwidthLatin
+ ]
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3002"
+ alternativeKeys: "\u3001\uFF01\u3002\uFF1F,.?!"
+ smallText: "!?"
+ smallTextVisible: true
+ highlighted: true
+ }
+ EnterKey {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+ Component {
+ id: fullWidthQwerty
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Key {
+ key: Qt.Key_Q
+ text: "\uFF51"
+ }
+ Key {
+ id: normalKey
+ key: Qt.Key_W
+ text: "\uFF57"
+ }
+ Key {
+ key: Qt.Key_E
+ text: "\uFF45"
+ }
+ Key {
+ key: Qt.Key_R
+ text: "\uFF52"
+ }
+ Key {
+ key: Qt.Key_T
+ text: "\uFF54"
+ }
+ Key {
+ key: Qt.Key_Y
+ text: "\uFF59"
+ }
+ Key {
+ key: Qt.Key_U
+ text: "\uFF55"
+ }
+ Key {
+ key: Qt.Key_I
+ text: "\uFF49"
+ }
+ Key {
+ key: Qt.Key_O
+ text: "\uFF4F"
+ }
+ Key {
+ key: Qt.Key_P
+ text: "\uFF50"
+ }
+ }
+ KeyboardRow {
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ FillerKey {
+ }
+ Key {
+ key: Qt.Key_A
+ text: "\uFF41"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ Key {
+ key: Qt.Key_S
+ text: "\uFF53"
+ }
+ Key {
+ key: Qt.Key_D
+ text: "\uFF44"
+ }
+ Key {
+ key: Qt.Key_F
+ text: "\uFF46"
+ }
+ Key {
+ key: Qt.Key_G
+ text: "\uFF47"
+ }
+ Key {
+ key: Qt.Key_H
+ text: "\uFF48"
+ }
+ Key {
+ key: Qt.Key_J
+ text: "\uFF4A"
+ }
+ Key {
+ key: Qt.Key_K
+ text: "\uFF4B"
+ }
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ Key {
+ key: Qt.Key_L
+ text: "\uFF4C"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ FillerKey {
+ }
+ }
+ }
+ KeyboardRow {
+ ShiftKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Z
+ text: "\uFF5A"
+ }
+ Key {
+ key: Qt.Key_X
+ text: "\uFF58"
+ }
+ Key {
+ key: Qt.Key_C
+ text: "\uFF43"
+ }
+ Key {
+ key: Qt.Key_V
+ text: "\uFF56"
+ }
+ Key {
+ key: Qt.Key_B
+ text: "\uFF42"
+ }
+ Key {
+ key: Qt.Key_N
+ text: "\uFF4E"
+ }
+ Key {
+ key: Qt.Key_M
+ text: "\uFF4D"
+ }
+ BackspaceKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ InputModeKey {
+ enabled: !(InputContext.inputMethodHints & Qt.ImhLatinOnly) && inputModeCount > 1
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ inputModeNameList: [
+ "半角", // InputEngine.InputMode.Latin
+ "", // InputEngine.InputMode.Numeric
+ "", // InputEngine.InputMode.Dialable
+ "", // InputEngine.InputMode.Pinyin
+ "", // InputEngine.InputMode.Cangjie
+ "", // InputEngine.InputMode.Zhuyin
+ "", // InputEngine.InputMode.Hangul
+ "あ", // InputEngine.InputMode.Hiragana
+ "カ", // InputEngine.InputMode.Katakana
+ "全角", // InputEngine.InputMode.FullwidthLatin
+ ]
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3002"
+ alternativeKeys: "\u3001\uFF01\u3002\uFF1F,.?!"
+ smallText: "!?"
+ smallTextVisible: true
+ highlighted: true
+ }
+ EnterKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+}
diff --git a/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/zh_CN/main.qml b/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/zh_CN/main.qml
new file mode 100644
index 00000000..be10d434
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/zh_CN/main.qml
@@ -0,0 +1,165 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Components
+
+KeyboardLayout {
+ function createInputMethod() {
+ return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; PinyinInputMethod {}', parent, "main.qml")
+ }
+ sharedLayouts: ['symbols']
+ smallTextVisible: true
+ inputMode: InputEngine.InputMode.Pinyin
+ KeyboardRow {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignHCenter
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 1
+ Key {
+ text: "~"
+ alternativeKeys: "~『』「」()〔〕〈〉《》【】"
+ highlighted: true
+ }
+ Key {
+ text: "@"
+ alternativeKeys: "@#$%^&*()=<>,.:;!?~"
+ highlighted: true
+ }
+ FillerKey {}
+ SymbolModeKey {}
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 30
+ KeyboardRow {
+ FillerKey {}
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 3
+ KeyboardRow {
+ Key {
+ enabled: InputContext.preeditText.length > 0
+ key: Qt.Key_Apostrophe
+ text: "'"
+ displayText: "词"
+ smallText: "1"
+ alternativeKeys: "'1"
+ }
+ Key {
+ key: Qt.Key_2
+ text: "a"
+ displayText: "ABC"
+ smallText: "2"
+ alternativeKeys: "abc2"
+ }
+ Key {
+ key: Qt.Key_3
+ text: "d"
+ displayText: "DEF"
+ smallText: "3"
+ alternativeKeys: "def3"
+ }
+ }
+ KeyboardRow {
+ Key {
+ key: Qt.Key_4
+ text: "g"
+ displayText: "GHI"
+ smallText: "4"
+ alternativeKeys: "ghi4"
+ }
+ Key {
+ key: Qt.Key_5
+ text: "j"
+ displayText: "JKL"
+ smallText: "5"
+ alternativeKeys: "jkl5"
+ }
+ Key {
+ key: Qt.Key_6
+ text: "m"
+ displayText: "MNO"
+ smallText: "6"
+ alternativeKeys: "mno6"
+ }
+ }
+ KeyboardRow {
+ Key {
+ key: Qt.Key_7
+ text: "p"
+ displayText: "PQRS"
+ smallText: "7"
+ alternativeKeys: "pqrs7"
+ }
+ Key {
+ key: Qt.Key_8
+ text: "t"
+ displayText: "TUV"
+ smallText: "8"
+ alternativeKeys: "tuv8"
+ }
+ Key {
+ key: Qt.Key_9
+ text: "w"
+ displayText: "WXYZ"
+ smallText: "9"
+ alternativeKeys: "wxyz9"
+ }
+ }
+ KeyboardRow {
+ Key {
+ key: Qt.Key_Comma
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ }
+ Key {
+ key: Qt.Key_0
+ text: ","
+ alternativeKeys: ",;0"
+ smallText: "0"
+ }
+ Key {
+ text: "。"
+ alternativeKeys: "。?!:"
+ }
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 30
+ KeyboardRow {
+ FillerKey {}
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 1
+ BackspaceKey {}
+ Key {
+ text: "\u3000"
+ displayText: "\u2423"
+ repeat: true
+ showPreview: false
+ key: Qt.Key_Space
+ highlighted: true
+ }
+ HideKeyboardKey {
+ visible: true
+ }
+ EnterKey {}
+ }
+ }
+}
diff --git a/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/zh_TW/main.qml b/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/zh_TW/main.qml
new file mode 100644
index 00000000..9754bdb5
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/9key_layouts/content/layouts/zh_TW/main.qml
@@ -0,0 +1,161 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Components
+
+KeyboardLayout {
+ function createInputMethod() {
+ return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; StrokeInputMethod {}', parent, "main.qml")
+ }
+ sharedLayouts: ['symbols']
+ smallTextVisible: true
+ inputMode: InputEngine.InputMode.Stroke
+ KeyboardRow {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignHCenter
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 1
+ Key {
+ text: "~"
+ alternativeKeys: "~『』「」()〔〕〈〉《》【】"
+ highlighted: true
+ }
+ Key {
+ text: "@"
+ alternativeKeys: "@#$%^&*()=<>,.:;!?~"
+ highlighted: true
+ }
+ FillerKey {}
+ SymbolModeKey {}
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 30
+ KeyboardRow {
+ FillerKey {}
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 3
+ KeyboardRow {
+ Key {
+ key: Qt.Key_1
+ text: "\u0001"
+ displayText: "\u4E00"
+ smallText: "1"
+ alternativeKeys: "\u00011"
+ }
+ Key {
+ key: Qt.Key_2
+ text: "\u0002"
+ displayText: "\u4E28"
+ smallText: "2"
+ alternativeKeys: "\u00022"
+ }
+ Key {
+ key: Qt.Key_3
+ text: "\u0003"
+ displayText: "\u4E3F"
+ smallText: "3"
+ alternativeKeys: "\u00033"
+ }
+ }
+ KeyboardRow {
+ Key {
+ key: Qt.Key_4
+ text: "\u0004"
+ displayText: "\u4E36"
+ smallText: "4"
+ alternativeKeys: "\u00044"
+ }
+ Key {
+ key: Qt.Key_5
+ text: "\u0005"
+ displayText: "\u4E5B"
+ smallText: "5"
+ alternativeKeys: "\u00055"
+ }
+ Key {
+ key: Qt.Key_6
+ text: "\u0006"
+ displayText: "*"
+ smallText: "6"
+ alternativeKeys: "\u00066"
+ }
+ }
+ KeyboardRow {
+ Key {
+ key: Qt.Key_7
+ text: ","
+ smallText: "7"
+ alternativeKeys: ",7"
+ }
+ Key {
+ key: Qt.Key_8
+ text: "!"
+ smallText: "8"
+ alternativeKeys: "!8"
+ }
+ Key {
+ key: Qt.Key_9
+ text: "?"
+ smallText: "9"
+ alternativeKeys: "?9"
+ }
+ }
+ KeyboardRow {
+ Key {
+ key: Qt.Key_Comma
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ }
+ Key {
+ key: Qt.Key_0
+ text: ";"
+ alternativeKeys: ";0"
+ smallText: "0"
+ }
+ Key {
+ text: "。"
+ alternativeKeys: "。:"
+ }
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 30
+ KeyboardRow {
+ FillerKey {}
+ }
+ }
+ KeyboardColumn {
+ Layout.fillWidth: false
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.height / 4 * 1
+ BackspaceKey {}
+ Key {
+ text: "\u3000"
+ displayText: "\u2423"
+ repeat: true
+ showPreview: false
+ key: Qt.Key_Space
+ highlighted: true
+ }
+ HideKeyboardKey {
+ visible: true
+ }
+ EnterKey {}
+ }
+ }
+}
diff --git a/src/plugins/cerence/xt9/plugin/CMakeLists.txt b/src/plugins/cerence/xt9/plugin/CMakeLists.txt
new file mode 100644
index 00000000..4494c68e
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/CMakeLists.txt
@@ -0,0 +1,172 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## QtVirtualKeyboardXt9Plugin Plugin:
+#####################################################################
+
+qt_internal_add_qml_module(qtvkbcerencext9plugin
+ URI "QtQuick.VirtualKeyboard.Plugins.Cerence.XT9"
+ VERSION "${PROJECT_VERSION}"
+ PAST_MAJOR_VERSIONS 2
+ PLUGIN_TARGET qtvkbcerencext9plugin
+ NO_PLUGIN_OPTIONAL
+ DEPENDENCIES
+ QtQuick.VirtualKeyboard/auto
+ SOURCES
+ xt9awinputmethod.cpp xt9awinputmethod_p.h
+ xt9awinputmethodprivate.cpp xt9awinputmethodprivate_p.h
+ xt9cpinputmethod.cpp xt9cpinputmethod_p.h
+ xt9cpinputmethodprivate.cpp xt9cpinputmethodprivate_p.h
+ xt9inputmethod.cpp xt9inputmethod_p.h
+ xt9inputmethodprivate.cpp xt9inputmethodprivate_p.h
+ xt9jinputmethod.cpp xt9jinputmethod_p.h
+ xt9jinputmethodprivate.cpp xt9jinputmethodprivate_p.h
+ xt9kinputmethod.cpp xt9kinputmethod_p.h
+ xt9kinputmethodprivate.cpp xt9kinputmethodprivate_p.h
+ xt9thaiinputmethod.cpp xt9thaiinputmethod_p.h
+ xt9thaiinputmethodprivate.cpp xt9thaiinputmethodprivate_p.h
+ DEFINES
+ QT_ASCII_CAST_WARNINGS
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_FROM_BYTEARRAY
+ QT_NO_CAST_TO_ASCII
+ LIBRARIES
+ Qt::BundledCerencecommon
+ Qt::BundledXt9Common
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::VirtualKeyboardPrivate
+ NO_GENERATE_CPP_EXPORTS
+)
+
+set(qmake_virtualkeyboard_xt9_layouts_resource_files)
+if (QT_FEATURE_vkb_lang_ja_JP)
+ list(APPEND qmake_virtualkeyboard_xt9_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/ja_JP/digits.fallback"
+ "${VKB_LAYOUTS_BASE}/ja_JP/numbers.fallback"
+ "${VKB_LAYOUTS_BASE}/ja_JP/symbols.qml"
+ "${VKB_LAYOUTS_BASE}/ja_JP/dialpad.fallback"
+ )
+endif()
+if (QT_FEATURE_vkb_lang_ko_KR)
+ list(APPEND qmake_virtualkeyboard_xt9_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/ko_KR/digits.fallback"
+ "${VKB_LAYOUTS_BASE}/ko_KR/main.qml"
+ "${VKB_LAYOUTS_BASE}/ko_KR/numbers.fallback"
+ "${VKB_LAYOUTS_BASE}/ko_KR/symbols.qml"
+ "${VKB_LAYOUTS_BASE}/ko_KR/dialpad.fallback"
+ )
+endif()
+if (QT_FEATURE_vkb_lang_th_TH)
+ list(APPEND qmake_virtualkeyboard_xt9_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/th_TH/digits.fallback"
+ "${VKB_LAYOUTS_BASE}/th_TH/main.qml"
+ "${VKB_LAYOUTS_BASE}/th_TH/numbers.fallback"
+ "${VKB_LAYOUTS_BASE}/th_TH/symbols.qml"
+ "${VKB_LAYOUTS_BASE}/th_TH/dialpad.fallback"
+ )
+endif()
+if (QT_FEATURE_vkb_lang_zh_CN)
+ list(APPEND qmake_virtualkeyboard_xt9_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/zh_CN/digits.fallback"
+ "${VKB_LAYOUTS_BASE}/zh_CN/numbers.fallback"
+ "${VKB_LAYOUTS_BASE}/zh_CN/symbols.qml"
+ "${VKB_LAYOUTS_BASE}/zh_CN/dialpad.fallback"
+ )
+endif()
+if (NOT FEATURE_vkb_cerence_xt9_9key_layouts AND QT_FEATURE_vkb_lang_zh_CN)
+ list(APPEND qmake_virtualkeyboard_xt9_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/zh_CN/main.qml"
+ )
+endif()
+if (QT_FEATURE_vkb_lang_zh_TW)
+ list(APPEND qmake_virtualkeyboard_xt9_layouts_resource_files
+ "${VKB_LAYOUTS_BASE}/zh_TW/digits.fallback"
+ "${VKB_LAYOUTS_BASE}/zh_TW/numbers.fallback"
+ "${VKB_LAYOUTS_BASE}/zh_TW/symbols.qml"
+ "${VKB_LAYOUTS_BASE}/zh_TW/dialpad.fallback"
+ )
+endif()
+
+qt_internal_add_resource(qtvkbcerencext9plugin "qmake_virtualkeyboard_xt9_layouts"
+ PREFIX
+ "${VKB_LAYOUTS_PREFIX}"
+ BASE
+ "${VKB_LAYOUTS_BASE}"
+ FILES
+ ${qmake_virtualkeyboard_xt9_layouts_resource_files}
+)
+
+
+set(qmake_virtualkeyboard_xt9_custom_layouts_resource_files)
+if (QT_FEATURE_vkb_lang_ja_JP AND NOT FEATURE_vkb_cerence_xt9_9key_layouts)
+ list(APPEND qmake_virtualkeyboard_xt9_custom_layouts_resource_files
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts/ja_JP/main.qml"
+ )
+endif()
+if (QT_FEATURE_vkb_lang_zh_HK)
+ list(APPEND qmake_virtualkeyboard_xt9_custom_layouts_resource_files
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts/zh_HK/digits.fallback"
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts/zh_HK/numbers.fallback"
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts/zh_HK/symbols.qml"
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts/zh_HK/main.qml"
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts/zh_HK/dialpad.fallback"
+ )
+endif()
+
+qt_internal_add_resource(qtvkbcerencext9plugin "qmake_virtualkeyboard_xt9_custom_layouts"
+ PREFIX
+ "${VKB_LAYOUTS_PREFIX}"
+ BASE
+ "${CMAKE_CURRENT_SOURCE_DIR}/content/layouts"
+ FILES
+ ${qmake_virtualkeyboard_xt9_custom_layouts_resource_files}
+)
+
+if (QT_FEATURE_vkb_lang_zh_TW AND NOT FEATURE_vkb_cerence_xt9_9key_layouts)
+ # Resources:
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/content/layouts/zh_HK/main.qml ${CMAKE_CURRENT_BINARY_DIR}/content/layouts/zh_TW/main.qml COPYONLY)
+ set(qmake_virtualkeyboard_xt9_cangjie_layouts_resource_files
+ "${CMAKE_CURRENT_BINARY_DIR}/content/layouts/zh_TW/main.qml"
+ )
+
+ qt_internal_add_resource(qtvkbcerencext9plugin "qmake_virtualkeyboard_xt9_cangjie_layouts"
+ PREFIX
+ "${VKB_LAYOUTS_PREFIX}"
+ BASE
+ "${CMAKE_CURRENT_BINARY_DIR}/content/layouts"
+ FILES
+ ${qmake_virtualkeyboard_xt9_cangjie_layouts_resource_files}
+ )
+endif()
+
+if (FEATURE_vkb_cerence_xt9_9key_layouts)
+ if (QT_FEATURE_vkb_lang_zh_CN)
+ list(APPEND qmake_virtualkeyboard_xt9_9key_layouts_resource_files
+ "${CMAKE_CURRENT_SOURCE_DIR}/9key_layouts/content/layouts/zh_CN/main.qml"
+ )
+ endif()
+
+ if (QT_FEATURE_vkb_lang_zh_TW)
+ list(APPEND qmake_virtualkeyboard_xt9_9key_layouts_resource_files
+ "${CMAKE_CURRENT_SOURCE_DIR}/9key_layouts/content/layouts/zh_TW/main.qml"
+ )
+ endif()
+
+ if (QT_FEATURE_vkb_lang_ja_JP)
+ list(APPEND qmake_virtualkeyboard_xt9_9key_layouts_resource_files
+ "${CMAKE_CURRENT_SOURCE_DIR}/9key_layouts/content/layouts/ja_JP/main.qml"
+ )
+ endif()
+
+ qt_internal_add_resource(qtvkbcerencext9plugin "qmake_virtualkeyboard_xt9_9key_layouts"
+ PREFIX
+ "${VKB_LAYOUTS_PREFIX}"
+ BASE
+ "${CMAKE_CURRENT_SOURCE_DIR}/9key_layouts/content/layouts"
+ FILES
+ ${qmake_virtualkeyboard_xt9_9key_layouts_resource_files}
+ )
+endif()
diff --git a/src/plugins/cerence/xt9/plugin/content/layouts/ja_JP/main.qml b/src/plugins/cerence/xt9/plugin/content/layouts/ja_JP/main.qml
new file mode 100644
index 00000000..b837d257
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/content/layouts/ja_JP/main.qml
@@ -0,0 +1,927 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Components
+
+KeyboardLayoutLoader {
+ function createInputMethod() {
+ return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; JapaneseInputMethod {}', parent, "main.qml")
+ }
+ sourceComponent: {
+ switch (InputContext.inputEngine.inputMode) {
+ case InputEngine.Katakana:
+ return katakana
+ case InputEngine.Hiragana:
+ return hiragana
+ case InputEngine.FullwidthLatin:
+ return fullWidthQwerty
+ default:
+ return qwerty
+ }
+ }
+ Component {
+ id: hiragana
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Layout.preferredHeight: 3
+ KeyboardColumn {
+ KeyboardRow {
+ smallTextVisible: true
+ Key {
+ text: "\u306C"
+ }
+ Key {
+ id: normalKey
+ text: "\u3075"
+ }
+ Key {
+ text: "\u3042"
+ alternativeKeys: "\u3042\u3041"
+ }
+ Key {
+ text: "\u3046"
+ alternativeKeys: "\u3046\u3045"
+ }
+ Key {
+ text: "\u3048"
+ alternativeKeys: "\u3048\u3047"
+ }
+ Key {
+ text: "\u304A"
+ alternativeKeys: "\u304A\u3049"
+ }
+ Key {
+ text: "\u3084"
+ alternativeKeys: "\u3084\u3083"
+ }
+ Key {
+ text: "\u3086"
+ alternativeKeys: "\u3086\u3085"
+ }
+ Key {
+ text: "\u3088"
+ alternativeKeys: "\u3088\u3087"
+ }
+ Key {
+ text: "\u308F"
+ alternativeKeys: "\u308F\u3092"
+ }
+ Key {
+ text: "\u307B"
+ }
+ Key {
+ text: "\u3078"
+ }
+ }
+ KeyboardRow {
+ smallTextVisible: true
+ Key {
+ text: "\u305F"
+ }
+ Key {
+ text: "\u3066"
+ }
+ Key {
+ text: "\u3044"
+ alternativeKeys: "\u3044\u3043"
+ }
+ Key {
+ text: "\u3059"
+ }
+ Key {
+ text: "\u304B"
+ }
+ Key {
+ text: "\u3093"
+ }
+ Key {
+ text: "\u306A"
+ }
+ Key {
+ text: "\u306B"
+ }
+ Key {
+ text: "\u3089"
+ }
+ Key {
+ text: "\u305B"
+ }
+ Key {
+ text: "\u3099"
+ }
+ Key {
+ text: "\u309A"
+ }
+ }
+ KeyboardRow {
+ Key {
+ text: "\u3061"
+ }
+ Key {
+ text: "\u3068"
+ }
+ Key {
+ text: "\u3057"
+ }
+ Key {
+ text: "\u306F"
+ }
+ Key {
+ text: "\u304D"
+ }
+ Key {
+ text: "\u304F"
+ }
+ Key {
+ text: "\u307E"
+ }
+ Key {
+ text: "\u306E"
+ }
+ Key {
+ text: "\u308A"
+ }
+ Key {
+ text: "\u308C"
+ }
+ Key {
+ text: "\u3051"
+ }
+ Key {
+ text: "\u3080"
+ }
+ }
+ KeyboardRow {
+ smallTextVisible: true
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ FillerKey {
+ }
+ Key {
+ text: "\u3064"
+ alternativeKeys: "\u3064\u3063"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ Key {
+ text: "\u3055"
+ }
+ Key {
+ text: "\u305D"
+ }
+ Key {
+ text: "\u3072"
+ }
+ Key {
+ text: "\u3053"
+ }
+ Key {
+ text: "\u307F"
+ }
+ Key {
+ text: "\u3082"
+ }
+ Key {
+ text: "\u306D"
+ }
+ Key {
+ text: "\u308B"
+ }
+ Key {
+ text: "\u3081"
+ }
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ Key {
+ text: "\u308D"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ FillerKey {
+ }
+ }
+ }
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ InputModeKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ enabled: !(InputContext.inputMethodHints & Qt.ImhLatinOnly) && inputModeCount > 1
+ inputModeNameList: [
+ "半角", // InputEngine.InputMode.Latin
+ "", // InputEngine.InputMode.Numeric
+ "", // InputEngine.InputMode.Dialable
+ "", // InputEngine.InputMode.Pinyin
+ "", // InputEngine.InputMode.Cangjie
+ "", // InputEngine.InputMode.Zhuyin
+ "", // InputEngine.InputMode.Hangul
+ "あ", // InputEngine.InputMode.Hiragana
+ "カ", // InputEngine.InputMode.Katakana
+ "全角", // InputEngine.InputMode.FullwidthLatin
+ ]
+ }
+ ChangeLanguageKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3002"
+ alternativeKeys: "\u3001\uFF01\u3002\uFF1F,.?!"
+ smallText: "!?"
+ smallTextVisible: true
+ highlighted: true
+ }
+ HideKeyboardKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ BackspaceKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ EnterKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+ Component {
+ id: katakana
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Layout.preferredHeight: 3
+ KeyboardColumn {
+ KeyboardRow {
+ smallTextVisible: true
+ Key {
+ text: "\u30CC"
+ }
+ Key {
+ id: normalKey
+ text: "\u30D5"
+ }
+ Key {
+ text: "\u30A2"
+ alternativeKeys: "\u30A2\u30A1"
+ }
+ Key {
+ text: "\u30A6"
+ alternativeKeys: "\u30A6\u30A5"
+ }
+ Key {
+ text: "\u30A8"
+ alternativeKeys: "\u30A8\u30A7"
+ }
+ Key {
+ text: "\u30AA"
+ alternativeKeys: "\u30AA\u30A9"
+ }
+ Key {
+ text: "\u30E4"
+ alternativeKeys: "\u30E4\u30E3"
+ }
+ Key {
+ text: "\u30E6"
+ alternativeKeys: "\u30E6\u30E5"
+ }
+ Key {
+ text: "\u30E8"
+ alternativeKeys: "\u30E8\u30E7"
+ }
+ Key {
+ text: "\u30EF"
+ alternativeKeys: "\u30EF\u30F2"
+ }
+ Key {
+ text: "\u30DB"
+ }
+ Key {
+ text: "\u30D8"
+ }
+ }
+ KeyboardRow {
+ smallTextVisible: true
+ Key {
+ text: "\u30BF"
+ }
+ Key {
+ text: "\u30C6"
+ }
+ Key {
+ text: "\u30A4"
+ alternativeKeys: "\u30A4\u30A3"
+ }
+ Key {
+ text: "\u30B9"
+ }
+ Key {
+ text: "\u30AB"
+ }
+ Key {
+ text: "\u30F3"
+ }
+ Key {
+ text: "\u30CA"
+ }
+ Key {
+ text: "\u30CB"
+ }
+ Key {
+ text: "\u30E9"
+ }
+ Key {
+ text: "\u30BB"
+ }
+ Key {
+ text: "\u3099"
+ }
+ Key {
+ text: "\u309A"
+ }
+ }
+ KeyboardRow {
+ Key {
+ text: "\u30C1"
+ }
+ Key {
+ text: "\u30C8"
+ }
+ Key {
+ text: "\u30B7"
+ }
+ Key {
+ text: "\u30CF"
+ }
+ Key {
+ text: "\u30AD"
+ }
+ Key {
+ text: "\u30AF"
+ }
+ Key {
+ text: "\u30DE"
+ }
+ Key {
+ text: "\u30CE"
+ }
+ Key {
+ text: "\u30EA"
+ }
+ Key {
+ text: "\u30EC"
+ }
+ Key {
+ text: "\u30B1"
+ }
+ Key {
+ text: "\u30E0"
+ }
+ }
+ KeyboardRow {
+ smallTextVisible: true
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ FillerKey {
+ }
+ Key {
+ text: "\u30C4"
+ alternativeKeys: "\u30C4\u30C3"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ Key {
+ text: "\u30B5"
+ }
+ Key {
+ text: "\u30BD"
+ }
+ Key {
+ text: "\u30D2"
+ }
+ Key {
+ text: "\u30B3"
+ }
+ Key {
+ text: "\u30DF"
+ }
+ Key {
+ text: "\u30E2"
+ }
+ Key {
+ text: "\u30CD"
+ }
+ Key {
+ text: "\u30EB"
+ }
+ Key {
+ text: "\u30E1"
+ }
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ Key {
+ text: "\u30ED"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ FillerKey {
+ }
+ }
+ }
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ InputModeKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ enabled: !(InputContext.inputMethodHints & Qt.ImhLatinOnly) && inputModeCount > 1
+ inputModeNameList: [
+ "半角", // InputEngine.InputMode.Latin
+ "", // InputEngine.InputMode.Numeric
+ "", // InputEngine.InputMode.Dialable
+ "", // InputEngine.InputMode.Pinyin
+ "", // InputEngine.InputMode.Cangjie
+ "", // InputEngine.InputMode.Zhuyin
+ "", // InputEngine.InputMode.Hangul
+ "あ", // InputEngine.InputMode.Hiragana
+ "カ", // InputEngine.InputMode.Katakana
+ "全角", // InputEngine.InputMode.FullwidthLatin
+ ]
+ }
+ ChangeLanguageKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3002"
+ alternativeKeys: "\u3001\uFF01\u3002\uFF1F,.?!"
+ smallText: "!?"
+ smallTextVisible: true
+ highlighted: true
+ }
+ HideKeyboardKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ BackspaceKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ EnterKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+ Component {
+ id: qwerty
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Key {
+ key: Qt.Key_Q
+ text: "q"
+ }
+ Key {
+ id: normalKey
+ key: Qt.Key_W
+ text: "w"
+ }
+ Key {
+ key: Qt.Key_E
+ text: "e"
+ }
+ Key {
+ key: Qt.Key_R
+ text: "r"
+ }
+ Key {
+ key: Qt.Key_T
+ text: "t"
+ }
+ Key {
+ key: Qt.Key_Y
+ text: "y"
+ }
+ Key {
+ key: Qt.Key_U
+ text: "u"
+ }
+ Key {
+ key: Qt.Key_I
+ text: "i"
+ }
+ Key {
+ key: Qt.Key_O
+ text: "o"
+ }
+ Key {
+ key: Qt.Key_P
+ text: "p"
+ }
+ }
+ KeyboardRow {
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ FillerKey {
+ }
+ Key {
+ key: Qt.Key_A
+ text: "a"
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ Key {
+ key: Qt.Key_S
+ text: "s"
+ }
+ Key {
+ key: Qt.Key_D
+ text: "d"
+ }
+ Key {
+ key: Qt.Key_F
+ text: "f"
+ }
+ Key {
+ key: Qt.Key_G
+ text: "g"
+ }
+ Key {
+ key: Qt.Key_H
+ text: "h"
+ }
+ Key {
+ key: Qt.Key_J
+ text: "j"
+ }
+ Key {
+ key: Qt.Key_K
+ text: "k"
+ }
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ Key {
+ key: Qt.Key_L
+ text: "l"
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ FillerKey {
+ }
+ }
+ }
+ KeyboardRow {
+ ShiftKey {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Z
+ text: "z"
+ }
+ Key {
+ key: Qt.Key_X
+ text: "x"
+ }
+ Key {
+ key: Qt.Key_C
+ text: "c"
+ }
+ Key {
+ key: Qt.Key_V
+ text: "v"
+ }
+ Key {
+ key: Qt.Key_B
+ text: "b"
+ }
+ Key {
+ key: Qt.Key_N
+ text: "n"
+ }
+ Key {
+ key: Qt.Key_M
+ text: "m"
+ }
+ BackspaceKey {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ InputModeKey {
+ enabled: !(InputContext.inputMethodHints & Qt.ImhLatinOnly) && inputModeCount > 1
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ inputModeNameList: [
+ "半角", // InputEngine.InputMode.Latin
+ "", // InputEngine.InputMode.Numeric
+ "", // InputEngine.InputMode.Dialable
+ "", // InputEngine.InputMode.Pinyin
+ "", // InputEngine.InputMode.Cangjie
+ "", // InputEngine.InputMode.Zhuyin
+ "", // InputEngine.InputMode.Hangul
+ "あ", // InputEngine.InputMode.Hiragana
+ "カ", // InputEngine.InputMode.Katakana
+ "全角", // InputEngine.InputMode.FullwidthLatin
+ ]
+ }
+ ChangeLanguageKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ Layout.preferredWidth: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3002"
+ alternativeKeys: "\u3001\uFF01\u3002\uFF1F,.?!"
+ smallText: "!?"
+ smallTextVisible: true
+ highlighted: true
+ }
+ HideKeyboardKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ EnterKey {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+ Component {
+ id: fullWidthQwerty
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Key {
+ key: Qt.Key_Q
+ text: "\uFF51"
+ }
+ Key {
+ id: normalKey
+ key: Qt.Key_W
+ text: "\uFF57"
+ }
+ Key {
+ key: Qt.Key_E
+ text: "\uFF45"
+ }
+ Key {
+ key: Qt.Key_R
+ text: "\uFF52"
+ }
+ Key {
+ key: Qt.Key_T
+ text: "\uFF54"
+ }
+ Key {
+ key: Qt.Key_Y
+ text: "\uFF59"
+ }
+ Key {
+ key: Qt.Key_U
+ text: "\uFF55"
+ }
+ Key {
+ key: Qt.Key_I
+ text: "\uFF49"
+ }
+ Key {
+ key: Qt.Key_O
+ text: "\uFF4F"
+ }
+ Key {
+ key: Qt.Key_P
+ text: "\uFF50"
+ }
+ }
+ KeyboardRow {
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ FillerKey {
+ }
+ Key {
+ key: Qt.Key_A
+ text: "\uFF41"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ Key {
+ key: Qt.Key_S
+ text: "\uFF53"
+ }
+ Key {
+ key: Qt.Key_D
+ text: "\uFF44"
+ }
+ Key {
+ key: Qt.Key_F
+ text: "\uFF46"
+ }
+ Key {
+ key: Qt.Key_G
+ text: "\uFF47"
+ }
+ Key {
+ key: Qt.Key_H
+ text: "\uFF48"
+ }
+ Key {
+ key: Qt.Key_J
+ text: "\uFF4A"
+ }
+ Key {
+ key: Qt.Key_K
+ text: "\uFF4B"
+ }
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ Key {
+ key: Qt.Key_L
+ text: "\uFF4C"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ FillerKey {
+ }
+ }
+ }
+ KeyboardRow {
+ ShiftKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Z
+ text: "\uFF5A"
+ }
+ Key {
+ key: Qt.Key_X
+ text: "\uFF58"
+ }
+ Key {
+ key: Qt.Key_C
+ text: "\uFF43"
+ }
+ Key {
+ key: Qt.Key_V
+ text: "\uFF56"
+ }
+ Key {
+ key: Qt.Key_B
+ text: "\uFF42"
+ }
+ Key {
+ key: Qt.Key_N
+ text: "\uFF4E"
+ }
+ Key {
+ key: Qt.Key_M
+ text: "\uFF4D"
+ }
+ BackspaceKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ InputModeKey {
+ enabled: !(InputContext.inputMethodHints & Qt.ImhLatinOnly) && inputModeCount > 1
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ inputModeNameList: [
+ "半角", // InputEngine.InputMode.Latin
+ "", // InputEngine.InputMode.Numeric
+ "", // InputEngine.InputMode.Dialable
+ "", // InputEngine.InputMode.Pinyin
+ "", // InputEngine.InputMode.Cangjie
+ "", // InputEngine.InputMode.Zhuyin
+ "", // InputEngine.InputMode.Hangul
+ "あ", // InputEngine.InputMode.Hiragana
+ "カ", // InputEngine.InputMode.Katakana
+ "全角", // InputEngine.InputMode.FullwidthLatin
+ ]
+ }
+ ChangeLanguageKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3001"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\u3002"
+ alternativeKeys: "\u3001\uFF01\u3002\uFF1F,.?!"
+ smallText: "!?"
+ smallTextVisible: true
+ highlighted: true
+ }
+ HideKeyboardKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ EnterKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+}
diff --git a/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/dialpad.fallback b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/dialpad.fallback
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/dialpad.fallback
diff --git a/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/digits.fallback b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/digits.fallback
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/digits.fallback
diff --git a/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/main.qml b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/main.qml
new file mode 100644
index 00000000..e54efca9
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/main.qml
@@ -0,0 +1,168 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Components
+
+KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ function createInputMethod() {
+ return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; CangjieInputMethod {}', parent, "main.qml")
+ }
+ sharedLayouts: ['symbols']
+ smallTextVisible: true
+ inputMode: InputEngine.InputMode.Cangjie
+ KeyboardRow {
+ Key {
+ text: "\u624B"
+ }
+ Key {
+ id: normalKey
+ text: "\u7530"
+ }
+ Key {
+ text: "\u6C34"
+ }
+ Key {
+ text: "\u53E3"
+ }
+ Key {
+ text: "\u5EFF"
+ }
+ Key {
+ text: "\u535C"
+ }
+ Key {
+ text: "\u5C71"
+ }
+ Key {
+ text: "\u6208"
+ }
+ Key {
+ text: "\u4EBA"
+ }
+ Key {
+ text: "\u5FC3"
+ }
+ }
+ KeyboardRow {
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ FillerKey {
+ }
+ Key {
+ text: "\u65E5"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ Key {
+ text: "\u5C38"
+ }
+ Key {
+ text: "\u6728"
+ }
+ Key {
+ text: "\u706B"
+ }
+ Key {
+ text: "\u571F"
+ }
+ Key {
+ text: "\u7AF9"
+ }
+ Key {
+ text: "\u5341"
+ }
+ Key {
+ text: "\u5927"
+ }
+ KeyboardRow {
+ Layout.preferredWidth: functionKeyWidth
+ Layout.fillWidth: false
+ Key {
+ text: "\u4E2D"
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ FillerKey {
+ }
+ }
+ }
+ KeyboardRow {
+ ShiftKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ text: "\u91CD"
+ }
+ Key {
+ text: "\u96E3"
+ }
+ Key {
+ text: "\u91D1"
+ }
+ Key {
+ text: "\u5973"
+ }
+ Key {
+ text: "\u6708"
+ }
+ Key {
+ text: "\u5F13"
+ }
+ Key {
+ text: "\u4E00"
+ }
+ BackspaceKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ ChangeLanguageKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\uFF0C"
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ InputModeKey {
+ visible: InputContext.inputEngine.inputModes.indexOf(InputEngine.InputMode.Zhuyin) !== -1
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "\uFF0E"
+ alternativeKeys: "\uFF1B\u3001\uFF0E\uFF1A\u3002?!"
+ smallText: "!?"
+ smallTextVisible: true
+ highlighted: true
+ }
+ EnterKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+}
diff --git a/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/numbers.fallback b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/numbers.fallback
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/numbers.fallback
diff --git a/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/symbols.qml b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/symbols.qml
new file mode 100644
index 00000000..3205ae53
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/content/layouts/zh_HK/symbols.qml
@@ -0,0 +1,476 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Components
+
+KeyboardLayoutLoader {
+ function createInputMethod() {
+ return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; CangjieInputMethod {}', parent, "symbols.qml")
+ }
+ sharedLayouts: ['main']
+ property int page
+ readonly property int numPages: 3
+ sourceComponent: {
+ switch (page) {
+ case 2: return page3
+ case 1: return page2
+ default: return page1
+ }
+ }
+ Component {
+ id: page1
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Key {
+ key: Qt.Key_1
+ text: "1"
+ }
+ Key {
+ id: normalKey
+ key: Qt.Key_2
+ text: "2"
+ }
+ Key {
+ key: Qt.Key_3
+ text: "3"
+ }
+ Key {
+ key: Qt.Key_4
+ text: "4"
+ }
+ Key {
+ key: Qt.Key_5
+ text: "5"
+ }
+ Key {
+ key: Qt.Key_6
+ text: "6"
+ }
+ Key {
+ key: Qt.Key_7
+ text: "7"
+ }
+ Key {
+ key: Qt.Key_8
+ text: "8"
+ }
+ Key {
+ key: Qt.Key_9
+ text: "9"
+ }
+ Key {
+ key: Qt.Key_0
+ text: "0"
+ }
+ }
+ KeyboardRow {
+ Key {
+ key: Qt.Key_1
+ text: "@"
+ }
+ Key {
+ key: Qt.Key_1
+ text: "#"
+ }
+ Key {
+ key: Qt.Key_1
+ text: "%"
+ }
+ Key {
+ key: Qt.Key_1
+ text: "&"
+ }
+ Key {
+ key: Qt.Key_1
+ text: "*"
+ }
+ Key {
+ key: Qt.Key_1
+ text: "_"
+ }
+ Key {
+ key: Qt.Key_1
+ text: "-"
+ }
+ Key {
+ key: Qt.Key_1
+ text: "+"
+ }
+ Key {
+ key: Qt.Key_1
+ text: "("
+ }
+ Key {
+ key: Qt.Key_1
+ text: ")"
+ }
+ }
+ KeyboardRow {
+ Key {
+ displayText: (page + 1) + "/" + numPages
+ functionKey: true
+ onClicked: page = (page + 1) % numPages
+ highlighted: true
+ }
+ Key {
+ text: "“"
+ }
+ Key {
+ text: "”"
+ }
+ Key {
+ text: "、"
+ }
+ Key {
+ text: ":"
+ }
+ Key {
+ text: ";"
+ }
+ Key {
+ text: "!"
+ }
+ Key {
+ text: "?"
+ }
+ Key {
+ text: "~"
+ }
+ BackspaceKey {
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ displayText: "ABC"
+ }
+ ChangeLanguageKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: ","
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "—"
+ highlighted: true
+ }
+ HideKeyboardKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ EnterKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+ Component {
+ id: page2
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Key {
+ text: "½"
+ alternativeKeys: "½¼¾"
+ }
+ Key {
+ id: normalKey
+ text: "'"
+ }
+ Key {
+ text: "/"
+ }
+ Key {
+ text: "\\"
+ }
+ Key {
+ text: "|"
+ }
+ Key {
+ text: "["
+ }
+ Key {
+ text: "]"
+ }
+ Key {
+ text: "{"
+ }
+ Key {
+ text: "}"
+ }
+ Key {
+ text: "·"
+ }
+ }
+ KeyboardRow {
+ Key {
+ text: "<"
+ }
+ Key {
+ text: ">"
+ }
+ Key {
+ text: ","
+ }
+ Key {
+ text: "."
+ }
+ Key {
+ text: ":"
+ }
+ Key {
+ text: ";"
+ }
+ Key {
+ text: "!"
+ }
+ Key {
+ text: "?"
+ }
+ Key {
+ text: "="
+ }
+ Key {
+ text: "~"
+ }
+ }
+ KeyboardRow {
+ Key {
+ displayText: (page + 1) + "/" + numPages
+ functionKey: true
+ onClicked: page = (page + 1) % numPages
+ highlighted: true
+ }
+ Key {
+ text: "\""
+ }
+ Key {
+ text: "§"
+ }
+ Key {
+ text: "^"
+ }
+ Key {
+ text: "$"
+ }
+ Key {
+ text: "¥"
+ }
+ Key {
+ text: "€"
+ }
+ Key {
+ text: "£"
+ }
+ Key {
+ text: "¢"
+ }
+ BackspaceKey {
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ displayText: "ABC"
+ }
+ ChangeLanguageKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: ","
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "。"
+ highlighted: true
+ }
+ HideKeyboardKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ EnterKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+ Component {
+ id: page3
+ KeyboardLayout {
+ keyWeight: 160
+ readonly property real normalKeyWidth: normalKey.width
+ readonly property real functionKeyWidth: mapFromItem(normalKey, normalKey.width / 2, 0).x
+ KeyboardRow {
+ Key {
+ text: "\"
+ }
+ Key {
+ id: normalKey
+ text: "/"
+ }
+ Key {
+ text: "("
+ }
+ Key {
+ text: ")"
+ }
+ Key {
+ text: "〔"
+ }
+ Key {
+ text: "〕"
+ }
+ Key {
+ text: "〈"
+ }
+ Key {
+ text: "〉"
+ }
+ Key {
+ text: "《"
+ }
+ Key {
+ text: "》"
+ }
+ }
+ KeyboardRow {
+ Key {
+ text: "→"
+ }
+ Key {
+ text: "←"
+ }
+ Key {
+ text: "↑"
+ }
+ Key {
+ text: "↓"
+ }
+ Key {
+ text: "■"
+ }
+ Key {
+ text: "□"
+ }
+ Key {
+ text: "●"
+ }
+ Key {
+ text: "○"
+ }
+ Key {
+ text: "【"
+ }
+ Key {
+ text: "】"
+ }
+ }
+ KeyboardRow {
+ Key {
+ displayText: (page + 1) + "/" + numPages
+ functionKey: true
+ onClicked: page = (page + 1) % numPages
+ highlighted: true
+ }
+ Key {
+ text: "『"
+ }
+ Key {
+ text: "』"
+ }
+ Key {
+ text: "「"
+ }
+ Key {
+ text: "」"
+ }
+ Key {
+ text: "★"
+ }
+ Key {
+ text: "☆"
+ }
+ Key {
+ text: "◆"
+ }
+ Key {
+ text: "◇"
+ }
+ BackspaceKey {
+ }
+ }
+ KeyboardRow {
+ SymbolModeKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ displayText: "ABC"
+ }
+ ChangeLanguageKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ Key {
+ key: Qt.Key_Comma
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: ","
+ smallText: "\u2699"
+ smallTextVisible: keyboard.isFunctionPopupListAvailable()
+ highlighted: true
+ }
+ SpaceKey {
+ }
+ Key {
+ key: Qt.Key_Period
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ text: "…"
+ highlighted: true
+ }
+ HideKeyboardKey {
+ weight: normalKeyWidth
+ Layout.fillWidth: false
+ }
+ EnterKey {
+ weight: functionKeyWidth
+ Layout.fillWidth: false
+ }
+ }
+ }
+ }
+}
diff --git a/src/plugins/cerence/xt9/plugin/xt9awinputmethod.cpp b/src/plugins/cerence/xt9/plugin/xt9awinputmethod.cpp
new file mode 100644
index 00000000..f0794be2
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9awinputmethod.cpp
@@ -0,0 +1,303 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9awinputmethod_p.h"
+#include "xt9awinputmethodprivate_p.h"
+#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
+#include <QtVirtualKeyboard/qvirtualkeyboardobserver.h>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+/*!
+ \class QtVirtualKeyboard::Xt9AwInputMethod
+ \internal
+*/
+
+Xt9AwInputMethod::Xt9AwInputMethod(Xt9AwInputMethodPrivate &dd, QObject *parent) :
+ Xt9InputMethod(dd, parent)
+{
+}
+
+Xt9AwInputMethod::Xt9AwInputMethod(QObject *parent) :
+ Xt9InputMethod(*new Xt9AwInputMethodPrivate(this), parent)
+{
+}
+
+QList<QVirtualKeyboardInputEngine::InputMode> Xt9AwInputMethod::inputModes(const QString &locale)
+{
+ QList<QVirtualKeyboardInputEngine::InputMode> result;
+ bool supportsLatinInputMode = true;
+ switch (QLocale(locale).script()) {
+ case QLocale::GreekScript:
+ result.append(QVirtualKeyboardInputEngine::InputMode::Greek);
+ break;
+ case QLocale::CyrillicScript:
+ if (locale == QLatin1String("uk_UA") || locale == QLatin1String("ru_RU") || locale == QLatin1String("bg_BG")) {
+ result.append(QVirtualKeyboardInputEngine::InputMode::Cyrillic);
+ supportsLatinInputMode = false;
+ }
+ break;
+ case QLocale::ArabicScript:
+ result.append(QVirtualKeyboardInputEngine::InputMode::Arabic);
+ break;
+ case QLocale::HebrewScript:
+ result.append(QVirtualKeyboardInputEngine::InputMode::Hebrew);
+ break;
+ default:
+ break;
+ }
+ if (supportsLatinInputMode)
+ result.append(QVirtualKeyboardInputEngine::InputMode::Latin);
+ result.append(QVirtualKeyboardInputEngine::InputMode::Numeric);
+ return result;
+}
+
+bool Xt9AwInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
+{
+ Q_D(Xt9AwInputMethod);
+
+ return d->init(QLocale(locale), inputMode);
+}
+
+bool Xt9AwInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase)
+{
+ Q_UNUSED(textCase)
+ return true;
+}
+
+bool Xt9AwInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
+{
+ Q_UNUSED(modifiers)
+ Q_D(Xt9AwInputMethod);
+
+ QVirtualKeyboardInputContext *ic = inputContext();
+ const Qt::InputMethodHints inputMethodHints = ic->inputMethodHints();
+ if (inputMethodHints.testFlag(Qt::ImhHiddenText) || inputMethodHints.testFlag(Qt::ImhNoPredictiveText)) {
+ const Qt::KeyboardModifiers mods = (key == Qt::Key_Return) ? Qt::NoModifier : modifiers;
+ inputContext()->sendKeyClick(key, text, mods);
+ return true;
+ }
+
+ switch (key) {
+ case Qt::Key_Backspace: {
+ const int cursorPosition = ic->cursorPosition();
+ if (cursorPosition > 0 && !d->xt9Ime()->hasActiveInput() &&
+ reselect(cursorPosition, QVirtualKeyboardInputEngine::ReselectFlag::WordBeforeCursor))
+ return true;
+ return d->processBackspace();
+ }
+
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ case Qt::Key_Tab:
+ case Qt::Key_Space: {
+ Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(d);
+ if (d->xt9Ime()->hasActiveInput() && !d->selectionList.isEmpty() && d->defaultListIndex >= 0) {
+ if (key == Qt::Key_Space)
+ d->selectionList.replace(d->defaultListIndex, d->selectionList.at(d->defaultListIndex) + QLatin1String(" "));
+ d->selectionListSelectItem(d->defaultListIndex);
+ if (key == Qt::Key_Space) {
+ d->setAutoSpaceAllowed(false);
+ return true;
+ }
+ return false;
+ }
+
+ update();
+ if (key == Qt::Key_Space) {
+ ic->commit(QLatin1String(" "));
+ d->buildSelectionList();
+ return true;
+ }
+ break;
+ }
+
+ case Qt::Key_Shift:
+ break;
+
+ default:
+ if (text.length() > 0) {
+ const bool autoSpaceAllowed = d->xt9Ime()->hasActiveInput() || d->autoSpaceAllowed;
+ if (text.length() == 1) {
+ if (!d->processKeyBySymbol(text.at(0))) {
+ ic->sendKeyClick(key, text, modifiers);
+ d->setAutoSpaceAllowed(autoSpaceAllowed);
+ }
+ return true;
+ } else {
+ update();
+ d->setAutoSpaceAllowed(true);
+ if (autoSpaceAllowed && d->isAutoSpaceAllowed())
+ ic->commit(QLatin1String(" "));
+ ic->commit(text);
+ d->setAutoSpaceAllowed(autoSpaceAllowed);
+ d->buildSelectionList();
+ return true;
+ }
+ }
+ break;
+ }
+
+ return false;
+}
+
+QList<QVirtualKeyboardSelectionListModel::Type> Xt9AwInputMethod::selectionLists()
+{
+ return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList;
+}
+
+int Xt9AwInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type)
+{
+ Q_UNUSED(type)
+ Q_D(Xt9AwInputMethod);
+ return d->selectionList.size();
+}
+
+QVariant Xt9AwInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role)
+{
+ Q_UNUSED(type)
+ QVariant result;
+ Q_D(Xt9AwInputMethod);
+ switch (role) {
+ case QVirtualKeyboardSelectionListModel::Role::Display:
+ result = QVariant(d->selectionList.at(index));
+ break;
+ case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength:
+ result.setValue(0);
+ break;
+ case QVirtualKeyboardSelectionListModel::Role::Dictionary:
+ {
+ ET9AWWordInfo *wordInfo = nullptr;
+ XT9_API(ET9AWSelLstGetWord, &d->xt9Ime()->sLingInfo, &wordInfo, static_cast<ET9U8>(index));
+ if (wordInfo) {
+ QVirtualKeyboardSelectionListModel::DictionaryType dictionaryType = wordInfo->bWordSource == ET9AWORDSOURCE_CUSTOM ?
+ QVirtualKeyboardSelectionListModel::DictionaryType::User :
+ QVirtualKeyboardSelectionListModel::DictionaryType::Default;
+ result = QVariant(static_cast<int>(dictionaryType));
+ }
+ break;
+ }
+ case QVirtualKeyboardSelectionListModel::Role::CanRemoveSuggestion:
+ {
+ ET9AWWordInfo *wordInfo = nullptr;
+ XT9_API(ET9AWSelLstGetWord, &d->xt9Ime()->sLingInfo, &wordInfo, static_cast<ET9U8>(index));
+ result = QVariant(wordInfo && wordInfo->bIsDeletable != 0);
+ break;
+ }
+ }
+ return result;
+}
+
+void Xt9AwInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index)
+{
+ Q_UNUSED(type)
+ Q_D(Xt9AwInputMethod);
+
+ d->selectionListSelectItem(index);
+}
+
+bool Xt9AwInputMethod::selectionListRemoveItem(QVirtualKeyboardSelectionListModel::Type type, int index)
+{
+ Q_D(Xt9AwInputMethod);
+ Q_UNUSED(type)
+
+ if (index <= 0 || index >= d->selectionList.size())
+ return false;
+
+ QString word = d->selectionList.at(index);
+ d->removeFromDictionary(word);
+ d->buildSelectionList();
+
+ return true;
+}
+
+bool Xt9AwInputMethod::reselect(int cursorPosition, const QVirtualKeyboardInputEngine::ReselectFlags &reselectFlags)
+{
+ Q_D(Xt9AwInputMethod);
+
+ QVirtualKeyboardInputContext *ic = inputContext();
+ if (!ic)
+ return false;
+
+ QString word;
+ const QString surroundingText = ic->surroundingText();
+ int replaceFrom = 0;
+
+ if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordBeforeCursor)) {
+ for (int i = cursorPosition - 1; i >= 0; --i) {
+ QChar c = surroundingText.at(i);
+ if (!d->isValidInputChar(c))
+ break;
+ word.insert(0, c);
+ --replaceFrom;
+ }
+
+ while (replaceFrom < 0 && d->isJoiner(word.at(0))) {
+ word.remove(0, 1);
+ ++replaceFrom;
+ }
+ }
+
+ if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor) && replaceFrom == 0)
+ return false;
+
+ if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAfterCursor)) {
+ for (int i = cursorPosition; i < surroundingText.length(); ++i) {
+ QChar c = surroundingText.at(i);
+ if (!d->isValidInputChar(c))
+ break;
+ word.append(c);
+ }
+
+ while (replaceFrom > -word.length()) {
+ int lastPos = word.length() - 1;
+ if (!d->isJoiner(word.at(lastPos)))
+ break;
+ word.remove(lastPos, 1);
+ }
+ }
+
+ if (word.isEmpty())
+ return false;
+
+ if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor) && replaceFrom == -word.length())
+ return false;
+
+ if (d->isJoiner(word.at(0)))
+ return false;
+
+ if (d->isJoiner(word.at(word.length() - 1)))
+ return false;
+
+ if (!d->reselectWord(word))
+ return false;
+
+ ic->setPreeditText(word, QList<QInputMethodEvent::Attribute>(), replaceFrom, word.length());
+ d->setAutoSpaceAllowed(false);
+
+ return true;
+}
+
+void Xt9AwInputMethod::reset()
+{
+ Q_D(Xt9AwInputMethod);
+
+ d->reset();
+}
+
+void Xt9AwInputMethod::update()
+{
+ Q_D(Xt9AwInputMethod);
+
+ if (d->xt9Ime()->hasActiveInput()) {
+ d->learnWord(d->xt9Ime()->exactWord());
+ inputContext()->commit();
+ }
+
+ reset();
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9awinputmethod_p.h b/src/plugins/cerence/xt9/plugin/xt9awinputmethod_p.h
new file mode 100644
index 00000000..a2eb521b
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9awinputmethod_p.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9AWINPUTMETHOD_P_H
+#define XT9AWINPUTMETHOD_P_H
+
+#include "xt9inputmethod_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9AwInputMethodPrivate;
+
+class Xt9AwInputMethod : public Xt9InputMethod
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(Xt9AwInputMethod)
+ QML_NAMED_ELEMENT(DefaultInputMethod)
+ QML_ADDED_IN_VERSION(2, 0)
+
+protected:
+ Xt9AwInputMethod(Xt9AwInputMethodPrivate &dd, QObject *parent = nullptr);
+
+public:
+ explicit Xt9AwInputMethod(QObject *parent = nullptr);
+
+ QList<QVirtualKeyboardInputEngine::InputMode> inputModes(const QString &locale);
+ bool setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode);
+ bool setTextCase(QVirtualKeyboardInputEngine::TextCase textCase);
+
+ bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers);
+
+ QList<QVirtualKeyboardSelectionListModel::Type> selectionLists();
+ int selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type);
+ QVariant selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role);
+ void selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index);
+ bool selectionListRemoveItem(QVirtualKeyboardSelectionListModel::Type type, int index);
+
+ bool reselect(int cursorPosition, const QVirtualKeyboardInputEngine::ReselectFlags &reselectFlags);
+
+ void reset();
+ void update();
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/cerence/xt9/plugin/xt9awinputmethodprivate.cpp b/src/plugins/cerence/xt9/plugin/xt9awinputmethodprivate.cpp
new file mode 100644
index 00000000..4bb3c691
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9awinputmethodprivate.cpp
@@ -0,0 +1,147 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9awinputmethodprivate_p.h"
+#include "xt9awinputmethod_p.h"
+#include "xt9languagemap.h"
+#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
+#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
+#include <QtVirtualKeyboard/qvirtualkeyboarddictionarymanager.h>
+#include <QtVirtualKeyboard/private/settings_p.h>
+#include <et9api.h>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9AwInputMethodPrivate::Xt9AwInputMethodPrivate(Xt9InputMethod *q, Xt9Ime *xt9Ime, const QString &aDlmFileName) :
+ Xt9InputMethodPrivate(q, xt9Ime, aDlmFileName)
+{
+}
+
+Xt9AwInputMethodPrivate::Xt9AwInputMethodPrivate(Xt9AwInputMethod *q) :
+ Xt9InputMethodPrivate(q, new Xt9AwIme(this), QStringLiteral("xt9aw.dlm"))
+{
+}
+
+void Xt9AwInputMethodPrivate::uninit()
+{
+ Xt9InputMethodPrivate::uninit();
+
+ defaultDictionaryDisabledConnection = QMetaObject::Connection();
+}
+
+void Xt9AwInputMethodPrivate::bindToSettings()
+{
+ Xt9InputMethodPrivate::bindToSettings();
+
+ if (!defaultDictionaryDisabledConnection)
+ defaultDictionaryDisabledConnection = QObjectPrivate::connect(
+ Settings::instance(), &Settings::defaultDictionaryDisabledChanged,
+ this, &Xt9AwInputMethodPrivate::onDefaultDictionaryDisabledChanged);
+}
+
+void Xt9AwInputMethodPrivate::updateLdb()
+{
+ Xt9InputMethodPrivate::updateLdb();
+
+ xt9Ime()->setLdbEnabled(!Settings::instance()->isDefaultDictionaryDisabled());
+}
+
+void Xt9AwInputMethodPrivate::selectionListSelectItem(int index)
+{
+ Q_Q(Xt9AwInputMethod);
+ Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(this);
+
+ if (index >= 0 && index < selectionList.size()) {
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ const QString &selectedWord = selectionList.at(index);
+
+ // Auto space after next word prediction
+ bool wordIsPunct = selectedWord.length() == 1 && selectedWord.at(0).isPunct();
+ QString exactWord = xt9Ime()->exactWord();
+ if (!wordIsPunct && exactWord.isEmpty() && isAutoSpaceAllowed())
+ ic->commit(QLatin1String(" "));
+
+ // Commit selected word
+ xt9Ime()->selectWord(index, true);
+ ic->commit(selectedWord);
+ }
+
+ reset();
+ setAutoSpaceAllowed(true);
+
+ // Next word prediction
+ buildSelectionList();
+}
+
+bool Xt9AwInputMethodPrivate::reselectWord(const QString &word)
+{
+ ET9STATUS eStatus;
+ ET9U8 totalWords;
+ ET9U8 defaultListIndex;
+ ET9BOOL selectedWasAutomatic;
+ ET9BOOL wasFoundInHistory;
+
+ xt9Ime()->cursorMoved();
+
+ eStatus = XT9_API(ET9AWReselectWord,
+ &xt9Ime()->sLingInfo,
+ &xt9Ime()->sKdbInfo,
+ static_cast<const ET9SYMB *>(word.utf16()),
+ static_cast<ET9U16>(word.length()),
+ ET9AWReselectMode_Edit_Retain_Default,
+ &totalWords,
+ &defaultListIndex,
+ &selectedWasAutomatic,
+ &wasFoundInHistory);
+
+ if (eStatus)
+ return false;
+
+ buildSelectionList();
+
+ return true;
+}
+
+void Xt9AwInputMethodPrivate::learnWord(const QString &word)
+{
+ xt9Ime()->noteWordDone(word);
+}
+
+bool Xt9AwInputMethodPrivate::removeFromDictionary(const QString &word)
+{
+ ET9STATUS eStatus = XT9_API(ET9AWDLMDeleteWord,
+ &xt9Ime()->sLingInfo,
+ static_cast<const ET9SYMB *>(word.utf16()),
+ static_cast<ET9U16>(word.length()));
+
+ return !eStatus;
+}
+
+bool Xt9AwInputMethodPrivate::isJoiner(const QChar &c) const
+{
+ if (Xt9InputMethodPrivate::isJoiner(c))
+ return true;
+
+ if (c.isPunct() || c.isSymbol()) {
+ ushort unicode = c.unicode();
+ if (unicode == Qt::Key_Apostrophe || unicode == Qt::Key_Minus)
+ return true;
+ }
+ return false;
+}
+
+ET9U32 Xt9AwInputMethodPrivate::inputModeToET9InputMode(QVirtualKeyboardInputEngine::InputMode aInputMode) const
+{
+ Q_UNUSED(aInputMode)
+ return ET9AWInputMode_Default;
+}
+
+void Xt9AwInputMethodPrivate::onDefaultDictionaryDisabledChanged()
+{
+ xt9Ime()->setLdbEnabled(!Settings::instance()->isDefaultDictionaryDisabled());
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9awinputmethodprivate_p.h b/src/plugins/cerence/xt9/plugin/xt9awinputmethodprivate_p.h
new file mode 100644
index 00000000..d0036691
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9awinputmethodprivate_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9AWINPUTMETHODPRIVATE_P_H
+#define XT9AWINPUTMETHODPRIVATE_P_H
+
+#include "xt9inputmethodprivate_p.h"
+#include "xt9awinputmethod_p.h"
+#include "xt9awime.h"
+#include <QMetaObject>
+#include <QByteArray>
+#include <QLocale>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9AwInputMethodPrivate : public Xt9InputMethodPrivate
+{
+public:
+ Q_DECLARE_PUBLIC(Xt9AwInputMethod)
+
+protected:
+ Xt9AwInputMethodPrivate(Xt9InputMethod *q_ptr, Xt9Ime *xt9Ime, const QString &aDlmFileName);
+
+public:
+ Xt9AwInputMethodPrivate(Xt9AwInputMethod *q_ptr);
+
+ inline Xt9AwIme *xt9Ime() const;
+
+ void uninit() override;
+ void bindToSettings() override;
+ void updateLdb() override;
+ void selectionListSelectItem(int index) override;
+
+ bool reselectWord(const QString &word);
+ void learnWord(const QString &word);
+ bool removeFromDictionary(const QString &word);
+
+ bool isJoiner(const QChar &c) const override;
+ ET9U32 inputModeToET9InputMode(QVirtualKeyboardInputEngine::InputMode aInputMode) const override;
+
+ void onDefaultDictionaryDisabledChanged();
+
+private:
+ QMetaObject::Connection defaultDictionaryDisabledConnection;
+};
+
+Xt9AwIme *Xt9AwInputMethodPrivate::xt9Ime() const
+{
+ return static_cast<Xt9AwIme *>(_xt9Ime.data());
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9AWINPUTMETHODPRIVATE_P_H
diff --git a/src/plugins/cerence/xt9/plugin/xt9cpinputmethod.cpp b/src/plugins/cerence/xt9/plugin/xt9cpinputmethod.cpp
new file mode 100644
index 00000000..f8a59334
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9cpinputmethod.cpp
@@ -0,0 +1,165 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9cpinputmethod_p.h"
+#include "xt9cpinputmethodprivate_p.h"
+#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
+#include <QtVirtualKeyboard/qvirtualkeyboardobserver.h>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+/*!
+ \class QtVirtualKeyboard::Xt9CpInputMethod
+ \internal
+*/
+
+Xt9CpInputMethod::Xt9CpInputMethod(QObject *parent) :
+ Xt9InputMethod(*new Xt9CpInputMethodPrivate(this), parent)
+{
+}
+
+QList<QVirtualKeyboardInputEngine::InputMode> Xt9CpInputMethod::inputModes(const QString &locale)
+{
+ Q_UNUSED(locale)
+ QList<QVirtualKeyboardInputEngine::InputMode> result;
+ result.append(QVirtualKeyboardInputEngine::InputMode::Pinyin);
+ result.append(QVirtualKeyboardInputEngine::InputMode::Cangjie);
+ result.append(QVirtualKeyboardInputEngine::InputMode::Stroke);
+ return result;
+}
+
+bool Xt9CpInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
+{
+ Q_D(Xt9CpInputMethod);
+
+ return d->init(QLocale(locale), inputMode);
+}
+
+bool Xt9CpInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase)
+{
+ Q_UNUSED(textCase)
+ return true;
+}
+
+bool Xt9CpInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
+{
+ Q_UNUSED(modifiers)
+ Q_D(Xt9CpInputMethod);
+
+ switch (key) {
+ case Qt::Key_Backspace:
+ return d->processBackspace();
+
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ case Qt::Key_Tab:
+ case Qt::Key_Space:
+ if (d->xt9Ime()->hasActiveInput() && !d->selectionList.isEmpty() && d->defaultListIndex >= 0) {
+ selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->defaultListIndex);
+ return key == Qt::Key_Space;
+ }
+
+ update();
+ break;
+
+ case Qt::Key_Shift:
+ break;
+
+ case Qt::Key_Apostrophe:
+ d->cycleTones();
+ return true;
+
+ default:
+ if (text.length() > 0) {
+ if (text.length() == 1) {
+ QString symbs = (d->inputMode == QVirtualKeyboardInputEngine::InputMode::Cangjie) ?
+ d->xt9Ime()->getCangjieConverter()->convertFrom(text) : text;
+ if (!d->processKeyBySymbol(symbs.at(0))) {
+ inputContext()->sendKeyClick(key, text, modifiers);
+ }
+ } else {
+ update();
+ inputContext()->commit(text);
+ }
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+QList<QVirtualKeyboardSelectionListModel::Type> Xt9CpInputMethod::selectionLists()
+{
+ return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList;
+}
+
+int Xt9CpInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type)
+{
+ Q_UNUSED(type)
+ Q_D(Xt9CpInputMethod);
+ return d->selectionList.size();
+}
+
+QVariant Xt9CpInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role)
+{
+ QVariant result;
+ Q_D(Xt9CpInputMethod);
+ switch (role) {
+ case QVirtualKeyboardSelectionListModel::Role::Display:
+ result = QVariant(d->selectionList.at(index));
+ break;
+ case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength:
+ result.setValue(0);
+ break;
+ default:
+ result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role);
+ break;
+ }
+ return result;
+}
+
+void Xt9CpInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index)
+{
+ Q_UNUSED(type)
+ Q_D(Xt9CpInputMethod);
+
+ d->selectionListSelectItem(index);
+}
+
+void Xt9CpInputMethod::reset()
+{
+ Q_D(Xt9CpInputMethod);
+
+ d->reset();
+}
+
+void Xt9CpInputMethod::update()
+{
+ Q_D(Xt9CpInputMethod);
+
+ if (d->xt9Ime()->hasActiveInput() && !d->selectionList.isEmpty() && d->defaultListIndex >= 0) {
+ d->selectionListSelectItem(d->defaultListIndex);
+ }
+
+ inputContext()->clear();
+
+ d->reset();
+}
+
+CangjieInputMethod::CangjieInputMethod(QObject *parent) :
+ Xt9CpInputMethod(parent)
+{
+
+}
+
+StrokeInputMethod::StrokeInputMethod(QObject *parent) :
+ Xt9CpInputMethod(parent)
+{
+
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9cpinputmethod_p.h b/src/plugins/cerence/xt9/plugin/xt9cpinputmethod_p.h
new file mode 100644
index 00000000..6486c662
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9cpinputmethod_p.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9CPINPUTMETHOD_P_H
+#define XT9CPINPUTMETHOD_P_H
+
+#include "xt9inputmethod_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9CpInputMethodPrivate;
+
+class Xt9CpInputMethod : public Xt9InputMethod
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(Xt9CpInputMethod)
+ QML_NAMED_ELEMENT(PinyinInputMethod)
+ QML_ADDED_IN_VERSION(2, 0)
+
+public:
+ explicit Xt9CpInputMethod(QObject *parent = nullptr);
+
+ QList<QVirtualKeyboardInputEngine::InputMode> inputModes(const QString &locale);
+ bool setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode);
+ bool setTextCase(QVirtualKeyboardInputEngine::TextCase textCase);
+
+ bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers);
+
+ QList<QVirtualKeyboardSelectionListModel::Type> selectionLists();
+ int selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type);
+ QVariant selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role);
+ void selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index);
+
+ void reset();
+ void update();
+};
+
+class CangjieInputMethod : public Xt9CpInputMethod
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(CangjieInputMethod)
+public:
+ explicit CangjieInputMethod(QObject *parent = nullptr);
+};
+
+class StrokeInputMethod : public Xt9CpInputMethod
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(StrokeInputMethod)
+public:
+ explicit StrokeInputMethod(QObject *parent = nullptr);
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/cerence/xt9/plugin/xt9cpinputmethodprivate.cpp b/src/plugins/cerence/xt9/plugin/xt9cpinputmethodprivate.cpp
new file mode 100644
index 00000000..f42c1981
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9cpinputmethodprivate.cpp
@@ -0,0 +1,134 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9cpinputmethodprivate_p.h"
+#include "xt9cpinputmethod_p.h"
+#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
+#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
+#include <QtVirtualKeyboard/qvirtualkeyboardobserver.h>
+#include <et9api.h>
+#include "xt9languagemap.h"
+#include <QDateTime>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9CpInputMethodPrivate::Xt9CpInputMethodPrivate(Xt9CpInputMethod *q) :
+ Xt9InputMethodPrivate(q, new Xt9CpIme(this), QStringLiteral("xt9cp.dlm"))
+{
+}
+
+void Xt9CpInputMethodPrivate::cycleTones()
+{
+ if (!xt9Ime()->sWordSymbInfo.wNumSymbs)
+ return;
+
+ const ET9SYMB lastSymb = xt9Ime()->lastSymb();
+ ET9U8 tone = ET9CPSymToCPTone(lastSymb);
+
+ if (lastSymb == ET9CPSYLLABLEDELIMITER || tone) {
+ XT9_API(ET9ClearOneSymb, &xt9Ime()->sWordSymbInfo);
+
+ for (tone++; tone < 6; tone++) {
+ if (xt9Ime()->addTone(static_cast<ET9CPSYMB>(tone + ET9CPTONE1 - 1)))
+ break;
+ }
+
+ if (tone == 6) {
+ for (tone = 1; tone < 6; tone++) {
+ if (xt9Ime()->addTone(static_cast<ET9CPSYMB>(tone + ET9CPTONE1 - 1)))
+ break;
+ }
+ }
+
+ if (tone == 6) {
+ /* cycle back to delimiter */
+ XT9_API(ET9AddExplicitSymb, &xt9Ime()->sWordSymbInfo, ET9CPSYLLABLEDELIMITER, 0, ET9NOSHIFT, ET9_NO_ACTIVE_INDEX);
+ }
+ } else { /* first call, add delim */
+ XT9_API(ET9AddExplicitSymb, &xt9Ime()->sWordSymbInfo, ET9CPSYLLABLEDELIMITER, 0, ET9NOSHIFT, ET9_NO_ACTIVE_INDEX);
+ }
+
+ buildSelectionList();
+ updatePreeditText();
+}
+
+void Xt9CpInputMethodPrivate::updatePreeditText()
+{
+ QString spell = xt9Ime()->spell();
+ if (!spell.isEmpty()) {
+ Q_Q(Xt9InputMethod);
+ if (inputMode == QVirtualKeyboardInputEngine::InputMode::Cangjie)
+ spell = xt9Ime()->getCangjieConverter()->convertTo(spell);
+ q->inputContext()->setPreeditText(spell);
+ } else {
+ Xt9InputMethodPrivate::updatePreeditText();
+ }
+}
+
+void Xt9CpInputMethodPrivate::selectionListSelectItem(int index)
+{
+ Q_Q(Xt9CpInputMethod);
+ Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(this);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+
+ ET9STATUS eStatus = xt9Ime()->selectWord(index);
+ if (eStatus == ET9STATUS_SELECTED_CHINESE_COMPONENT) {
+ processKeyBySymbol(selectionList.at(index).at(0));
+ } else {
+ ic->commit(selectionList.at(index));
+ if (eStatus == ET9STATUS_ALL_SYMB_SELECTED) {
+ reset();
+ } else {
+ updatePreeditText();
+ }
+ buildSelectionList();
+ }
+}
+
+bool Xt9CpInputMethodPrivate::isValidInputChar(const QChar &c) const
+{
+ const ushort ucs = c.unicode();
+ if (inputMode == QVirtualKeyboardInputEngine::InputMode::Stroke) {
+ return ET9CPIsStrokeSymbol(ucs) || ET9CPIsComponent(&xt9Ime()->sLingInfo, ucs);
+ }
+
+ return Xt9InputMethodPrivate::isValidInputChar(c);
+}
+
+void Xt9CpInputMethodPrivate::reset()
+{
+ xt9Ime()->commitSelection();
+
+ Xt9InputMethodPrivate::reset();
+}
+
+bool Xt9CpInputMethodPrivate::maybeInsertSpaceBeforeNextInputSymbol(QChar symbol) const
+{
+ Q_UNUSED(symbol)
+ return false;
+}
+
+ET9U32 Xt9CpInputMethodPrivate::inputModeToET9InputMode(QVirtualKeyboardInputEngine::InputMode aInputMode) const
+{
+ switch (aInputMode) {
+ case QVirtualKeyboardInputEngine::InputMode::Pinyin:
+ return ET9CPMODE_PINYIN;
+
+ case QVirtualKeyboardInputEngine::InputMode::Cangjie:
+ return ET9CPMODE_CANGJIE;
+
+ case QVirtualKeyboardInputEngine::InputMode::Zhuyin:
+ return ET9CPMODE_BPMF;
+
+ case QVirtualKeyboardInputEngine::InputMode::Stroke:
+ return ET9CPMODE_STROKE;
+
+ default:
+ return ET9CPMODE_PINYIN;
+ }
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9cpinputmethodprivate_p.h b/src/plugins/cerence/xt9/plugin/xt9cpinputmethodprivate_p.h
new file mode 100644
index 00000000..520d37c3
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9cpinputmethodprivate_p.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9CPINPUTMETHODPRIVATE_P_H
+#define XT9CPINPUTMETHODPRIVATE_P_H
+
+#include "xt9inputmethodprivate_p.h"
+#include "xt9cpinputmethod_p.h"
+#include "xt9cpime.h"
+#include <QMetaObject>
+#include <QByteArray>
+#include <QLocale>
+#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9CpInputMethodPrivate : public Xt9InputMethodPrivate
+{
+ Q_DECLARE_PUBLIC(Xt9CpInputMethod)
+public:
+ Xt9CpInputMethodPrivate(Xt9CpInputMethod *q_ptr);
+
+ inline Xt9CpIme *xt9Ime() const;
+
+ void cycleTones();
+ void updatePreeditText() override;
+ void selectionListSelectItem(int index) override;
+ bool isValidInputChar(const QChar &c) const override;
+ void reset() override;
+
+ bool maybeInsertSpaceBeforeNextInputSymbol(QChar symbol) const override;
+ ET9U32 inputModeToET9InputMode(QVirtualKeyboardInputEngine::InputMode aInputMode) const override;
+};
+
+Xt9CpIme *Xt9CpInputMethodPrivate::xt9Ime() const
+{
+ return static_cast<Xt9CpIme *>(_xt9Ime.data());
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9CPINPUTMETHODPRIVATE_P_H
diff --git a/src/plugins/cerence/xt9/plugin/xt9inputmethod.cpp b/src/plugins/cerence/xt9/plugin/xt9inputmethod.cpp
new file mode 100644
index 00000000..3211f292
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9inputmethod.cpp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9inputmethod_p.h"
+#include "xt9inputmethodprivate_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+/*!
+ \class QtVirtualKeyboard::Xt9InputMethod
+ \internal
+*/
+
+Xt9InputMethod::Xt9InputMethod(Xt9InputMethodPrivate &dd, QObject *parent) :
+ QVirtualKeyboardAbstractInputMethod(dd, parent)
+{
+ Q_D(Xt9InputMethod);
+ d->sysInit();
+}
+
+void Xt9InputMethod::clearInputMode()
+{
+ qCDebug(lcXT9) << "clearInputMode";
+ Q_D(Xt9InputMethod);
+ d->uninit();
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9inputmethod_p.h b/src/plugins/cerence/xt9/plugin/xt9inputmethod_p.h
new file mode 100644
index 00000000..76635b2a
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9inputmethod_p.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9INPUTMETHOD_P_H
+#define XT9INPUTMETHOD_P_H
+
+#include <QtVirtualKeyboard/qvirtualkeyboardabstractinputmethod.h>
+#include "xt9inputmethodprivate_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9InputMethod : public QVirtualKeyboardAbstractInputMethod
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(Xt9InputMethod)
+
+protected:
+ Xt9InputMethod(Xt9InputMethodPrivate &dd, QObject *parent = nullptr);
+
+public:
+ void clearInputMode() override;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/cerence/xt9/plugin/xt9inputmethodprivate.cpp b/src/plugins/cerence/xt9/plugin/xt9inputmethodprivate.cpp
new file mode 100644
index 00000000..2518bbe1
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9inputmethodprivate.cpp
@@ -0,0 +1,603 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9inputmethodprivate_p.h"
+#include "xt9inputmethod_p.h"
+#include "xt9languagemap.h"
+#include "xt9dbfile.h"
+#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
+#include <QtVirtualKeyboard/qvirtualkeyboardobserver.h>
+#include <QtVirtualKeyboard/qvirtualkeyboarddictionarymanager.h>
+#include <QtVirtualKeyboard/qvirtualkeyboarddictionary.h>
+#include <QtVirtualKeyboard/private/settings_p.h>
+#include <QDateTime>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9InputMethodPrivate::Xt9InputMethodPrivate(Xt9InputMethod *q_ptr, Xt9Ime *xt9Ime, const QString &aDlmFileName) :
+ QVirtualKeyboardAbstractInputMethodPrivate(),
+ q_ptr(q_ptr),
+ _xt9Ime(xt9Ime),
+ inputMode(QVirtualKeyboardInputEngine::InputMode::Latin),
+ selectionListUpdateCount(0),
+ defaultListIndex(ET9_NO_ACTIVE_INDEX),
+ autoSpaceAllowed(false),
+ initDone(false),
+ dlmFileName(aDlmFileName)
+{
+#ifdef HAVE_XT9_RESOURCE
+ Q_INIT_RESOURCE(qmake_cerencecommondata_db);
+#endif
+}
+
+void Xt9InputMethodPrivate::sysInit()
+{
+ xt9Ime()->sysInit();
+ bindToSettings();
+ xt9Ime()->setWorkingDirectory(Settings::instance()->userDataPath());
+ xt9Ime()->removeAllIndexes();
+}
+
+bool Xt9InputMethodPrivate::init(QLocale aLocale, QVirtualKeyboardInputEngine::InputMode aInputMode)
+{
+ initDone = true;
+
+ xt9Ime()->setWorkingDirectory(Settings::instance()->userDataPath());
+
+ bindToDictionaryManager();
+ bindToSettings();
+ bindToKeyboard();
+
+ this->locale = aLocale;
+ this->inputMode = aInputMode;
+
+ updateLdb();
+ updateShiftState();
+ updateLayout();
+ updateDlm();
+ updateDynamicDictionaries();
+
+ return true;
+}
+
+void Xt9InputMethodPrivate::uninit()
+{
+ Q_Q(Xt9InputMethod);
+
+ initDone = false;
+
+ QObject::disconnect(availableDictionariesChangedConnection);
+ QObject::disconnect(activeDictionariesChangedConnection);
+ QObject::disconnect(userDataPathChangedConnection);
+ QObject::disconnect(userDataResetConnection);
+ QObject::disconnect(layoutChangedConnection);
+ QObject::disconnect(shiftActiveChangedConnection);
+ QObject::disconnect(capsLockActiveChangedConnection);
+ QObject::disconnect(inputMethodHintsChangedConnection);
+
+ dlmDeactivate();
+
+ removeAllDynamicDictionaries();
+
+ xt9Ime()->uninit();
+}
+
+void Xt9InputMethodPrivate::bindToDictionaryManager()
+{
+ if (!availableDictionariesChangedConnection)
+ availableDictionariesChangedConnection = QObjectPrivate::connect(QVirtualKeyboardDictionaryManager::instance(),
+ &QVirtualKeyboardDictionaryManager::availableDictionariesChanged,
+ this, &Xt9InputMethodPrivate::onAvailableDynamicDictionariesChanged);
+
+ if (!activeDictionariesChangedConnection)
+ activeDictionariesChangedConnection = QObjectPrivate::connect(QVirtualKeyboardDictionaryManager::instance(),
+ &QVirtualKeyboardDictionaryManager::activeDictionariesChanged,
+ this, &Xt9InputMethodPrivate::onActiveDynamicDictionariesChanged);
+}
+
+void Xt9InputMethodPrivate::bindToSettings()
+{
+ if (!userDataPathChangedConnection)
+ userDataPathChangedConnection = QObjectPrivate::connect(
+ Settings::instance(), &Settings::userDataPathChanged,
+ this, &Xt9InputMethodPrivate::onUserDataPathChanged);
+
+ if (!userDataResetConnection)
+ userDataResetConnection = QObjectPrivate::connect(
+ Settings::instance(), &Settings::userDataReset,
+ this, &Xt9InputMethodPrivate::onUserDataReset);
+}
+
+void Xt9InputMethodPrivate::bindToKeyboard()
+{
+ Q_Q(Xt9InputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (!ic)
+ return;
+
+ if (!layoutChangedConnection)
+ layoutChangedConnection = QObjectPrivate::connect(
+ ic->keyboardObserver(), &QVirtualKeyboardObserver::layoutChanged,
+ this, &Xt9InputMethodPrivate::updateLayout);
+
+ if (!shiftActiveChangedConnection)
+ shiftActiveChangedConnection = QObjectPrivate::connect(
+ ic, &QVirtualKeyboardInputContext::shiftActiveChanged,
+ this, &Xt9InputMethodPrivate::updateShiftState);
+
+ if (!capsLockActiveChangedConnection)
+ capsLockActiveChangedConnection = QObjectPrivate::connect(
+ ic, &QVirtualKeyboardInputContext::capsLockActiveChanged,
+ this, &Xt9InputMethodPrivate::updateShiftState);
+
+ if (!inputMethodHintsChangedConnection)
+ inputMethodHintsChangedConnection = QObjectPrivate::connect(
+ ic, &QVirtualKeyboardInputContext::inputMethodHintsChanged,
+ this, &Xt9InputMethodPrivate::updateDlm);
+}
+
+void Xt9InputMethodPrivate::dlmActivate()
+{
+ const QString userDataPath = Settings::instance()->userDataPath();
+ const QString dlmFile = QStringLiteral("%1/%2").arg(userDataPath).arg(dlmFileName);
+ if (dlm && dlm->fileName() != dlmFile)
+ dlmDeactivate();
+
+ if (!dlm) {
+ if (!userDataPath.isEmpty()) {
+ qCDebug(lcXT9) << "dlmActivate" << dlmFile;
+ dlm.reset(new Xt9DbFile(dlmFile));
+ void *data = dlm->rwData(xt9Ime()->dlmPreferredSize());
+ qint64 size = dlm->size();
+ if (data != nullptr && size > 0) {
+ if (!xt9Ime()->dlmInit(data, size)) {
+ qCWarning(lcXT9) << "Failed to init DLM file - " << dlmFile;
+ dlm.reset();
+ }
+ } else {
+ qCWarning(lcXT9) << "Failed to open DLM file - " << dlmFile;
+ dlm.reset();
+ }
+ }
+ }
+}
+
+void Xt9InputMethodPrivate::dlmDeactivate()
+{
+ if (dlm) {
+ qCDebug(lcXT9) << "dlmDeactivate";
+ xt9Ime()->dlmInit(nullptr, 0);
+ dlm.reset();
+ }
+}
+
+void Xt9InputMethodPrivate::updateLdb()
+{
+ ET9U32 dwFirstLdbNum = Xt9LanguageMap::languageId(locale);
+ ET9U32 eInputMode = inputModeToET9InputMode(inputMode);
+
+ xt9Ime()->ldbInit(dwFirstLdbNum, ET9PLIDNone, eInputMode);
+}
+
+void Xt9InputMethodPrivate::updateDlm()
+{
+ Q_Q(Xt9InputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (ic == nullptr)
+ return;
+
+ const Qt::InputMethodHints inputMethodHints = ic->inputMethodHints();
+ if (!inputMethodHints.testFlag(Qt::ImhHiddenText) && !inputMethodHints.testFlag(Qt::ImhSensitiveData))
+ dlmActivate();
+ else
+ dlmDeactivate();
+}
+
+void Xt9InputMethodPrivate::removeAllDynamicDictionaries()
+{
+ Q_Q(Xt9InputMethod);
+ xt9Ime()->removeAllIndexes();
+
+ QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
+ const QStringList availableDictionaries = dictionaryManager->availableDictionaries();
+ for (const QString &dictionaryName : availableDictionaries) {
+ QVirtualKeyboardDictionary *dictionary = dictionaryManager->dictionary(dictionaryName);
+ dictionary->disconnect(q); // lambdas
+ }
+
+ attachedDynamicDictionaries.clear();
+ dynamicDictionaries.clear();
+ dynamicDictionaryNextId = 0;
+}
+
+void Xt9InputMethodPrivate::updateDynamicDictionaries()
+{
+ onAvailableDynamicDictionariesChanged();
+ onActiveDynamicDictionariesChanged();
+}
+
+void Xt9InputMethodPrivate::updateShiftState()
+{
+ Q_Q(Xt9InputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (ic) {
+ if (ic->isCapsLockActive())
+ xt9Ime()->setCapsLock();
+ else if (ic->isShiftActive())
+ xt9Ime()->setShift();
+ else
+ xt9Ime()->setUnShift();
+ }
+}
+
+bool Xt9InputMethodPrivate::updateLayout()
+{
+ Q_Q(Xt9InputMethod);
+ QVirtualKeyboardInputContext *ic = q->inputContext();
+ if (ic == nullptr)
+ return false;
+
+ QVariantMap vkbLayout = ic->keyboardObserver()->layout().toMap();
+ ET9U32 dwFirstLdbNum = Xt9LanguageMap::languageId(locale);
+ vkbLayout[Xt9KeyboardGenerator::PRIMARY_ID] = static_cast<int>(dwFirstLdbNum & ET9PLIDMASK);
+ vkbLayout[Xt9KeyboardGenerator::SECONDARY_ID] = static_cast<int>(ET9SKIDNone);
+
+ return xt9Ime()->kdbInit(vkbLayout);
+}
+
+void Xt9InputMethodPrivate::updatePreeditText()
+{
+ Q_Q(Xt9InputMethod);
+
+ QString exactWord = xt9Ime()->exactWord();
+ q->inputContext()->setPreeditText(exactWord);
+}
+
+void Xt9InputMethodPrivate::buildSelectionList()
+{
+ ET9STATUS eStatus = ET9STATUS_NONE;
+ buildSelectionList(eStatus);
+}
+
+void Xt9InputMethodPrivate::buildSelectionList(ET9STATUS &eStatus)
+{
+ ET9U16 gestureValue;
+ Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(this);
+
+ eStatus = ET9STATUS_NONE;
+
+ if (xt9Ime()->exactWord().isEmpty()) {
+ // Check if next word prediction is not allowed
+ Q_Q(Xt9InputMethod);
+ const Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
+ if (inputMethodHints.testFlag(Qt::ImhHiddenText) || inputMethodHints.testFlag(Qt::ImhNoPredictiveText)) {
+ selectionList.clear();
+ defaultListIndex = -1;
+ return;
+ }
+
+ // Ensure the buffer context is up-to-date for next word prediction
+ xt9Ime()->cursorMoved();
+ }
+
+ selectionList = xt9Ime()->buildSelectionList(&defaultListIndex, &gestureValue, eStatus);
+}
+
+void Xt9InputMethodPrivate::selectionListUpdate()
+{
+ Q_Q(Xt9InputMethod);
+ emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
+ emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, defaultListIndex);
+}
+
+void Xt9InputMethodPrivate::updatePunctuationBreaking()
+{
+ Q_Q(Xt9InputMethod);
+ Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
+ bool enabled = !inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) &&
+ !inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly);
+
+ if (enabled)
+ XT9_API(ET9SetPunctuationBreaking, &xt9Ime()->sWordSymbInfo);
+ else
+ XT9_API(ET9ClearPunctuationBreaking, &xt9Ime()->sWordSymbInfo);
+}
+
+bool Xt9InputMethodPrivate::processBackspace()
+{
+ if (XT9_API(ET9ClearOneSymb, &xt9Ime()->sWordSymbInfo))
+ return false;
+
+ buildSelectionList();
+ updatePreeditText();
+
+ return true;
+}
+
+bool Xt9InputMethodPrivate::processKeyBySymbol(const QChar &symbol)
+{
+ QString exactWord = xt9Ime()->exactWord();
+ bool addToWord = isValidInputChar(symbol) && (!exactWord.isEmpty() || !isJoiner(symbol));
+ if (!addToWord) {
+ Q_Q(Xt9InputMethod);
+ q->update();
+ return false;
+ }
+
+ if (maybeInsertSpaceBeforeNextInputSymbol(symbol)) {
+ Q_Q(Xt9InputMethod);
+ q->update();
+ q->inputContext()->commit(QLatin1String(" "));
+ }
+
+ if (exactWord.isEmpty()) {
+ updatePunctuationBreaking();
+ updateShiftState();
+ xt9Ime()->cursorMoved();
+ }
+
+ ET9STATUS eStatus;
+ ET9SYMB functionKey = 0;
+ const ET9U8 currIndexInList = defaultListIndex < 0 ?
+ ET9_NO_ACTIVE_INDEX : static_cast<ET9U8>(defaultListIndex);
+ const ET9BOOL bInitialSymCheck = 1;
+ ET9U32 dwTimeMS = static_cast<ET9U32>(QDateTime::currentMSecsSinceEpoch());
+
+ eStatus = XT9_API(ET9KDB_ProcessKeyBySymbol,
+ &xt9Ime()->sKdbInfo,
+ symbol.unicode(),
+ dwTimeMS,
+ currIndexInList,
+ &functionKey,
+ bInitialSymCheck);
+
+ const bool noKey = eStatus == ET9STATUS_NO_KEY;
+ if (noKey) {
+ const ET9INPUTSHIFTSTATE eShiftState = ET9SHIFT_STATE(&xt9Ime()->sWordSymbInfo);
+ eStatus = XT9_API(ET9AddExplicitSymb, &xt9Ime()->sWordSymbInfo, symbol.unicode(), dwTimeMS, eShiftState, 0);
+ } else if (eStatus == ET9STATUS_FULL) {
+ /*
+ Reject input when buffer is full. If we would return false,
+ the input would be added as an explicit symbol to text editor,
+ which is not what is wanted.
+ */
+ return true;
+ } else if (eStatus) {
+ return false;
+ }
+
+ Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(this);
+ buildSelectionList(eStatus);
+ if (eStatus == ET9STATUS_INVALID_INPUT) {
+ /*
+ The symbol rejected as an invalid input:
+ 1. Remove the symbol and rebuild selection list
+ 2. Select the default candidate from selection list and finalize input (update)
+ 3. Start new input with the symbol
+ */
+
+ XT9_API(ET9ClearOneSymb, &xt9Ime()->sWordSymbInfo);
+ buildSelectionList(eStatus);
+
+ Q_Q(Xt9InputMethod);
+ q->update();
+
+ dwTimeMS = static_cast<ET9U32>(QDateTime::currentMSecsSinceEpoch());
+ if (noKey) {
+ const ET9INPUTSHIFTSTATE eShiftState = ET9SHIFT_STATE(&xt9Ime()->sWordSymbInfo);
+ XT9_API(ET9AddExplicitSymb,
+ &xt9Ime()->sWordSymbInfo,
+ symbol.unicode(),
+ dwTimeMS,
+ eShiftState, 0);
+ } else {
+ XT9_API(ET9KDB_ProcessKeyBySymbol,
+ &xt9Ime()->sKdbInfo,
+ symbol.unicode(),
+ dwTimeMS,
+ ET9_NO_ACTIVE_INDEX,
+ &functionKey,
+ bInitialSymCheck);
+ }
+ buildSelectionList();
+ }
+ updatePreeditText();
+
+ return true;
+}
+
+bool Xt9InputMethodPrivate::maybeInsertSpaceBeforeNextInputSymbol(QChar symbol) const
+{
+ Q_UNUSED(symbol)
+
+ QString exactWord = xt9Ime()->exactWord();
+ if (exactWord.isEmpty()) {
+ Q_Q(const Xt9InputMethod);
+
+ if (QVirtualKeyboardInputContext *ic = q->inputContext()) {
+ const QString surroundingText = ic->surroundingText();
+ const int cursorPosition = ic->cursorPosition();
+
+ if (!surroundingText.isEmpty() && cursorPosition == surroundingText.length()) {
+ QChar lastChar = surroundingText.at(cursorPosition - 1);
+
+ if (!lastChar.isSpace() && lastChar != QChar(Qt::Key_Minus) && isAutoSpaceAllowed())
+ return symbol.isLetterOrNumber();
+ }
+ }
+ }
+
+ return false;
+}
+
+void Xt9InputMethodPrivate::setAutoSpaceAllowed(bool value)
+{
+ if (autoSpaceAllowed == value)
+ return;
+
+ autoSpaceAllowed = value;
+ qCDebug(lcXT9) << "setAutoSpaceAllowed():" << value;
+}
+
+bool Xt9InputMethodPrivate::isAutoSpaceAllowed() const
+{
+ Q_Q(const Xt9InputMethod);
+ if (!autoSpaceAllowed)
+ return false;
+ if (q->inputEngine()->inputMode() == QVirtualKeyboardInputEngine::InputMode::Numeric)
+ return false;
+ Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
+ return !inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) &&
+ !inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly);
+}
+
+bool Xt9InputMethodPrivate::isValidInputChar(const QChar &c) const
+{
+ if (c.isLetterOrNumber())
+ return true;
+ if (isJoiner(c))
+ return true;
+ if (c.isMark())
+ return true;
+ return false;
+}
+
+bool Xt9InputMethodPrivate::isJoiner(const QChar &c) const
+{
+ if (c.isPunct() || c.isSymbol()) {
+ Q_Q(const Xt9InputMethod);
+ if (QVirtualKeyboardInputContext *ic = q->inputContext()) {
+ Qt::InputMethodHints inputMethodHints = ic->inputMethodHints();
+ if (inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) || inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly))
+ return QStringView(u":/?#[]@!$&'()*+,;=-_.%").contains(c);
+ }
+ }
+ return false;
+}
+
+void Xt9InputMethodPrivate::setShiftState(bool shift, bool caps)
+{
+ if (caps)
+ xt9Ime()->setCapsLock();
+ else if (shift)
+ xt9Ime()->setShift();
+ else
+ xt9Ime()->setUnShift();
+}
+
+void Xt9InputMethodPrivate::reset()
+{
+ xt9Ime()->clearInput();
+
+ if (!selectionList.isEmpty() || defaultListIndex != -1) {
+ Xt9DeferredSelectionListUpdate deferredSelectionListUpdate(this);
+
+ selectionList.clear();
+ defaultListIndex = -1;
+ }
+
+ setAutoSpaceAllowed(false);
+}
+
+void Xt9InputMethodPrivate::onAvailableDynamicDictionariesChanged()
+{
+ Q_Q(Xt9InputMethod);
+ QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
+
+ const QStringList availableDictionaries = dictionaryManager->availableDictionaries();
+ for (const QString &dictionaryName : availableDictionaries) {
+ if (!dynamicDictionaries.contains(dictionaryName)) {
+
+ QVirtualKeyboardDictionary *dictionary = dictionaryManager->dictionary(dictionaryName);
+ const quint16 id = static_cast<quint16>(dynamicDictionaryNextId.fetchAndAddRelaxed(1));
+ dynamicDictionaries[dictionaryName] = id;
+
+ xt9Ime()->updateIndex(id, dictionary->contents());
+
+ q->connect(dictionary, &QVirtualKeyboardDictionary::contentsChanged, q, [=]() {
+ xt9Ime()->updateIndex(id, dictionary->contents());
+ if (attachedDynamicDictionaries.contains(dictionaryName))
+ xt9Ime()->mountIndex(id);
+ });
+ }
+ }
+}
+
+void Xt9InputMethodPrivate::onActiveDynamicDictionariesChanged()
+{
+ QVirtualKeyboardDictionaryManager *dictionaryManager = QVirtualKeyboardDictionaryManager::instance();
+
+ // Attach
+ const QStringList activeDictionaries = dictionaryManager->activeDictionaries();
+ for (const QString &dictionaryName : activeDictionaries) {
+ if (!attachedDynamicDictionaries.contains(dictionaryName)) {
+ const quint16 id = dynamicDictionaries.value(dictionaryName);
+ xt9Ime()->mountIndex(id);
+ attachedDynamicDictionaries[dictionaryName] = id;
+ }
+ }
+
+ // Detach
+ for (const QString &dictionaryName : attachedDynamicDictionaries.keys()) {
+ if (!activeDictionaries.contains(dictionaryName)) {
+ const quint16 id = attachedDynamicDictionaries[dictionaryName];
+ xt9Ime()->unmountIndex(id);
+ attachedDynamicDictionaries.remove(dictionaryName);
+ }
+ }
+}
+
+void Xt9InputMethodPrivate::onUserDataPathChanged()
+{
+ updateDlm();
+ xt9Ime()->setWorkingDirectory(Settings::instance()->userDataPath());
+}
+
+void Xt9InputMethodPrivate::onUserDataReset()
+{
+ dlmDeactivate();
+ removeAllDynamicDictionaries();
+}
+
+ET9STATUS Xt9InputMethodPrivate::request(ET9_Request *const pRequest)
+{
+ Q_Q(Xt9InputMethod);
+
+ switch (pRequest->eType) {
+ case ET9_REQ_AutoCap:
+ break;
+
+ case ET9_REQ_AutoAccept:
+ selectionListSelectItem(defaultListIndex);
+ break;
+
+ 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;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9inputmethodprivate_p.h b/src/plugins/cerence/xt9/plugin/xt9inputmethodprivate_p.h
new file mode 100644
index 00000000..9020ebd7
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9inputmethodprivate_p.h
@@ -0,0 +1,119 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9INPUTMETHODPRIVATE_P_H
+#define XT9INPUTMETHODPRIVATE_P_H
+
+#include <QtGlobal>
+#include <et9api.h>
+#include <QStringList>
+#include "xt9callbacks.h"
+#include "xt9ime.h"
+#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
+#include <QtVirtualKeyboard/private/qvirtualkeyboardabstractinputmethod_p.h>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9InputMethod;
+
+class Xt9InputMethodPrivate : public QVirtualKeyboardAbstractInputMethodPrivate, public Xt9RequestCallback
+{
+public:
+ Q_DECLARE_PUBLIC(Xt9InputMethod)
+
+ Xt9InputMethodPrivate(Xt9InputMethod *q_ptr, Xt9Ime *xt9Ime, const QString &aDlmFileName);
+
+ inline Xt9Ime *xt9Ime() const;
+ void sysInit();
+ bool init(QLocale aLocale, QVirtualKeyboardInputEngine::InputMode aInputMode);
+ virtual void uninit();
+ void bindToDictionaryManager();
+ virtual void bindToSettings();
+ void bindToKeyboard();
+ void dlmActivate();
+ void dlmDeactivate();
+ virtual void updateLdb();
+ void updateDlm();
+ void removeAllDynamicDictionaries();
+ void updateDynamicDictionaries();
+ void updateShiftState();
+ bool updateLayout();
+ virtual void updatePreeditText();
+ void buildSelectionList();
+ void buildSelectionList(ET9STATUS &eStatus);
+ void selectionListUpdate();
+ virtual void selectionListSelectItem(int index) = 0;
+ void updatePunctuationBreaking();
+ bool processBackspace();
+ bool processKeyBySymbol(const QChar &symbol);
+ virtual bool maybeInsertSpaceBeforeNextInputSymbol(QChar symbol) const;
+ void setAutoSpaceAllowed(bool value);
+ virtual bool isAutoSpaceAllowed() const;
+ virtual bool isValidInputChar(const QChar &c) const;
+ virtual bool isJoiner(const QChar &c) const;
+ void setShiftState(bool shift, bool caps);
+ virtual void reset();
+ void onAvailableDynamicDictionariesChanged();
+ void onActiveDynamicDictionariesChanged();
+ void onUserDataPathChanged();
+ void onUserDataReset();
+
+ virtual ET9U32 inputModeToET9InputMode(QVirtualKeyboardInputEngine::InputMode aInputMode) const = 0;
+ ET9STATUS request(ET9_Request *const pRequest) override;
+
+public:
+ Xt9InputMethod *q_ptr;
+ QScopedPointer<Xt9Ime> _xt9Ime;
+ QMetaObject::Connection availableDictionariesChangedConnection;
+ QMetaObject::Connection activeDictionariesChangedConnection;
+ QMetaObject::Connection userDataPathChangedConnection;
+ QMetaObject::Connection userDataResetConnection;
+ QMetaObject::Connection layoutChangedConnection;
+ QMetaObject::Connection shiftActiveChangedConnection;
+ QMetaObject::Connection capsLockActiveChangedConnection;
+ QMetaObject::Connection inputMethodHintsChangedConnection;
+ QLocale locale;
+ QVirtualKeyboardInputEngine::InputMode inputMode;
+ QStringList selectionList;
+ QAtomicInt selectionListUpdateCount;
+ int defaultListIndex;
+ bool autoSpaceAllowed;
+ bool initDone;
+ const QString dlmFileName;
+ QSharedPointer<Xt9DbFile> dlm;
+ QMap<QString, quint16> dynamicDictionaries;
+ QMap<QString, quint16> attachedDynamicDictionaries;
+ QAtomicInt dynamicDictionaryNextId;
+};
+
+Xt9Ime *Xt9InputMethodPrivate::xt9Ime() const
+{
+ return _xt9Ime.data();
+}
+
+class Xt9DeferredSelectionListUpdate
+{
+ Q_DISABLE_COPY(Xt9DeferredSelectionListUpdate)
+public:
+ inline explicit Xt9DeferredSelectionListUpdate(Xt9InputMethodPrivate *d) :
+ d(d)
+ {
+ d->selectionListUpdateCount.ref();
+ }
+
+ inline ~Xt9DeferredSelectionListUpdate()
+ {
+ if (!d->selectionListUpdateCount.deref()) {
+ d->selectionListUpdate();
+ }
+ }
+
+private:
+ Xt9InputMethodPrivate *d;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9INPUTMETHODPRIVATE_P_H
diff --git a/src/plugins/cerence/xt9/plugin/xt9jinputmethod.cpp b/src/plugins/cerence/xt9/plugin/xt9jinputmethod.cpp
new file mode 100644
index 00000000..6f94f214
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9jinputmethod.cpp
@@ -0,0 +1,104 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9jinputmethod_p.h"
+#include "xt9jinputmethodprivate_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+/*!
+ \class QtVirtualKeyboard::Xt9JInputMethod
+ \internal
+*/
+
+Xt9JInputMethod::Xt9JInputMethod(QObject *parent) :
+ Xt9AwInputMethod(*new Xt9JInputMethodPrivate(this), parent)
+{
+}
+
+QList<QVirtualKeyboardInputEngine::InputMode> Xt9JInputMethod::inputModes(const QString &locale)
+{
+ QList<QVirtualKeyboardInputEngine::InputMode> result;
+ result.append(QVirtualKeyboardInputEngine::InputMode::Hiragana);
+ result.append(QVirtualKeyboardInputEngine::InputMode::Katakana);
+ result.append(QVirtualKeyboardInputEngine::InputMode::Romaji);
+ return result;
+}
+
+bool Xt9JInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
+{
+ return Xt9AwInputMethod::setInputMode(locale, inputMode);
+}
+
+bool Xt9JInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
+{
+ Q_D(Xt9JInputMethod);
+
+ switch (key) {
+ case Qt::Key_Right:
+ case Qt::Key_Left:
+ {
+ ET9U16 wCurrLen;
+ ET9STATUS eStatus = XT9_API(ET9GetSegmentationLength, &d->xt9Ime()->sWordSymbInfo, &wCurrLen);
+ if (eStatus == ET9STATUS_INVALID_MODE) {
+ if (key == Qt::Key_Right) {
+
+ } else if (!XT9_API(ET9SetSegmentationLength, &d->xt9Ime()->sWordSymbInfo, wCurrLen)) {
+ d->selectionListUpdate();
+ return true;
+ }
+ } else if (!eStatus) {
+ if (key == Qt::Key_Left) {
+ --wCurrLen;
+ } else {
+ ++wCurrLen;
+ }
+
+ if (!XT9_API(ET9SetSegmentationLength, &d->xt9Ime()->sWordSymbInfo, wCurrLen)) {
+ d->selectionListUpdate();
+ return true;
+ }
+ }
+ break;
+ }
+
+ case 0x5C0F:
+ case 0x3099:
+ case 0x309A:
+ {
+ ET9MODIFIER eModifier;
+ switch (key) {
+ case 0x3099:
+ eModifier = ET9MODIFIER_JPNDAKUTEN_HANDAKUTEN;
+ break;
+ case 0x309A:
+ eModifier = ET9MODIFIER_JPNHANDAKUTEN;
+ break;
+ default:
+ eModifier = ET9MODIFIER_JPNALL;
+ break;
+ }
+ if (!XT9_API(ET9KDB_ModifyCurrentKey, &d->xt9Ime()->sKdbInfo, eModifier)) {
+ d->buildSelectionList();
+ d->updatePreeditText();
+ return true;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return Xt9AwInputMethod::keyEvent(key, d->xt9Ime()->codeConverter->convertTo(text), modifiers);
+}
+
+bool Xt9JInputMethod::isModifyKeyEnabled() const
+{
+ Q_D(const Xt9JInputMethod);
+ return d->isModifyKeyEnabled;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9jinputmethod_p.h b/src/plugins/cerence/xt9/plugin/xt9jinputmethod_p.h
new file mode 100644
index 00000000..0c4e37c1
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9jinputmethod_p.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9JINPUTMETHOD_P_H
+#define XT9JINPUTMETHOD_P_H
+
+#include "xt9awinputmethod_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9JInputMethodPrivate;
+
+class Xt9JInputMethod : public Xt9AwInputMethod
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(Xt9JInputMethod)
+ Q_PROPERTY(bool modifyKeyEnabled READ isModifyKeyEnabled NOTIFY modifyKeyEnabledChanged)
+ QML_NAMED_ELEMENT(JapaneseInputMethod)
+ QML_ADDED_IN_VERSION(2, 0)
+
+public:
+ explicit Xt9JInputMethod(QObject *parent = nullptr);
+
+ QList<QVirtualKeyboardInputEngine::InputMode> inputModes(const QString &locale) override;
+ bool setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) override;
+
+ bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) override;
+
+ bool isModifyKeyEnabled() const;
+
+signals:
+ void modifyKeyEnabledChanged();
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/cerence/xt9/plugin/xt9jinputmethodprivate.cpp b/src/plugins/cerence/xt9/plugin/xt9jinputmethodprivate.cpp
new file mode 100644
index 00000000..fb27002b
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9jinputmethodprivate.cpp
@@ -0,0 +1,116 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9jinputmethodprivate_p.h"
+#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
+#include <QTextFormat>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9JInputMethodPrivate::Xt9JInputMethodPrivate(Xt9JInputMethod *q) :
+ Xt9AwInputMethodPrivate(q, new Xt9JIme(this), QStringLiteral("xt9j.dlm")),
+ isModifyKeyEnabled(false)
+{
+}
+
+void Xt9JInputMethodPrivate::updateLdb()
+{
+ Xt9AwInputMethodPrivate::updateLdb();
+ XT9_API(ET9AWSetMultiWordInputProperties, &xt9Ime()->sLingInfo, ET9AW_MWI_SegmentMode_Manual, 1);
+ if (inputMode == QVirtualKeyboardInputEngine::InputMode::Katakana) {
+ static const ET9AW_UtilityWord eList[] = {
+ ET9AW_UtilityWord_Katakana_FW,
+ ET9AW_UtilityWord_Hiragana_FW,
+ ET9AW_UtilityWord_Romaji_IC_HW,
+ ET9AW_UtilityWord_Romaji_LC_HW,
+ ET9AW_UtilityWord_Romaji_UC_HW
+ };
+ XT9_API(ET9AWSetUtilityWords, &xt9Ime()->sLingInfo, eList, sizeof(eList) / sizeof(ET9AW_UtilityWord), 2);
+ } else if (inputMode == QVirtualKeyboardInputEngine::InputMode::Hiragana) {
+ static const ET9AW_UtilityWord eList[] = {
+ ET9AW_UtilityWord_Hiragana_FW,
+ ET9AW_UtilityWord_Katakana_FW,
+ ET9AW_UtilityWord_Romaji_IC_HW,
+ ET9AW_UtilityWord_Romaji_LC_HW,
+ ET9AW_UtilityWord_Romaji_UC_HW
+ };
+ XT9_API(ET9AWSetUtilityWords, &xt9Ime()->sLingInfo, eList, sizeof(eList) / sizeof(ET9AW_UtilityWord), 2);
+ } else if (inputMode == QVirtualKeyboardInputEngine::InputMode::Latin) {
+ static const ET9AW_UtilityWord eList[] = {
+ ET9AW_UtilityWord_Romaji_IC_HW,
+ ET9AW_UtilityWord_Hiragana_FW,
+ ET9AW_UtilityWord_Katakana_FW,
+ ET9AW_UtilityWord_Romaji_LC_HW,
+ ET9AW_UtilityWord_Romaji_UC_HW
+ };
+ XT9_API(ET9AWSetUtilityWords, &xt9Ime()->sLingInfo, eList, sizeof(eList) / sizeof(ET9AW_UtilityWord), 2);
+ } else if (inputMode == QVirtualKeyboardInputEngine::InputMode::FullwidthLatin) {
+ static const ET9AW_UtilityWord eList[] = {
+ ET9AW_UtilityWord_Romaji_IC_FW,
+ ET9AW_UtilityWord_Hiragana_FW,
+ ET9AW_UtilityWord_Katakana_FW,
+ ET9AW_UtilityWord_Romaji_LC_HW,
+ ET9AW_UtilityWord_Romaji_UC_HW
+ };
+ XT9_API(ET9AWSetUtilityWords, &xt9Ime()->sLingInfo, eList, sizeof(eList) / sizeof(ET9AW_UtilityWord), 2);
+ }
+}
+
+void Xt9JInputMethodPrivate::updatePreeditText()
+{
+ Q_Q(Xt9JInputMethod);
+
+ QString exactWord = xt9Ime()->exactWord();
+ if (inputMode == QVirtualKeyboardInputEngine::InputMode::Katakana) {
+ exactWord = xt9Ime()->codeConverter->convertFrom(exactWord);
+ }
+
+ QList<QInputMethodEvent::Attribute> attributes;
+ QTextCharFormat textFormat;
+ textFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+ attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, exactWord.length(), textFormat));
+
+ ET9U16 wCurrLen;
+ if (!XT9_API(ET9GetSegmentationLength, &xt9Ime()->sWordSymbInfo, &wCurrLen)) {
+ attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, wCurrLen, 1, QVariant()));
+ }
+
+ setModifyKeyEnabled(!exactWord.isEmpty());
+
+ q->inputContext()->setPreeditText(exactWord, attributes);
+}
+
+ET9U32 Xt9JInputMethodPrivate::inputModeToET9InputMode(QVirtualKeyboardInputEngine::InputMode aInputMode) const
+{
+ switch (aInputMode) {
+ case QVirtualKeyboardInputEngine::InputMode::Latin:
+ return ET9AWInputMode_Transliteration;
+
+ case QVirtualKeyboardInputEngine::InputMode::Romaji:
+ return ET9AWInputMode_RomajiConversion;
+
+ default:
+ break;
+ }
+
+ return ET9AWInputMode_Conversion;
+}
+
+void Xt9JInputMethodPrivate::reset()
+{
+ Xt9AwInputMethodPrivate::reset();
+ setModifyKeyEnabled(false);
+}
+
+void Xt9JInputMethodPrivate::setModifyKeyEnabled(bool value)
+{
+ if (isModifyKeyEnabled != value) {
+ isModifyKeyEnabled = value;
+ Q_Q(Xt9JInputMethod);
+ emit q->modifyKeyEnabledChanged();
+ }
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9jinputmethodprivate_p.h b/src/plugins/cerence/xt9/plugin/xt9jinputmethodprivate_p.h
new file mode 100644
index 00000000..c5f2a39c
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9jinputmethodprivate_p.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9JINPUTMETHODPRIVATE_P_H
+#define XT9JINPUTMETHODPRIVATE_P_H
+
+#include "xt9awinputmethodprivate_p.h"
+#include "xt9jinputmethod_p.h"
+#include "xt9jime.h"
+#include <QMetaObject>
+#include <QByteArray>
+#include <QLocale>
+#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9JInputMethodPrivate : public Xt9AwInputMethodPrivate
+{
+ Q_DECLARE_PUBLIC(Xt9JInputMethod)
+public:
+ Xt9JInputMethodPrivate(Xt9JInputMethod *q_ptr);
+
+ inline Xt9JIme *xt9Ime() const;
+
+ void updateLdb() override;
+ void updatePreeditText() override;
+ ET9U32 inputModeToET9InputMode(QVirtualKeyboardInputEngine::InputMode aInputMode) const override;
+ void reset() override;
+
+ void setModifyKeyEnabled(bool value);
+
+ bool isModifyKeyEnabled;
+};
+
+Xt9JIme *Xt9JInputMethodPrivate::xt9Ime() const
+{
+ return static_cast<Xt9JIme *>(_xt9Ime.data());
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9JINPUTMETHODPRIVATE_P_H
diff --git a/src/plugins/cerence/xt9/plugin/xt9kinputmethod.cpp b/src/plugins/cerence/xt9/plugin/xt9kinputmethod.cpp
new file mode 100644
index 00000000..956df3cf
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9kinputmethod.cpp
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9kinputmethod_p.h"
+#include "xt9kinputmethodprivate_p.h"
+#include <QVirtualKeyboardInputContext>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+/*!
+ \class QtVirtualKeyboard::Xt9KInputMethod
+ \internal
+*/
+
+Xt9KInputMethod::Xt9KInputMethod(QObject *parent) :
+ Xt9AwInputMethod(*new Xt9KInputMethodPrivate(this), parent)
+{
+}
+
+bool Xt9KInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
+{
+ Q_D(Xt9KInputMethod);
+ return Xt9AwInputMethod::keyEvent(key, d->xt9Ime()->codeConverter->convertTo(text), modifiers);
+}
+
+void Xt9KInputMethod::update()
+{
+ Q_D(Xt9KInputMethod);
+
+ if (d->xt9Ime()->hasActiveInput()) {
+ if (d->selectionList.size() > 0) {
+ d->learnWord(d->selectionList.at(0));
+ d->setAutoSpaceAllowed(false);
+ d->selectionListSelectItem(0);
+ } else {
+ inputContext()->commit();
+ reset();
+ }
+ }
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9kinputmethod_p.h b/src/plugins/cerence/xt9/plugin/xt9kinputmethod_p.h
new file mode 100644
index 00000000..e952163d
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9kinputmethod_p.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KINPUTMETHOD_P_H
+#define XT9KINPUTMETHOD_P_H
+
+#include "xt9awinputmethod_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9KInputMethodPrivate;
+
+class Xt9KInputMethod : public Xt9AwInputMethod
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(Xt9KInputMethod)
+ QML_NAMED_ELEMENT(HangulInputMethod)
+ QML_ADDED_IN_VERSION(2, 0)
+
+public:
+ explicit Xt9KInputMethod(QObject *parent = nullptr);
+
+ bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) override;
+
+ void update() override;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/cerence/xt9/plugin/xt9kinputmethodprivate.cpp b/src/plugins/cerence/xt9/plugin/xt9kinputmethodprivate.cpp
new file mode 100644
index 00000000..a471a1fa
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9kinputmethodprivate.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9kinputmethodprivate_p.h"
+#include <QVirtualKeyboardInputContext>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9KInputMethodPrivate::Xt9KInputMethodPrivate(Xt9KInputMethod *q) :
+ Xt9AwInputMethodPrivate(q, new Xt9KIme(this), QStringLiteral("xt9k.dlm"))
+{
+}
+
+void Xt9KInputMethodPrivate::updatePreeditText()
+{
+ ET9KHangulWord hangul;
+ ET9STATUS eStatus = XT9_API(ET9KBuildHangul, &xt9Ime()->sLingInfo, &hangul);
+ if (!eStatus && hangul.wLen) {
+ Q_Q(Xt9KInputMethod);
+ q->inputContext()->setPreeditText(QString::fromUtf16(reinterpret_cast<const char16_t*>(hangul.sString), hangul.wLen));
+ } else {
+ Xt9AwInputMethodPrivate::updatePreeditText();
+ }
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9kinputmethodprivate_p.h b/src/plugins/cerence/xt9/plugin/xt9kinputmethodprivate_p.h
new file mode 100644
index 00000000..73efb971
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9kinputmethodprivate_p.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KINPUTMETHODPRIVATE_P_H
+#define XT9KINPUTMETHODPRIVATE_P_H
+
+#include "xt9awinputmethodprivate_p.h"
+#include "xt9kinputmethod_p.h"
+#include "xt9kime.h"
+#include <QMetaObject>
+#include <QByteArray>
+#include <QLocale>
+#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9KInputMethodPrivate : public Xt9AwInputMethodPrivate
+{
+ Q_DECLARE_PUBLIC(Xt9KInputMethod)
+public:
+ Xt9KInputMethodPrivate(Xt9KInputMethod *q_ptr);
+
+ inline Xt9KIme *xt9Ime() const;
+
+ void updatePreeditText();
+};
+
+Xt9KIme *Xt9KInputMethodPrivate::xt9Ime() const
+{
+ return static_cast<Xt9KIme *>(_xt9Ime.data());
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KINPUTMETHODPRIVATE_P_H
diff --git a/src/plugins/cerence/xt9/plugin/xt9thaiinputmethod.cpp b/src/plugins/cerence/xt9/plugin/xt9thaiinputmethod.cpp
new file mode 100644
index 00000000..ed8d4abd
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9thaiinputmethod.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9thaiinputmethod_p.h"
+#include "xt9thaiinputmethodprivate_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+/*!
+ \class QtVirtualKeyboard::Xt9ThaiInputMethod
+ \internal
+*/
+
+Xt9ThaiInputMethod::Xt9ThaiInputMethod(QObject *parent) :
+ Xt9AwInputMethod(*new Xt9ThaiInputMethodPrivate(this), parent)
+{
+}
+
+bool Xt9ThaiInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
+{
+ const bool isMark = text.length() == 2 && text.at(0) == QLatin1Char(' ');
+ if (isMark) {
+ const QString mark(text.right(1));
+ return Xt9AwInputMethod::keyEvent(static_cast<Qt::Key>(mark.at(0).unicode()),
+ mark, modifiers);
+ }
+ return Xt9AwInputMethod::keyEvent(key, text, modifiers);
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9thaiinputmethod_p.h b/src/plugins/cerence/xt9/plugin/xt9thaiinputmethod_p.h
new file mode 100644
index 00000000..b7665cb1
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9thaiinputmethod_p.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9THAIINPUTMETHOD_P_H
+#define XT9THAIINPUTMETHOD_P_H
+
+#include "xt9awinputmethod_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9ThaiInputMethodPrivate;
+
+class Xt9ThaiInputMethod : public Xt9AwInputMethod
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(Xt9ThaiInputMethod)
+ QML_NAMED_ELEMENT(ThaiInputMethod)
+ QML_ADDED_IN_VERSION(2, 0)
+
+public:
+ explicit Xt9ThaiInputMethod(QObject *parent = nullptr);
+
+ bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) override;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/cerence/xt9/plugin/xt9thaiinputmethodprivate.cpp b/src/plugins/cerence/xt9/plugin/xt9thaiinputmethodprivate.cpp
new file mode 100644
index 00000000..a8e1240a
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9thaiinputmethodprivate.cpp
@@ -0,0 +1,15 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9thaiinputmethodprivate_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9ThaiInputMethodPrivate::Xt9ThaiInputMethodPrivate(Xt9ThaiInputMethod *q) :
+ Xt9AwInputMethodPrivate(q, new Xt9AwIme(this), QStringLiteral("xt9aw.dlm"))
+{
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/plugin/xt9thaiinputmethodprivate_p.h b/src/plugins/cerence/xt9/plugin/xt9thaiinputmethodprivate_p.h
new file mode 100644
index 00000000..8f68d563
--- /dev/null
+++ b/src/plugins/cerence/xt9/plugin/xt9thaiinputmethodprivate_p.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9THAIINPUTMETHODPRIVATE_P_H
+#define XT9THAIINPUTMETHODPRIVATE_P_H
+
+#include "xt9awinputmethodprivate_p.h"
+#include "xt9thaiinputmethod_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9ThaiInputMethodPrivate : public Xt9AwInputMethodPrivate
+{
+ Q_DECLARE_PUBLIC(Xt9ThaiInputMethod)
+public:
+ Xt9ThaiInputMethodPrivate(Xt9ThaiInputMethod *q_ptr);
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KINPUTMETHODPRIVATE_P_H
diff --git a/src/plugins/cerence/xt9/xt9common/CMakeLists.txt b/src/plugins/cerence/xt9/xt9common/CMakeLists.txt
new file mode 100644
index 00000000..08a6b1d6
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/CMakeLists.txt
@@ -0,0 +1,144 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from xt9common.pro.
+
+#####################################################################
+## BundledXt9Common Generic Library:
+#####################################################################
+
+qt_internal_add_3rdparty_library(BundledXt9Common
+ QMAKE_LIB_NAME xt9common
+ STATIC
+ SOURCES
+ xt9awime.cpp xt9awime.h
+ xt9callbacks.h
+ xt9cpime.cpp xt9cpime.h
+ xt9ime.cpp xt9ime.h
+ xt9jime.cpp xt9jime.h
+ xt9kdb.cpp xt9kdb.h
+ xt9kdbarea.cpp xt9kdbarea.h
+ xt9kdbelement.cpp xt9kdbelement.h
+ xt9kdbkey.cpp xt9kdbkey.h
+ xt9kdblayout.cpp xt9kdblayout.h
+ xt9keyboardgenerator.cpp xt9keyboardgenerator.h
+ xt9kime.cpp xt9kime.h
+ xt9languagemap.cpp xt9languagemap.h
+ PUBLIC_INCLUDE_DIRECTORIES
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+ LIBRARIES
+ Qt::BundledCerencecommon
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::VirtualKeyboard
+ Cerence::XT9
+)
+
+## Scopes:
+#####################################################################
+
+if(NOT FEATURE_vkb_cerence_static)
+ qt_copy_or_install(
+ FILES "${CERENCE_XT9_BINARIES}"
+ DESTINATION "${QT_BUILD_DIR}/${INSTALL_BINDIR}"
+ )
+endif()
+
+#### Keys ignored in scope 3:.:../..:../../cerence.pri:CERENCE_SDK_ROOT_ISEMPTY:
+# EXT_CERENCE_SDK_ROOT = "$$(CERENCE_SDK_ROOT)"
+
+#### Keys ignored in scope 4:.:../..:../../cerence.pri:NOT EXT_CERENCE_SDK_ROOT_ISEMPTY:
+# CERENCE_SDK_ROOT = "$$EXT_CERENCE_SDK_ROOT"
+
+#### Keys ignored in scope 6:.:../..:../../cerence.pri:CERENCE_SDK_ROOT_ISEMPTY:
+# CERENCE_SDK_ROOT = "$$PWD/sdk"
+
+#### Keys ignored in scope 9:.:../..:../../cerence.pri:QT_ARCH___equals___arm:
+# CERENCE_SHARED_LIB_PATH = "lib/linux/arm/shared"
+# CERENCE_STATIC_LIB_PATH = "lib/linux/arm/static"
+
+#### Keys ignored in scope 11:.:../..:../../cerence.pri:QT_ARCH___equals___arm64:
+# CERENCE_SHARED_LIB_PATH = "lib/linux/arm64/shared"
+# CERENCE_STATIC_LIB_PATH = "lib/linux/arm64/static"
+
+#### Keys ignored in scope 13:.:../..:../../cerence.pri:QT_ARCH___equals___x86_64:
+# CERENCE_SHARED_LIB_PATH = "lib/linux/x86_64/shared"
+# CERENCE_STATIC_LIB_PATH = "lib/linux/x86_64/static"
+
+#### Keys ignored in scope 15:.:../..:../../cerence.pri:QT_ARCH___equals___x86 OR QT_ARCH___equals___i386:
+# CERENCE_SHARED_LIB_PATH = "lib/linux/x86/shared"
+# CERENCE_STATIC_LIB_PATH = "lib/linux/x86/static"
+
+#### Keys ignored in scope 18:.:../..:../../cerence.pri:QT_ARCH___equals___x86_64:
+# CERENCE_SHARED_LIB_PATH = "lib/win32/x86_64/shared"
+# CERENCE_STATIC_LIB_PATH = "lib/win32/x86_64/static"
+
+#### Keys ignored in scope 19:.:../..:../../cerence.pri:else:
+# CERENCE_SHARED_LIB_PATH = "lib/win32/x86/shared"
+# CERENCE_STATIC_LIB_PATH = "lib/win32/x86/static"
+
+#### Keys ignored in scope 21:.:../..:../../cerence.pri:WIN32:
+# result = "$$1/*.obj"
+
+#### Keys ignored in scope 22:.:../..:../../cerence.pri:result_ISEMPTY:
+# result = "$$1/*.lib"
+
+#### Keys ignored in scope 23:.:../..:../../cerence.pri:else:
+# result = "$$1/*.o"
+
+#### Keys ignored in scope 24:.:../..:../../cerence.pri:result_ISEMPTY:
+# result = "$$1/*.a"
+
+#### Keys ignored in scope 26:.:../..:../../cerence.pri:WIN32:
+# result = "$$1/*.lib"
+
+#### Keys ignored in scope 27:.:../..:../../cerence.pri:else:
+# result = "$$1/*.so"
+
+#### Keys ignored in scope 29:.:../..:../../cerence.pri:WIN32:
+# result = "$$1/*.dll"
+
+#### Keys ignored in scope 30:.:../..:../../cerence.pri:else:
+# result = "$$1/*.so"
+
+#### Keys ignored in scope 31:.:../..:../../cerence.pri:EXISTS _ss_CERENCE_HWR_INCLUDEPATH/decuma_hwr.h:
+# CERENCE_HWR_ALPHABETIC_FOUND = "1"
+
+#### Keys ignored in scope 32:.:../..:../../cerence.pri:EXISTS _ss_CERENCE_HWR_INCLUDEPATH/decuma_hwr_cjk.h:
+# CERENCE_HWR_CJK_FOUND = "1"
+
+#### Keys ignored in scope 35:.:../..:../../cerence.pri:NOT cerence-hwr-static:
+# CERENCE_HWR_ALPHABETIC_LIBS = "$$findSharedLibrary($$CERENCE_SDK_ROOT/t9write/$$CERENCE_SHARED_LIB_PATH/alphabetic)"
+
+#### Keys ignored in scope 36:.:../..:../../cerence.pri:NOT CERENCE_HWR_ALPHABETIC_LIBS_ISEMPTY:
+# CERENCE_HWR_ALPHABETIC_BINS = "$$findSharedBinary($$CERENCE_SDK_ROOT/t9write/$$CERENCE_SHARED_LIB_PATH/alphabetic)"
+
+#### Keys ignored in scope 37:.:../..:../../cerence.pri:else:
+# CERENCE_HWR_ALPHABETIC_LIBS = "$$findStaticLibrary($$CERENCE_SDK_ROOT/t9write/$$CERENCE_STATIC_LIB_PATH/alphabetic)"
+
+#### Keys ignored in scope 39:.:../..:../../cerence.pri:NOT cerence-hwr-static:
+# CERENCE_HWR_CJK_LIBS = "$$findSharedLibrary($$CERENCE_SDK_ROOT/t9write/$$CERENCE_SHARED_LIB_PATH/cjk)"
+
+#### Keys ignored in scope 40:.:../..:../../cerence.pri:NOT CERENCE_HWR_CJK_LIBS_ISEMPTY:
+# CERENCE_HWR_CJK_BINS = "$$findSharedBinary($$CERENCE_SDK_ROOT/t9write/$$CERENCE_SHARED_LIB_PATH/cjk)"
+
+#### Keys ignored in scope 41:.:../..:../../cerence.pri:else:
+# CERENCE_HWR_CJK_LIBS = "$$findStaticLibrary($$CERENCE_SDK_ROOT/t9write/$$CERENCE_STATIC_LIB_PATH/cjk)"
+
+#### Keys ignored in scope 42:.:../..:../../cerence.pri:(CERENCE_HWR_ALPHABETIC_FOUND EQUAL 1) AND NOT CERENCE_HWR_ALPHABETIC_LIBS_ISEMPTY:
+# CERENCE_HWR_FOUND = "1"
+
+#### Keys ignored in scope 43:.:../..:../../cerence.pri:(CERENCE_HWR_CJK_FOUND EQUAL 1) AND NOT CERENCE_HWR_CJK_LIBS_ISEMPTY:
+# CERENCE_HWR_FOUND = "1"
+
+#### Keys ignored in scope 44:.:../..:../../cerence.pri:EXISTS _ss_XT9_INCLUDEPATH/et9api.h:
+# XT9_FOUND = "1"
+
+#### Keys ignored in scope 46:.:../..:../../cerence.pri:NOT QT_FEATURE_xt9_static:
+# XT9_LIBS = "$$findSharedLibrary($$CERENCE_SDK_ROOT/xt9/$$CERENCE_SHARED_LIB_PATH)"
+
+#### Keys ignored in scope 47:.:../..:../../cerence.pri:NOT XT9_LIBS_ISEMPTY:
+# XT9_BINS = "$$findSharedBinary($$CERENCE_SDK_ROOT/xt9/$$CERENCE_SHARED_LIB_PATH)"
+
+#### Keys ignored in scope 48:.:../..:../../cerence.pri:else:
+# XT9_LIBS = "$$findStaticLibrary($$CERENCE_SDK_ROOT/xt9/$$CERENCE_STATIC_LIB_PATH)"
diff --git a/src/plugins/cerence/xt9/xt9common/xt9awime.cpp b/src/plugins/cerence/xt9/xt9common/xt9awime.cpp
new file mode 100644
index 00000000..3beeb05f
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9awime.cpp
@@ -0,0 +1,112 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9awime.h"
+#include "xt9languagemap.h"
+#include "xt9dbfile.h"
+#include <QStandardPaths>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9AwIme::Xt9AwIme(Xt9RequestCallback *requestCallback, Xt9KeyboardGenerator::CodeConverter *codeConverter) :
+ Xt9Ime(requestCallback, codeConverter)
+{
+}
+
+void Xt9AwIme::sysInit()
+{
+ Xt9Ime::sysInit();
+ memset(&sLingInfo, 0, sizeof(sLingInfo));
+ memset(&sLingCmnInfo, 0, sizeof(sLingCmnInfo));
+ XT9_API(ET9AWSysInit, &sLingInfo, &sLingCmnInfo, &sWordSymbInfo, 1, this);
+}
+
+bool Xt9AwIme::ldbInit(ET9U32 dwFirstLdbNum, ET9U32 dwSecondLdbNum, ET9U32 eInputMode)
+{
+ ET9STATUS eStatus;
+
+ eStatus = XT9_API(ET9AWEnableDBs, &sLingInfo, ET9ALLDBMASK);
+
+ XT9_API(ET9AWLdbInit, &sLingInfo, &ET9AWLdbReadData);
+ eStatus = XT9_API(ET9AWLdbSetLanguage, &sLingInfo, dwFirstLdbNum, dwSecondLdbNum, 1, static_cast<ET9AWInputMode>(eInputMode));
+
+ //XT9_API(ET9AWClearNextWordPrediction, &sLingInfo);
+ XT9_API(ET9AWClearAutoAppendInList, &sLingInfo);
+
+ return !eStatus;
+}
+
+void Xt9AwIme::setLdbEnabled(bool enabled)
+{
+ XT9_API(ET9AWEnableDBs, &sLingInfo, ET9ALLDBMASK);
+
+ if (!enabled)
+ XT9_API(ET9AWDisableDBs, &sLingInfo, ET9STATELDBENABLEDMASK);
+}
+
+qint64 Xt9AwIme::dlmPreferredSize() const
+{
+ return ET9AWDLM_SIZE_NORMAL;
+}
+
+bool Xt9AwIme::dlmInit(void *data, qint64 size)
+{
+ ET9STATUS eStatus;
+
+ eStatus = XT9_API(ET9AWDLMInit, &sLingInfo, static_cast<_ET9DLM_info *>(data), static_cast<ET9U32>(size), nullptr);
+
+ return !eStatus;
+}
+
+QStringList Xt9AwIme::buildSelectionList(int *defaultListIndex, ET9U16 *gestureValue, ET9STATUS &eStatus)
+{
+ ET9U8 totalWords;
+ ET9U8 listIndex;
+
+ eStatus = XT9_API(ET9AWSelLstBuild, &sLingInfo, &totalWords, &listIndex, gestureValue);
+
+ if (defaultListIndex)
+ *defaultListIndex = listIndex == ET9_NO_ACTIVE_INDEX || exactWord().isEmpty() ? -1 : static_cast<int>(listIndex);
+
+ if (eStatus)
+ return QStringList();
+
+ QStringList list;
+ for (ET9U8 i = 0; i < totalWords; ++i) {
+ ET9AWWordInfo *wordInfo = nullptr;
+ eStatus = ET9AWSelLstGetWord(&sLingInfo, &wordInfo, i);
+ if (eStatus || !wordInfo)
+ return QStringList();
+
+ const QString word = QString::fromUtf16(reinterpret_cast<const char16_t *>(wordInfo->sWord), wordInfo->wWordLen);
+
+ list.append(word);
+ }
+
+ return list;
+}
+
+void Xt9AwIme::selectWord(int index, bool isUserExplicitChoice)
+{
+ if (index < 0)
+ return;
+
+ qCDebug(lcXT9) << "selectWord" << index;
+
+ XT9_API(ET9AWSelLstSelWord, &sLingInfo, static_cast<ET9U8>(index), isUserExplicitChoice);
+}
+
+void Xt9AwIme::noteWordDone(const QString &word)
+{
+ XT9_API(ET9AWNoteWordDone, &sLingInfo, word.utf16(), static_cast<ET9U32>(word.length()), 0);
+}
+
+ET9STATUS Xt9AwIme::ET9AWLdbReadData(ET9AWLingInfo *pLingInfo, ET9U8 *ET9FARDATA *ppbSrc, ET9U32 *pdwSizeInBytes)
+{
+ return reinterpret_cast<Xt9AwIme *>(pLingInfo->pPublicExtension)->ldbReadData(pLingInfo->pLingCmnInfo->dwLdbNum, ppbSrc, pdwSizeInBytes);
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9awime.h b/src/plugins/cerence/xt9/xt9common/xt9awime.h
new file mode 100644
index 00000000..e65e0ba1
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9awime.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9AWIME_H
+#define XT9AWIME_H
+
+#include "xt9ime.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9AwIme : public Xt9Ime
+{
+public:
+ Xt9AwIme(Xt9RequestCallback *requestCallback, Xt9KeyboardGenerator::CodeConverter *codeConverter = nullptr);
+
+ void sysInit() override;
+ bool ldbInit(ET9U32 dwFirstLdbNum, ET9U32 dwSecondLdbNum = ET9PLIDNone, ET9U32 eInputMode = 0) override;
+ void setLdbEnabled(bool enabled);
+ qint64 dlmPreferredSize() const override;
+ bool dlmInit(void *data, qint64 size) override;
+
+ QStringList buildSelectionList(int *defaultListIndex, ET9U16 *gestureValue, ET9STATUS &eStatus) override;
+ void selectWord(int index, bool isUserExplicitChoice);
+
+ void noteWordDone(const QString &word);
+
+private:
+ static ET9STATUS ET9AWLdbReadData(ET9AWLingInfo *pLingInfo, ET9U8 *ET9FARDATA *ppbSrc, ET9U32 *pdwSizeInBytes);
+
+public:
+ ET9AWLingInfo sLingInfo;
+ ET9AWLingCmnInfo sLingCmnInfo;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9AWIME_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9callbacks.h b/src/plugins/cerence/xt9/xt9common/xt9callbacks.h
new file mode 100644
index 00000000..1cb75973
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9callbacks.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9CPALLBACKS_H
+#define XT9CPALLBACKS_H
+
+#include <et9api.h>
+#include <QtGlobal>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9RequestCallback {
+public:
+ virtual ET9STATUS request(ET9_Request *const pRequest) = 0;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9CPALLBACKS_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9cpime.cpp b/src/plugins/cerence/xt9/xt9common/xt9cpime.cpp
new file mode 100644
index 00000000..5dff7495
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9cpime.cpp
@@ -0,0 +1,284 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9cpime.h"
+#include "xt9languagemap.h"
+#include "xt9dbfile.h"
+#include "xt9callbacks.h"
+#include <QStandardPaths>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+static const ET9SYMB CANGJIE_MAP[] = {
+ 0x65E5, 0x6708, 0x91D1, 0x6728, 0x6C34, 0x706B, 0x571F, 0x7AF9, 0x6208,
+ 0x5341, 0x5927, 0x4E2D, 0x4E00, 0x5F13, 0x4EBA, 0x5FC3, 0x624B, 0x53E3,
+ 0x5C38, 0x5EFF, 0x5C71, 0x5973, 0x7530, 0x96E3, 0x535C };
+static const size_t CANGJIE_MAP_SIZE = sizeof(CANGJIE_MAP) / sizeof(ET9SYMB);
+static const ET9SYMB CANGJIE_WILDCARD_SYMB = 0x91CD;
+
+static ET9SYMB GetCangjieMappingSymb(ET9SYMB symb)
+{
+ if (symb == CANGJIE_WILDCARD_SYMB)
+ return ET9CPCANGJIEWILDCARD;
+
+ for (size_t i = 0; i < CANGJIE_MAP_SIZE; i++) {
+ if (CANGJIE_MAP[i] == symb)
+ return static_cast<ET9SYMB>(i + 'a');
+ }
+
+ return symb;
+}
+
+static ET9SYMB GetCangjieSymb(ET9SYMB symb)
+{
+ if (symb == ET9CPCANGJIEWILDCARD)
+ return CANGJIE_WILDCARD_SYMB;
+
+ if (ET9CPIsCangJieLowerSymbol(symb))
+ return CANGJIE_MAP[symb - 'a'];
+
+ if (ET9CPIsCangJieUpperSymbol(symb))
+ return CANGJIE_MAP[symb - 'A'];
+
+ return symb;
+}
+
+class CangjieConverter : public Xt9KeyboardGenerator::CodeConverter {
+public:
+ QString convertTo(const QString &codes) const override
+ {
+ QVector<ushort> cangjieBuf(codes.size());
+ for (int i = 0; i < codes.size(); ++i) {
+ cangjieBuf[i] = GetCangjieSymb(codes.at(i).unicode());
+ }
+ return QString::fromUtf16(reinterpret_cast<const char16_t *>(cangjieBuf.constData()), cangjieBuf.size());
+ }
+
+ QString convertFrom(const QString &codes) const override
+ {
+ QVector<ushort> cangjieBuf(codes.size());
+ for (int i = 0; i < codes.size(); ++i) {
+ cangjieBuf[i] = GetCangjieMappingSymb(codes.at(i).unicode());
+ }
+ return QString::fromUtf16(reinterpret_cast<const char16_t *>(cangjieBuf.constData()), cangjieBuf.size());
+ }
+};
+
+Q_GLOBAL_STATIC(CangjieConverter, cangjieConverter)
+
+Xt9CpIme::Xt9CpIme(Xt9RequestCallback *requestCallback) :
+ Xt9Ime(requestCallback)
+{
+}
+
+Xt9KeyboardGenerator::CodeConverter *Xt9CpIme::getCangjieConverter()
+{
+ return cangjieConverter;
+}
+
+void Xt9CpIme::sysInit()
+{
+ Xt9Ime::sysInit();
+ memset(&sLingInfo, 0, sizeof(sLingInfo));
+ XT9_API(ET9CPSysInit, &sLingInfo, &sWordSymbInfo, this);
+}
+
+bool Xt9CpIme::ldbInit(ET9U32 dwFirstLdbNum, ET9U32 dwSecondLdbNum, ET9U32 eInputMode)
+{
+ ET9STATUS eStatus;
+ Q_UNUSED(dwSecondLdbNum)
+
+ eStatus = XT9_API(ET9CPLdbInit, &sLingInfo, dwFirstLdbNum, &ET9CPLdbReadData);
+ if (!eStatus) {
+ XT9_API(ET9CPSetInputMode, &sLingInfo, static_cast<ET9CPMode>(eInputMode));
+ XT9_API(ET9CPClearComponent, &sLingInfo);
+ XT9_API(ET9CPSetBilingual, &sLingInfo);
+ }
+
+ return !eStatus;
+}
+
+qint64 Xt9CpIme::dlmPreferredSize() const
+{
+ return ET9CPDLM_SIZE_NORMAL;
+}
+
+bool Xt9CpIme::dlmInit(void *data, qint64 size)
+{
+ ET9STATUS eStatus;
+
+ eStatus = XT9_API(ET9CPDLMInit, &sLingInfo, static_cast<ET9CPDLM_info *>(data), static_cast<ET9U32>(size), nullptr);
+
+ return !eStatus;
+}
+
+QString Xt9CpIme::exactWord(int *wordCompLen)
+{
+ QString exactWord = Xt9Ime::exactWord(wordCompLen);
+
+ ET9CPPhrase phrase;
+ ET9CPSpell spell;
+ ET9U8 selSymbCount = 0;
+ if (!XT9_API(ET9CPGetSelection, &sLingInfo, &phrase, &spell, &selSymbCount) && selSymbCount)
+ exactWord.remove(0, static_cast<int>(selSymbCount));
+
+ replaceSpecialSymbol(exactWord);
+
+ return exactWord;
+}
+
+void Xt9CpIme::replaceSpecialSymbol(QString &exactWord) const
+{
+ for (int i = 0; i < exactWord.length(); ++i) {
+ ET9SYMB symb = exactWord.at(i).unicode();
+ if (ET9CPSymToCPTone(symb)) {
+ exactWord.replace(i, 1, QChar(symb - ET9CPTONE1 + '1'));
+ } else if (ET9CPIsStrokeSymbol(symb)) {
+ int strokeIndex = symb - ET9CPSTROKE1;
+ static const ushort STROKE_TABLE[ET9CPSTROKEWILDCARD - ET9CPSTROKE1 + 1] = {
+ 0x4E00,
+ 0x4E28,
+ 0x4E3F,
+ 0x4E36,
+ 0x4E5B,
+ '*'
+ };
+ exactWord.replace(i, 1, QChar(STROKE_TABLE[strokeIndex]));
+ }
+ }
+}
+
+QString Xt9CpIme::spell()
+{
+ ET9STATUS eStatus;
+ ET9CPSpell spell;
+
+ eStatus = XT9_API(ET9CPGetSpell, &sLingInfo, &spell);
+ if (eStatus == ET9STATUS_NEED_SELLIST_BUILD) {
+ ET9U16 gestureValue;
+ eStatus = XT9_API(ET9CPBuildSelectionList, &sLingInfo, &gestureValue);
+ if (eStatus)
+ return QString();
+
+ eStatus = XT9_API(ET9CPGetSpell, &sLingInfo, &spell);
+ if (eStatus)
+ return QString();
+ } else if (eStatus) {
+ return QString();
+ }
+
+ QString result = QString::fromUtf16(reinterpret_cast<const char16_t *>(spell.pSymbs), spell.bLen);
+
+ replaceSpecialSymbol(result);
+
+ return result;
+}
+
+QStringList Xt9CpIme::buildSelectionList(int *defaultListIndex, ET9U16 *gestureValue, ET9STATUS &eStatus)
+{
+ ET9U16 totalWords;
+
+ eStatus = XT9_API(ET9CPBuildSelectionList, &sLingInfo, gestureValue);
+
+ if (defaultListIndex)
+ *defaultListIndex = -1;
+
+ if (eStatus)
+ return QStringList();
+
+ eStatus = XT9_API(ET9CPGetPhraseCount, &sLingInfo, &totalWords);
+
+ if (defaultListIndex && totalWords > 0 && !exactWord().isEmpty())
+ *defaultListIndex = 0;
+
+ QStringList list;
+ for (ET9U16 i = 0; i < totalWords; ++i) {
+ ET9CPPhrase phrase;
+ ET9CPSpell spell;
+ ET9CPPhraseSource phraseSource;
+ eStatus = ET9CPGetPhrase(&sLingInfo, i, &phrase, &spell, &phraseSource);
+ if (eStatus)
+ return QStringList();
+
+ const QString word = QString::fromUtf16(reinterpret_cast<const char16_t *>(phrase.pSymbs), phrase.bLen);
+
+ list.append(word);
+ }
+
+ return list;
+}
+
+ET9STATUS Xt9CpIme::selectWord(int index)
+{
+ ET9CPPhrase phrase;
+ ET9CPSpell spell;
+ ET9CPPhraseSource phraseSource;
+ ET9STATUS eStatus;
+
+ qCDebug(lcXT9) << "selectWord" << index;
+
+ eStatus = XT9_API(ET9CPGetPhrase, &sLingInfo, static_cast<ET9U16>(index), &phrase, &spell, &phraseSource);
+ if (!eStatus) {
+ eStatus = XT9_API(ET9CPSelectPhrase, &sLingInfo, static_cast<ET9U16>(index), &spell);
+ }
+
+ return eStatus;
+}
+
+void Xt9CpIme::cursorMoved()
+{
+ const ET9U32 maxBufLen = 456;
+ ET9SYMB buffer[maxBufLen];
+ ET9_Request request;
+
+ request.eType = ET9_REQ_BufferContext;
+ request.data.sBufferContextInfo.psBuf = buffer;
+ request.data.sBufferContextInfo.dwMaxBufLen = maxBufLen;
+ request.data.sBufferContextInfo.dwBufLen = static_cast<ET9U32>(-1);
+
+ _requestCallback->request(&request);
+
+ if (request.data.sBufferContextInfo.dwBufLen != static_cast<ET9U32>(-1))
+ ET9CPSetContext(&sLingInfo, request.data.sBufferContextInfo.psBuf, request.data.sBufferContextInfo.dwBufLen);
+ else
+ ET9CPSetContext(&sLingInfo, request.data.sBufferContextInfo.psBuf, 0);
+}
+
+void Xt9CpIme::commitSelection()
+{
+ XT9_API(ET9CPCommitSelection, &sLingInfo);
+}
+
+ET9SYMB Xt9CpIme::lastSymb()
+{
+ const QString word = Xt9Ime::exactWord();
+ const int wordLength = word.length();
+ return wordLength > 0 ? word.at(wordLength - 1).unicode() : 0;
+}
+
+bool Xt9CpIme::addTone(ET9CPSYMB symb)
+{
+ ET9STATUS eStatus;
+
+ eStatus = XT9_API(ET9AddExplicitSymb, sLingInfo.Base.pWordSymbInfo, symb, 0, ET9NOSHIFT, ET9_NO_ACTIVE_INDEX);
+ if (eStatus)
+ return false;
+
+ eStatus = XT9_API(ET9CPBuildSelectionList, &sLingInfo, nullptr);
+ if (eStatus) {
+ XT9_API(ET9ClearOneSymb, sLingInfo.Base.pWordSymbInfo);
+ return false;
+ }
+
+ return true;
+}
+
+ET9STATUS Xt9CpIme::ET9CPLdbReadData(ET9CPLingInfo *pLingInfo, ET9U8 *ET9FARDATA *ppbSrc, ET9U32 *pdwSizeInBytes)
+{
+ return reinterpret_cast<Xt9CpIme *>(pLingInfo->pPublicExtension)->ldbReadData(pLingInfo->dwLdbNum, ppbSrc, pdwSizeInBytes);
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9cpime.h b/src/plugins/cerence/xt9/xt9common/xt9cpime.h
new file mode 100644
index 00000000..09be859f
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9cpime.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9CPIME_H
+#define XT9CPIME_H
+
+#include "xt9ime.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9CpIme : public Xt9Ime
+{
+public:
+ Xt9CpIme(Xt9RequestCallback *requestCallback);
+
+ static Xt9KeyboardGenerator::CodeConverter *getCangjieConverter();
+
+ void sysInit() override;
+ bool ldbInit(ET9U32 dwFirstLdbNum, ET9U32 dwSecondLdbNum = ET9PLIDNone, ET9U32 eInputMode = 0) override;
+ qint64 dlmPreferredSize() const override;
+ bool dlmInit(void *data, qint64 size) override;
+
+ QString exactWord(int *wordCompLen = nullptr) override;
+ void replaceSpecialSymbol(QString &exactWord) const;
+ QString spell();
+ QStringList buildSelectionList(int *defaultListIndex, ET9U16 *gestureValue, ET9STATUS &eStatus) override;
+ ET9STATUS selectWord(int index);
+
+ void cursorMoved() override;
+
+ void commitSelection();
+
+ ET9SYMB lastSymb();
+ bool addTone(ET9CPSYMB symb);
+
+private:
+ static ET9STATUS ET9CPLdbReadData(ET9CPLingInfo *pLingInfo, ET9U8 *ET9FARDATA *ppbSrc, ET9U32 *pdwSizeInBytes);
+
+public:
+ ET9CPLingInfo sLingInfo;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9CPIME_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9ime.cpp b/src/plugins/cerence/xt9/xt9common/xt9ime.cpp
new file mode 100644
index 00000000..aac647b1
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9ime.cpp
@@ -0,0 +1,304 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9ime.h"
+#include "xt9callbacks.h"
+#include "xt9languagemap.h"
+#include "xt9dbfile.h"
+#include <QStandardPaths>
+#include <QDir>
+#include <QLoggingCategory>
+#include <QtVirtualKeyboard/qvirtualkeyboarddictionarymanager.h>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Q_LOGGING_CATEGORY(lcXT9, "qt.virtualkeyboard.xt9")
+
+Xt9Ime::Xt9Ime(Xt9RequestCallback *requestCallback, Xt9KeyboardGenerator::CodeConverter *codeConverter) :
+ _requestCallback(requestCallback),
+ codeConverter(codeConverter)
+{
+}
+
+void Xt9Ime::sysInit()
+{
+ if (!ldbManager)
+ ldbManager.reset(new Xt9LdbManager());
+ memset(&sWordSymbInfo, 0, sizeof(sWordSymbInfo));
+ memset(&sKdbInfo, 0, sizeof(sKdbInfo));
+ memset(&sSearchEngine, 0, sizeof(sSearchEngine));
+ XT9_API(ET9WordSymbInit, &sWordSymbInfo, 1, ET9Request, this);
+ XT9_API(ET9NAV_Init, &sSearchEngine, &sWordSymbInfo);
+}
+
+bool Xt9Ime::kdbInit(const QVariantMap &vkbLayout)
+{
+ ET9STATUS eStatus;
+
+ keyboardGenerator.reset(new Xt9KeyboardGenerator(vkbLayout, codeConverter));
+
+ const ET9U32 dwFirstKdbNum = static_cast<ET9U32>(vkbLayout[Xt9KeyboardGenerator::PRIMARY_ID].toInt());
+
+ eStatus = XT9_API(ET9KDB_Init,
+ &sKdbInfo,
+ &sWordSymbInfo,
+ dwFirstKdbNum,
+ 0,
+ &ET9KDBLoad,
+ &ET9KDBRequest,
+ this);
+
+ return !eStatus;
+}
+
+void Xt9Ime::uninit()
+{
+ clearInput();
+ keyboardGenerator.reset();
+ ldbManager->closeAll();
+ sysInit();
+}
+
+QString Xt9Ime::exactWord(int *wordCompLen)
+{
+ ET9SimpleWord simpleWord;
+ ET9STATUS eStatus;
+
+ eStatus = XT9_API(ET9GetExactWord, &sWordSymbInfo, &simpleWord);
+ if (eStatus) {
+ if (wordCompLen)
+ *wordCompLen = 0;
+ return QString();
+ }
+
+ if (wordCompLen)
+ *wordCompLen = simpleWord.wCompLen;
+
+ return QString::fromUtf16(reinterpret_cast<const char16_t *>(simpleWord.sString), simpleWord.wLen);
+}
+
+bool Xt9Ime::hasActiveInput() const
+{
+ return ET9HasActiveInput(&sWordSymbInfo);
+}
+
+QStringList Xt9Ime::buildSelectionList(int *defaultListIndex, unsigned short *gestureValue)
+{
+ ET9STATUS eStatus = ET9STATUS_NONE;
+ return buildSelectionList(defaultListIndex, gestureValue, eStatus);
+}
+
+void Xt9Ime::cursorMoved()
+{
+ XT9_API(ET9CursorMoved, &sWordSymbInfo, 1);
+}
+
+void Xt9Ime::clearInput()
+{
+ XT9_API(ET9ClearAllSymbs, &sWordSymbInfo);
+}
+
+void Xt9Ime::setCapsLock()
+{
+ XT9_API(ET9SetCapsLock, &sWordSymbInfo);
+}
+
+void Xt9Ime::setShift()
+{
+ XT9_API(ET9SetShift, &sWordSymbInfo);
+}
+
+void Xt9Ime::setUnShift()
+{
+ XT9_API(ET9SetUnShift, &sWordSymbInfo);
+}
+
+void Xt9Ime::setWorkingDirectory(const QString &workingDirectory)
+{
+ QString dir = workingDirectory;
+ if (!workingDirectory.isEmpty() && !dir.endsWith(QLatin1Char('/')))
+ dir.append(QLatin1Char('/'));
+ dir = QDir::toNativeSeparators(dir);
+ XT9_API(ET9NAVCore_SetWorkingDirectory, &sSearchEngine, dir.toUtf8().constData());
+}
+
+bool Xt9Ime::indexExists(quint16 id)
+{
+ ET9BOOL exists = 0;
+
+ XT9_API(ET9NAVCore_IndexExists, &sSearchEngine, id, &exists);
+
+ return exists != 0;
+}
+
+bool Xt9Ime::createIndex(quint16 id, quint32 contentInfo)
+{
+ ET9STATUS eStatus;
+ ET9NAVTypeInfo sTypeInfo;
+
+ if (indexExists(id)) {
+ removeIndex(id);
+ }
+
+ eStatus = XT9_API(ET9NAVTypeInfo_Init, &sTypeInfo, id, ET9NAVNO_RECORD_KEY_LENGTH, 0, ET9NAVSTORE_DISPLAY_STR_IN_INDEX);
+ if (!eStatus) {
+ eStatus = XT9_API(ET9NAVCore_CreateIndex, &sSearchEngine, id, &sTypeInfo, 1, contentInfo, 0);
+ }
+
+ return !eStatus;
+}
+
+bool Xt9Ime::insertRecord(quint16 id, const QString &phrase, const QString &tokens)
+{
+ ET9STATUS eStatus;
+ ET9NAVRecord sRecord;
+
+ eStatus = XT9_API(ET9NAVRecord_Init_NoKey, &sSearchEngine, &sRecord, id);
+ if (!eStatus) {
+ if (tokens.length() > 0) {
+ eStatus = XT9_API(ET9NAVRecord_AddField_Conversion, &sRecord,
+ static_cast<const ET9SYMB *>(tokens.utf16()),
+ static_cast<quint16>(tokens.length()),
+ static_cast<const ET9SYMB *>(phrase.utf16()),
+ static_cast<quint16>(phrase.length()),
+ ET9NAVMATCHLOGIC_ANY);
+ } else {
+ eStatus = XT9_API(ET9NAVRecord_AddField_16BIT, &sRecord,
+ static_cast<const ET9SYMB *>(phrase.utf16()),
+ static_cast<quint16>(phrase.length()),
+ ET9NAVMATCHLOGIC_ANY);
+ }
+
+ if (!eStatus) {
+ eStatus = XT9_API(ET9NAVCore_InsertRecord, &sSearchEngine, &sRecord);
+ }
+ }
+
+ return !eStatus;
+}
+
+void Xt9Ime::finalizeIndex(quint16 id)
+{
+ XT9_API(ET9NAVCore_FinalizeIndex, &sSearchEngine, id);
+}
+
+void Xt9Ime::updateIndex(quint16 id, const QStringList &wordList)
+{
+ createIndex(id);
+ for (const QString &word : wordList) {
+ insertRecord(id, word);
+ }
+ finalizeIndex(id);
+}
+
+void Xt9Ime::removeIndex(quint16 id)
+{
+ XT9_API(ET9NAVCore_RemoveIndex, &sSearchEngine, id);
+}
+
+void Xt9Ime::removeAllIndexes()
+{
+ XT9_API(ET9NAVCore_RemoveAllIndexes, &sSearchEngine);
+}
+
+void Xt9Ime::mountIndex(quint16 id)
+{
+ XT9_API(ET9NAVCore_MountIndex, &sSearchEngine, id, ET9NAVMountProperty_ReadOnlyNoValidation);
+}
+
+void Xt9Ime::unmountIndex(quint16 id)
+{
+ XT9_API(ET9NAVCore_UnmountIndex, &sSearchEngine, id);
+}
+
+ET9STATUS Xt9Ime::ldbReadData(ET9U32 dwLdbNum, ET9U8 *ET9FARDATA *ppbSrc, ET9U32 *pdwSizeInBytes)
+{
+ const QLocale locale = Xt9LanguageMap::locale(dwLdbNum);
+ if (locale.language() == QLocale::AnyLanguage) {
+ return ET9STATUS_READ_DB_FAIL;
+ }
+
+ const void *data;
+ qint64 size;
+ if (!ldbManager->loadDictionary(locale, data, size)) {
+ return ET9STATUS_READ_DB_FAIL;
+ }
+
+ *ppbSrc = static_cast<ET9U8 *>(const_cast<void *>(data));
+ *pdwSizeInBytes = static_cast<ET9U32>(size);
+
+ return ET9STATUS_NONE;
+}
+
+ET9STATUS Xt9Ime::ET9Request(ET9WordSymbInfo *const pWordSymbInfo, ET9_Request *const pRequest)
+{
+ return reinterpret_cast<Xt9Ime *>(pWordSymbInfo->pPublicExtension)->request(pRequest);
+}
+
+ET9STATUS Xt9Ime::request(ET9_Request *const pRequest)
+{
+#ifdef XT9_DEBUG
+ qCDebug(lcXT9) << "ET9Request, type =" << pRequest->eType;
+#endif
+ return _requestCallback->request(pRequest);
+}
+
+ET9STATUS Xt9Ime::ET9KDBLoad(ET9KDBInfo *const pKdbInfo, const ET9U32 dwKdbNum, const ET9U16 wPageNum)
+{
+ return reinterpret_cast<Xt9Ime *>(pKdbInfo->pPublicExtension)->kdbLoad(dwKdbNum, wPageNum);
+}
+
+ET9STATUS Xt9Ime::kdbLoad(const ET9U32 dwKdbNum, const ET9U16 wPageNum)
+{
+ Q_UNUSED(dwKdbNum)
+
+ ET9STATUS eStatus;
+ ET9UINT nErrorLine = 0;
+
+ if (keyboardGenerator->vkbLayout.isEmpty()) {
+ qCWarning(lcXT9) << "Keyboard layout is not set.";
+ return ET9STATUS_READ_DB_FAIL;
+ }
+
+ QByteArray xmlData = keyboardGenerator->createXmlLayout();
+ if (xmlData.isEmpty()) {
+ qCWarning(lcXT9) << "Failed to create xml layout.";
+ return ET9STATUS_READ_DB_FAIL;
+ }
+
+ eStatus = XT9_API(ET9KDB_Load_XmlKDB,
+ &sKdbInfo,
+ static_cast<ET9U16>(qRound(keyboardGenerator->layoutWidth)),
+ static_cast<ET9U16>(qRound(keyboardGenerator->layoutHeight)),
+ wPageNum,
+ reinterpret_cast<const ET9U8 *>(xmlData.constData()),
+ static_cast<ET9U32>(xmlData.size()),
+ nullptr,
+ nullptr,
+ &nErrorLine);
+
+ if (eStatus) {
+ qCWarning(lcXT9).nospace() << "Failed to load xml layout, status: " << eStatus << ", line: " << nErrorLine;
+ qCWarning(lcXT9).nospace().noquote() << xmlData.constData();
+ }
+
+ return eStatus;
+}
+
+ET9STATUS Xt9Ime::ET9KDBRequest(ET9KDBInfo *const pKDBInfo, ET9WordSymbInfo *const pWordSymbInfo, ET9KDB_Request *const pET9KDB_Request)
+{
+ Q_UNUSED(pWordSymbInfo)
+ return reinterpret_cast<Xt9Ime *>(pKDBInfo->pPublicExtension)->kdbRequest(pET9KDB_Request);
+}
+
+ET9STATUS Xt9Ime::kdbRequest(ET9KDB_Request *const pET9KDB_Request)
+{
+#ifdef XT9_DEBUG
+ qCDebug(lcXT9) << "ET9KDB_Request, type =" << pET9KDB_Request->eType;
+#endif
+ return ET9STATUS_NONE;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9ime.h b/src/plugins/cerence/xt9/xt9common/xt9ime.h
new file mode 100644
index 00000000..c86fc719
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9ime.h
@@ -0,0 +1,108 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9IME_H
+#define XT9IME_H
+
+#include <QtGlobal>
+#include <QVariantMap>
+#include <QLoggingCategory>
+#include <et9api.h>
+#include "xt9keyboardgenerator.h"
+#include "xt9ldbmanager.h"
+#include "xt9dbfile.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Q_DECLARE_LOGGING_CATEGORY(lcXT9)
+
+#ifdef XT9_DEBUG
+inline ET9STATUS xt9_log(ET9STATUS eStatus, const char *funcName) {
+ if (eStatus)
+ qCWarning(lcXT9) << funcName << "->" << eStatus;
+ else
+ qCDebug(lcXT9) << funcName;
+ return eStatus;
+}
+inline ET9STATUS xt9_nolog(ET9STATUS eStatus, const char *funcName) {
+ if (eStatus)
+ qCWarning(lcXT9) << funcName << "->" << eStatus;
+ return eStatus;
+}
+#else
+#define xt9_log(func, funcName) func
+#define xt9_nolog(func, funcName) func
+#endif
+
+#define XT9_API(FUNC_NAME, ...) \
+ xt9_log(FUNC_NAME(__VA_ARGS__), "" # FUNC_NAME)
+#define XT9_VAPI(FUNC_NAME, ...) \
+ xt9_nolog(FUNC_NAME(__VA_ARGS__), "" # FUNC_NAME)
+
+class Xt9RequestCallback;
+
+class Xt9Ime
+{
+public:
+ Xt9Ime(Xt9RequestCallback *requestCallback, Xt9KeyboardGenerator::CodeConverter *codeConverter = nullptr);
+ virtual ~Xt9Ime() {}
+
+ virtual void sysInit();
+ virtual bool ldbInit(ET9U32 dwFirstLdbNum, ET9U32 dwSecondLdbNum = ET9PLIDNone, ET9U32 eInputMode = 0) = 0;
+ virtual qint64 dlmPreferredSize() const = 0;
+ virtual bool dlmInit(void *data, qint64 size) = 0;
+ bool kdbInit(const QVariantMap &vkbLayout);
+
+ virtual void uninit();
+
+ virtual QString exactWord(int *wordCompLen = nullptr);
+ bool hasActiveInput() const;
+ virtual QStringList buildSelectionList(int *defaultListIndex, ET9U16 *gestureValue);
+ virtual QStringList buildSelectionList(int *defaultListIndex, ET9U16 *gestureValue, ET9STATUS &eStatus) = 0;
+ virtual void cursorMoved();
+ void clearInput();
+ void setCapsLock();
+ void setShift();
+ void setUnShift();
+
+ void setWorkingDirectory(const QString &workingDirectory);
+ bool indexExists(quint16 id);
+ bool createIndex(quint16 id, quint32 contentInfo = ET9_ContentInfo_None);
+ bool insertRecord(quint16 id, const QString &phrase, const QString &tokens = QString());
+ void finalizeIndex(quint16 id);
+ void updateIndex(quint16 id, const QStringList &wordList);
+ void removeIndex(quint16 id);
+ void removeAllIndexes();
+ void mountIndex(quint16 id);
+ void unmountIndex(quint16 id);
+
+protected:
+ ET9STATUS ldbReadData(ET9U32 dwLdbNum, ET9U8 *ET9FARDATA *ppbSrc, ET9U32 *pdwSizeInBytes);
+
+private:
+ static ET9STATUS ET9Request(ET9WordSymbInfo *const pWordSymbInfo, ET9_Request *const pRequest);
+ ET9STATUS request(ET9_Request *const pRequest);
+
+ static ET9STATUS ET9KDBLoad(ET9KDBInfo *const pKdbInfo, const ET9U32 dwKdbNum, const ET9U16 wPageNum);
+ ET9STATUS kdbLoad(const ET9U32 dwKdbNum, const ET9U16 wPageNum);
+
+ static ET9STATUS ET9KDBRequest(ET9KDBInfo *const pKDBInfo, ET9WordSymbInfo *const pWordSymbInfo, ET9KDB_Request *const pET9KDB_Request);
+ ET9STATUS kdbRequest(ET9KDB_Request *const pET9KDB_Request);
+
+public:
+ ET9WordSymbInfo sWordSymbInfo;
+ ET9KDBInfo sKdbInfo;
+ ET9NAVInfo sSearchEngine;
+ Xt9RequestCallback *const _requestCallback;
+ const Xt9KeyboardGenerator::CodeConverter *codeConverter;
+ QSharedPointer<Xt9LdbManager> ldbManager;
+
+protected:
+ QScopedPointer<Xt9KeyboardGenerator> keyboardGenerator;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9IME_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9jime.cpp b/src/plugins/cerence/xt9/xt9common/xt9jime.cpp
new file mode 100644
index 00000000..aef47329
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9jime.cpp
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9jime.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class KanaConverter : public Xt9KeyboardGenerator::CodeConverter {
+public:
+ QString convertTo(const QString &codes) const override
+ {
+ QString buf(codes);
+ for (int i = 0; i < buf.length(); ++i) {
+ const ushort uc = buf.at(i).unicode();
+ if ((uc >= 0x30a1 && uc <= 0x30f6) || uc == 0x30fd || uc == 0x30fe)
+ buf.replace(i, 1, QChar(uc - 0x0060));
+ }
+ return buf;
+ }
+
+ QString convertFrom(const QString &codes) const override
+ {
+ QString buf(codes);
+ for (int i = 0; i < buf.length(); ++i) {
+ const ushort uc = buf.at(i).unicode();
+ if ((uc >= 0x30a1 && uc <= 0x30f6) || uc == 0x30fd || uc == 0x30fe)
+ buf.replace(i, 1, QChar(uc + 0x0060));
+ }
+ return buf;
+ }
+};
+
+Q_GLOBAL_STATIC(KanaConverter, kanaConverter)
+
+Xt9JIme::Xt9JIme(Xt9RequestCallback *requestCallback) :
+ Xt9AwIme(requestCallback, kanaConverter)
+{
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9jime.h b/src/plugins/cerence/xt9/xt9common/xt9jime.h
new file mode 100644
index 00000000..57ed9168
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9jime.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9JIME_H
+#define XT9JIME_H
+
+#include "xt9awime.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9JIme : public Xt9AwIme
+{
+public:
+ Xt9JIme(Xt9RequestCallback *requestCallback);
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9JIME_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdb.cpp b/src/plugins/cerence/xt9/xt9common/xt9kdb.cpp
new file mode 100644
index 00000000..d9a3b05e
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdb.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9kdb.h"
+#include <QXmlStreamWriter>
+#include <QBuffer>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+QByteArray Xt9Kdb::generate(const Xt9KdbLayout &layout, bool prettyPrint)
+{
+ QByteArray result;
+ QXmlStreamWriter writer(&result);
+
+ writer.setAutoFormatting(prettyPrint);
+ writer.writeStartDocument();
+ layout.serialize(writer);
+ writer.writeEndDocument();
+
+ return result;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdb.h b/src/plugins/cerence/xt9/xt9common/xt9kdb.h
new file mode 100644
index 00000000..54762d1b
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdb.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KDB_H
+#define XT9KDB_H
+
+#include "xt9kdblayout.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9Kdb
+{
+ Q_DISABLE_COPY(Xt9Kdb)
+
+public:
+ static QByteArray generate(const Xt9KdbLayout &layout, bool prettyPrint = false);
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KDBGENERATOR_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdbarea.cpp b/src/plugins/cerence/xt9/xt9common/xt9kdbarea.cpp
new file mode 100644
index 00000000..a0157c4c
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdbarea.cpp
@@ -0,0 +1,21 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9kdbarea.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+void Xt9KdbArea::serialize(QXmlStreamWriter &writer) const
+{
+ writer.writeStartElement(QStringLiteral("area"));
+ if (!conditionValue.isEmpty())
+ writer.writeAttribute(QStringLiteral("conditionValue"), conditionValue);
+ for (const Xt9KdbKey &key : keys) {
+ key.serialize(writer);
+ }
+ writer.writeEndElement();
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdbarea.h b/src/plugins/cerence/xt9/xt9common/xt9kdbarea.h
new file mode 100644
index 00000000..552c37ad
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdbarea.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KDBAREA_H
+#define XT9KDBAREA_H
+
+#include "xt9kdbelement.h"
+#include "xt9kdbkey.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9KdbArea : public Xt9KdbElement
+{
+public:
+ void serialize(QXmlStreamWriter &writer) const;
+
+ QString conditionValue;
+ QList<Xt9KdbKey> keys;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KDBAREA_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdbelement.cpp b/src/plugins/cerence/xt9/xt9common/xt9kdbelement.cpp
new file mode 100644
index 00000000..2d46ada3
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdbelement.cpp
@@ -0,0 +1,15 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9kdbelement.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9KdbElement::~Xt9KdbElement()
+{
+
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdbelement.h b/src/plugins/cerence/xt9/xt9common/xt9kdbelement.h
new file mode 100644
index 00000000..229e4213
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdbelement.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KDBELEMENT_H
+#define XT9KDBELEMENT_H
+
+#include <QXmlStreamWriter>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9KdbElement
+{
+public:
+ virtual ~Xt9KdbElement();
+
+ virtual void serialize(QXmlStreamWriter &writer) const = 0;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KDBELEMENT_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdbkey.cpp b/src/plugins/cerence/xt9/xt9common/xt9kdbkey.cpp
new file mode 100644
index 00000000..617154b9
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdbkey.cpp
@@ -0,0 +1,100 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9kdbkey.h"
+#include <QMetaEnum>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+static const QString DP_VALUE = QStringLiteral("%1dp");
+static const QString PC_VALUE = QStringLiteral("%1%");
+const QString HEX_VALUE = QStringLiteral("0x%1");
+
+static bool isPrintable(const QString &str)
+{
+ for (const QChar &chr : str) {
+ if (!QChar::isPrint(chr.unicode()))
+ return false;
+ }
+ return true;
+}
+
+Xt9KdbKey::Xt9KdbKey() :
+ type(Type::nonRegional),
+ name(Name::NONE)
+{
+
+}
+
+void Xt9KdbKey::serialize(QXmlStreamWriter &writer) const
+{
+ /* WORKAROUND:
+ *
+ * This workaround generates a key for each alternate key. This ensures that
+ * ET9KDB_ProcessKeyBySymbol finds the key and that ET9KDB_ModifyCurrentKey
+ * works.
+ */
+ if (hackWriteDistinctKeysForAllCodes && !codes.isEmpty()) {
+ Xt9KdbKey tmpKey;
+ tmpKey = *this;
+ tmpKey.hackWriteDistinctKeysForAllCodes = false;
+ tmpKey.codes.clear();
+ tmpKey.codesShifted.clear();
+ if (!absolute.isEmpty()) {
+ tmpKey.absolute.adjust(0, 0, (tmpKey.absolute.width() / (codes.size() + 1)) - tmpKey.absolute.width(), 0);
+ } else if (!relative.isEmpty()) {
+ tmpKey.relative.adjust(0, 0, (tmpKey.relative.width() / (codes.size() + 1)) - tmpKey.relative.width(), 0);
+ }
+ tmpKey.serialize(writer);
+ if (!absolute.isEmpty()) {
+ tmpKey.absolute.adjust(tmpKey.absolute.width(), 0, tmpKey.absolute.width(), 0);
+ } else if (!relative.isEmpty()) {
+ tmpKey.relative.adjust(tmpKey.relative.width(), 0, tmpKey.relative.width(), 0);
+ }
+ for (const QChar &code : codes) {
+ tmpKey.label = code;
+ if (!labelShifted.isEmpty())
+ tmpKey.labelShifted = code.toUpper();
+ tmpKey.serialize(writer);
+ if (!absolute.isEmpty()) {
+ tmpKey.absolute.adjust(tmpKey.absolute.width(), 0, tmpKey.absolute.width(), 0);
+ } else if (!relative.isEmpty()) {
+ tmpKey.relative.adjust(tmpKey.relative.width(), 0, tmpKey.relative.width(), 0);
+ }
+ }
+ return;
+ }
+ /* WORKAROUND END */
+ if (!absolute.isEmpty()) {
+ writer.writeStartElement(QStringLiteral("key"));
+ writer.writeAttribute(QStringLiteral("keyLeft"), DP_VALUE.arg(absolute.left()));
+ writer.writeAttribute(QStringLiteral("keyTop"), DP_VALUE.arg(absolute.top()));
+ writer.writeAttribute(QStringLiteral("keyWidth"), DP_VALUE.arg(absolute.width()));
+ writer.writeAttribute(QStringLiteral("keyHeight"), DP_VALUE.arg(absolute.height()));
+ } else if (!relative.isEmpty()) {
+ writer.writeStartElement(QStringLiteral("key"));
+ writer.writeAttribute(QStringLiteral("keyLeft"), PC_VALUE.arg(relative.left() * 100.));
+ writer.writeAttribute(QStringLiteral("keyTop"), PC_VALUE.arg(relative.top() * 100.));
+ writer.writeAttribute(QStringLiteral("keyWidth"), PC_VALUE.arg(relative.width() * 100.));
+ writer.writeAttribute(QStringLiteral("keyHeight"), PC_VALUE.arg(relative.height() * 100.));
+ } else {
+ // No geometry, skip
+ return;
+ }
+ writer.writeAttribute(QStringLiteral("keyType"), QMetaEnum::fromType<Xt9KdbKey::Type>().key(static_cast<int>(type)));
+ if (name != Name::NONE)
+ writer.writeAttribute(QStringLiteral("keyName"), QMetaEnum::fromType<Xt9KdbKey::Name>().key(static_cast<int>(name)));
+ if (!label.isEmpty())
+ writer.writeAttribute(QStringLiteral("keyLabel"), isPrintable(label) ? label : joinCodeList(label));
+ if (!labelShifted.isEmpty())
+ writer.writeAttribute(QStringLiteral("keyLabelShifted"), isPrintable(labelShifted) ? labelShifted : joinCodeList(labelShifted));
+ if (!codes.isEmpty())
+ writer.writeAttribute(QStringLiteral("keyCodes"), joinCodeList(codes));
+ if (!codesShifted.isEmpty())
+ writer.writeAttribute(QStringLiteral("keyCodesShifted"), joinCodeList(codesShifted));
+ writer.writeEndElement();
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdbkey.h b/src/plugins/cerence/xt9/xt9common/xt9kdbkey.h
new file mode 100644
index 00000000..bd8e9a83
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdbkey.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KDBKEY_H
+#define XT9KDBKEY_H
+
+#include "xt9kdbelement.h"
+#include <QRect>
+#include <QRectF>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9KdbKey : public Xt9KdbElement
+{
+ Q_GADGET
+
+public:
+ enum class Type {
+ regional,
+ nonRegional,
+ smartPunct,
+ string,
+ function
+ };
+ Q_ENUM(Type)
+
+ // The names must match with et9kbdef.h.
+ // The enum value must not be assigned.
+ enum class Name {
+ NONE,
+ ET9KEY_BACK,
+ ET9KEY_TAB,
+ ET9KEY_NEW_LINE,
+ ET9KEY_SPACE,
+ ET9KEY_LEFT,
+ ET9KEY_UP,
+ ET9KEY_RIGHT,
+ ET9KEY_DOWN,
+ ET9KEY_SHIFT,
+ ET9KEY_LANGUAGE,
+ };
+ Q_ENUM(Name)
+
+ Xt9KdbKey();
+
+ void serialize(QXmlStreamWriter &writer) const;
+
+ QRect absolute;
+ QRectF relative;
+ Type type;
+ Name name;
+ QString label;
+ QString labelShifted;
+ QString codes;
+ QString codesShifted;
+ bool hackWriteDistinctKeysForAllCodes;
+
+private:
+ template<typename T>
+ static QString joinCodeList(const T &codes);
+};
+
+template<typename T>
+QString Xt9KdbKey::joinCodeList(const T &codes)
+{
+ static const QString HEX_VALUE = QStringLiteral("0x%1");
+
+ QString result;
+ bool first = true;
+
+ for (const QChar &code : codes) {
+ if (first)
+ first = false;
+ else
+ result.append(QLatin1Char(','));
+
+ result.append(HEX_VALUE.arg(code.unicode(), 0, 16));
+ }
+
+ return result;
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KDBKEY_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdblayout.cpp b/src/plugins/cerence/xt9/xt9common/xt9kdblayout.cpp
new file mode 100644
index 00000000..454b096e
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdblayout.cpp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9kdblayout.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+Xt9KdbLayout::Xt9KdbLayout() :
+ primaryId(0),
+ secondaryId(0),
+ defaultLayoutWidth(0),
+ defaultLayoutHeight(0),
+ supportsExact(false),
+ smartTouchActive(false)
+{
+
+}
+
+void Xt9KdbLayout::serialize(QXmlStreamWriter &writer) const
+{
+ writer.writeStartElement(QStringLiteral("keyboard"));
+ writer.writeAttribute(QStringLiteral("primaryId"), QString::number(primaryId));
+ writer.writeAttribute(QStringLiteral("secondaryId"), QString::number(secondaryId));
+ if (defaultLayoutWidth > 0 && defaultLayoutHeight > 0) {
+ writer.writeAttribute(QStringLiteral("defaultLayoutWidth"), QString::number(defaultLayoutWidth));
+ writer.writeAttribute(QStringLiteral("defaultLayoutHeight"), QString::number(defaultLayoutHeight + 1));
+ }
+ if (supportsExact)
+ writer.writeAttribute(QStringLiteral("supportsExact"), QString(QStringLiteral("%1")).arg(supportsExact));
+ if (smartTouchActive)
+ writer.writeAttribute(QStringLiteral("smartTouchActive"), QString(QStringLiteral("%1")).arg(smartTouchActive));
+ for (const Xt9KdbArea &area : areas) {
+ area.serialize(writer);
+ }
+ writer.writeEndElement();
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kdblayout.h b/src/plugins/cerence/xt9/xt9common/xt9kdblayout.h
new file mode 100644
index 00000000..1dc18053
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kdblayout.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KDBLAYOUT_H
+#define XT9KDBLAYOUT_H
+
+#include "xt9kdbarea.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9KdbLayout : public Xt9KdbElement
+{
+public:
+ Xt9KdbLayout();
+
+ void serialize(QXmlStreamWriter &writer) const;
+
+ int primaryId;
+ int secondaryId;
+ int defaultLayoutWidth;
+ int defaultLayoutHeight;
+ bool supportsExact;
+ bool smartTouchActive;
+ QList<Xt9KdbArea> areas;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KDBLAYOUT_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9keyboardgenerator.cpp b/src/plugins/cerence/xt9/xt9common/xt9keyboardgenerator.cpp
new file mode 100644
index 00000000..830b1d2b
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9keyboardgenerator.cpp
@@ -0,0 +1,141 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9keyboardgenerator.h"
+#include "xt9kdblayout.h"
+#include "xt9kdb.h"
+#include <QtVirtualKeyboard/qvirtualkeyboard_namespace.h>
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+const QString Xt9KeyboardGenerator::PRIMARY_ID = QStringLiteral("primaryId");
+const QString Xt9KeyboardGenerator::SECONDARY_ID = QStringLiteral("secondaryId");
+const QString Xt9KeyboardGenerator::WIDTH = QStringLiteral("width");
+const QString Xt9KeyboardGenerator::HEIGHT = QStringLiteral("height");
+const QString Xt9KeyboardGenerator::KEY = QStringLiteral("key");
+const QString Xt9KeyboardGenerator::KEYS = QStringLiteral("keys");
+const QString Xt9KeyboardGenerator::KEY_TYPE = QStringLiteral("keyType");
+const QString Xt9KeyboardGenerator::TOP = QStringLiteral("top");
+const QString Xt9KeyboardGenerator::LEFT = QStringLiteral("left");
+const QString Xt9KeyboardGenerator::TEXT = QStringLiteral("text");
+const QString Xt9KeyboardGenerator::ALT_KEYS = QStringLiteral("altKeys");
+const int Xt9KeyboardGenerator::EMOTICON_KEY = 0xE000;
+
+Xt9KeyboardGenerator::Xt9KeyboardGenerator(const QVariantMap &vkbLayout, const CodeConverter *keyConverter) :
+ vkbLayout(vkbLayout),
+ layoutWidth(vkbLayout[WIDTH].toDouble()),
+ layoutHeight(vkbLayout[HEIGHT].toDouble()),
+ codeConverter(keyConverter)
+{
+
+}
+
+QByteArray Xt9KeyboardGenerator::createXmlLayout() const
+{
+ if (layoutWidth <= 0 || layoutHeight <= 0)
+ return QByteArray();
+
+ Xt9KdbLayout kdbLayout;
+ kdbLayout.primaryId = vkbLayout[PRIMARY_ID].toInt();
+ kdbLayout.secondaryId = vkbLayout[SECONDARY_ID].toInt();
+ kdbLayout.defaultLayoutWidth = qRound(layoutWidth);
+ kdbLayout.defaultLayoutHeight = qRound(layoutHeight);
+
+ Xt9KdbArea xt9Area;
+ if (convertFromVkb(xt9Area))
+ kdbLayout.areas.append(xt9Area);
+
+ return Xt9Kdb::generate(kdbLayout, true);
+}
+
+bool Xt9KeyboardGenerator::convertFromVkb(Xt9KdbArea &xt9Area) const
+{
+ QVariantList vkbKeys = vkbLayout[KEYS].toList();
+
+ for (const QVariant &i : vkbKeys) {
+ Xt9KdbKey xt9Key;
+ if (convertFromVkb(xt9Key, i.toMap()))
+ xt9Area.keys.append(xt9Key);
+ }
+
+ return true;
+}
+
+bool Xt9KeyboardGenerator::convertFromVkb(Xt9KdbKey &xt9Key, const QVariantMap &vkbKey) const
+{
+ const KeyType vkbKeyType = static_cast<KeyType>(vkbKey[KEY_TYPE].toInt());
+
+ switch (vkbKeyType) {
+ case KeyType::BackspaceKey:
+ xt9Key.type = Xt9KdbKey::Type::function;
+ xt9Key.name = Xt9KdbKey::Name::ET9KEY_BACK;
+ break;
+
+ case KeyType::EnterKey:
+ xt9Key.type = Xt9KdbKey::Type::function;
+ xt9Key.name = Xt9KdbKey::Name::ET9KEY_NEW_LINE;
+ break;
+
+ case KeyType::Key:
+ case KeyType::FlickKey:
+ xt9Key.hackWriteDistinctKeysForAllCodes = (vkbKeyType == KeyType::FlickKey);
+ switch (vkbKey[KEY].toInt()) {
+ case Qt::Key_Space:
+ xt9Key.type = Xt9KdbKey::Type::function;
+ xt9Key.name = Xt9KdbKey::Name::ET9KEY_SPACE;
+ break;
+
+ case EMOTICON_KEY:
+ xt9Key.type = Xt9KdbKey::Type::string;
+ break;
+
+ default:
+ xt9Key.type = Xt9KdbKey::Type::nonRegional;
+ break;
+ }
+ break;
+
+ case KeyType::SpaceKey:
+ xt9Key.type = Xt9KdbKey::Type::function;
+ xt9Key.name = Xt9KdbKey::Name::ET9KEY_SPACE;
+ break;
+
+ default:
+ return false;
+ }
+
+ xt9Key.relative.setLeft(vkbKey[LEFT].toDouble() / layoutWidth);
+ xt9Key.relative.setTop(vkbKey[TOP].toDouble() / layoutHeight);
+ xt9Key.relative.setWidth(vkbKey[WIDTH].toDouble() / layoutWidth);
+ xt9Key.relative.setHeight(vkbKey[HEIGHT].toDouble() / layoutHeight);
+
+ switch (xt9Key.type) {
+ case Xt9KdbKey::Type::regional:
+ case Xt9KdbKey::Type::nonRegional:
+ case Xt9KdbKey::Type::string:
+ xt9Key.label = vkbKey[TEXT].toString().toUpper();
+ if (xt9Key.label.isEmpty())
+ return false;
+ xt9Key.codes = vkbKey[ALT_KEYS].toString();
+ if (codeConverter) {
+ xt9Key.label = codeConverter->convertTo(xt9Key.label);
+ xt9Key.codes = codeConverter->convertTo(xt9Key.codes);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+Xt9KeyboardGenerator::CodeConverter::~CodeConverter()
+{
+
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9keyboardgenerator.h b/src/plugins/cerence/xt9/xt9common/xt9keyboardgenerator.h
new file mode 100644
index 00000000..537e9745
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9keyboardgenerator.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KEYBOARDGENERATOR_H
+#define XT9KEYBOARDGENERATOR_H
+
+#include <QVariantMap>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9KdbArea;
+class Xt9KdbKey;
+
+class Xt9KeyboardGenerator
+{
+public:
+ class CodeConverter;
+
+ Xt9KeyboardGenerator(const QVariantMap &vkbLayout, const CodeConverter *codeConverter = nullptr);
+
+ QByteArray createXmlLayout() const;
+
+ static const QString PRIMARY_ID;
+ static const QString SECONDARY_ID;
+ static const QString WIDTH;
+ static const QString HEIGHT;
+ static const QString KEY;
+ static const QString KEYS;
+ static const QString KEY_TYPE;
+ static const QString TOP;
+ static const QString LEFT;
+ static const QString TEXT;
+ static const QString ALT_KEYS;
+ static const int EMOTICON_KEY;
+
+ class CodeConverter {
+ public:
+ virtual ~CodeConverter();
+ virtual QString convertTo(const QString &codes) const = 0;
+ virtual QString convertFrom(const QString &codes) const = 0;
+ };
+
+private:
+ bool convertFromVkb(Xt9KdbArea &xt9Area) const;
+ bool convertFromVkb(Xt9KdbKey &xt9Key, const QVariantMap &vkbKey) const;
+
+public:
+ const QVariantMap vkbLayout;
+ const double layoutWidth;
+ const double layoutHeight;
+ const CodeConverter *codeConverter;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KEYBOARDGENERATOR_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kime.cpp b/src/plugins/cerence/xt9/xt9common/xt9kime.cpp
new file mode 100644
index 00000000..76173728
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kime.cpp
@@ -0,0 +1,48 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9kime.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class JamoConverter : public Xt9KeyboardGenerator::CodeConverter {
+public:
+ QString convertTo(const QString &codes) const override
+ {
+ QVector<ushort> jamoBuf(codes.size());
+ memcpy(jamoBuf.data(), codes.utf16(), static_cast<size_t>(jamoBuf.size()) * sizeof(ushort));
+ XT9_VAPI(ET9KCompatibilityJamoToJamo, jamoBuf.data(), static_cast<ET9U32>(jamoBuf.size()));
+ return QString::fromUtf16(reinterpret_cast<const char16_t *>(jamoBuf.constData()), jamoBuf.size());
+ }
+
+ QString convertFrom(const QString &codes) const override
+ {
+ QVector<ushort> jamoBuf(codes.size());
+ memcpy(jamoBuf.data(), codes.utf16(), static_cast<size_t>(jamoBuf.size()) * sizeof(ushort));
+ XT9_VAPI(ET9KJamoToCompatibilityJamo, jamoBuf.data(), static_cast<ET9U32>(jamoBuf.size()));
+ return QString::fromUtf16(reinterpret_cast<const char16_t *>(jamoBuf.constData()), jamoBuf.size());
+ }
+};
+
+Q_GLOBAL_STATIC(JamoConverter, jamoConverter)
+
+Xt9KIme::Xt9KIme(Xt9RequestCallback *requestCallback) :
+ Xt9AwIme(requestCallback, jamoConverter)
+{
+}
+
+void Xt9KIme::sysInit()
+{
+ Xt9AwIme::sysInit();
+ memset(&sKLingCmn, 0, sizeof(sKLingCmn));
+ XT9_API(ET9KSysActivate, &sLingInfo, &sKLingCmn, 1);
+}
+
+QString Xt9KIme::exactWord(int *wordCompLen)
+{
+ return jamoConverter->convertFrom(Xt9AwIme::exactWord(wordCompLen));
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9kime.h b/src/plugins/cerence/xt9/xt9common/xt9kime.h
new file mode 100644
index 00000000..50578e81
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9kime.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9KIME_H
+#define XT9KIME_H
+
+#include "xt9awime.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9KIme : public Xt9AwIme
+{
+public:
+ Xt9KIme(Xt9RequestCallback *requestCallback);
+
+ void sysInit() override;
+ QString exactWord(int *wordCompLen = nullptr) override;
+
+public:
+ ET9KLingCmnInfo sKLingCmn;
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9KIME_H
diff --git a/src/plugins/cerence/xt9/xt9common/xt9languagemap.cpp b/src/plugins/cerence/xt9/xt9common/xt9languagemap.cpp
new file mode 100644
index 00000000..e53b562d
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9languagemap.cpp
@@ -0,0 +1,224 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "xt9languagemap.h"
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+const Xt9LanguageMap::SimpleLanguageMapEntry Xt9LanguageMap::SIMPLE_LANGUAGE_MAP[] = {
+ { ET9PLIDAbkhazian, QLocale::Language::Abkhazian },
+ { ET9PLIDAfar, QLocale::Language::Afar },
+ { ET9PLIDAfrikaans, QLocale::Language::Afrikaans },
+ { ET9PLIDAlbanian, QLocale::Language::Albanian },
+ { ET9PLIDAmharic, QLocale::Language::Amharic },
+ { ET9PLIDArabic, QLocale::Language::Arabic },
+ { ET9PLIDArmenian, QLocale::Language::Armenian },
+ { ET9PLIDAssamese, QLocale::Language::Assamese },
+ { ET9PLIDAymara, QLocale::Language::Aymara },
+ { ET9PLIDAzerbaijani, QLocale::Language::Azerbaijani },
+ { ET9PLIDBashkir, QLocale::Language::Bashkir },
+ { ET9PLIDBasque, QLocale::Language::Basque },
+ { ET9PLIDBelarusian, QLocale::Language::Belarusian },
+ { ET9PLIDBengali, QLocale::Language::Bengali },
+ { ET9PLIDBislama, QLocale::Language::Bislama },
+ { ET9PLIDBosnian, QLocale::Language::Bosnian },
+ { ET9PLIDBreton, QLocale::Language::Breton },
+ { ET9PLIDBulgarian, QLocale::Language::Bulgarian },
+ { ET9PLIDBurmese, QLocale::Language::Burmese },
+ { ET9PLIDCatalan, QLocale::Language::Catalan },
+ { ET9PLIDChineseSimplified, QLocale::Language::Chinese },
+ { ET9PLIDCorsican, QLocale::Language::Corsican },
+ { ET9PLIDCroatian, QLocale::Language::Croatian },
+ { ET9PLIDCzech, QLocale::Language::Czech },
+ { ET9PLIDDanish, QLocale::Language::Danish },
+ { ET9PLIDDutch, QLocale::Language::Dutch },
+ { ET9PLIDEnglish, QLocale::Language::English },
+ { ET9PLIDEsperanto, QLocale::Language::Esperanto },
+ { ET9PLIDEstonian, QLocale::Language::Estonian },
+ { ET9PLIDFilipino, QLocale::Language::Filipino },
+ { ET9PLIDFinnish, QLocale::Language::Finnish },
+ { ET9PLIDFrench, QLocale::Language::French },
+ { ET9PLIDGalician, QLocale::Language::Galician },
+ { ET9PLIDGeorgian, QLocale::Language::Georgian },
+ { ET9PLIDGerman, QLocale::Language::German },
+ { ET9PLIDGerman, QLocale::Language::LowGerman },
+ { ET9PLIDGerman, QLocale::Language::SwissGerman },
+ { ET9PLIDGreek, QLocale::Language::Greek },
+ { ET9PLIDGreenlandic, QLocale::Language::Greenlandic },
+ { ET9PLIDGuarani, QLocale::Language::Guarani },
+ { ET9PLIDGujarati, QLocale::Language::Gujarati },
+ { ET9PLIDHausa, QLocale::Language::Hausa },
+ { ET9PLIDHawaiian, QLocale::Language::Hawaiian },
+ { ET9PLIDHebrew, QLocale::Language::Hebrew },
+ { ET9PLIDHindi, QLocale::Language::Hindi },
+ { ET9PLIDHungarian, QLocale::Language::Hungarian },
+ { ET9PLIDIcelandic, QLocale::Language::Icelandic },
+ { ET9PLIDIgbo, QLocale::Language::Igbo },
+ { ET9PLIDIndonesian, QLocale::Language::Indonesian },
+ { ET9PLIDInterlingua, QLocale::Language::Interlingua },
+ { ET9PLIDInterlingue, QLocale::Language::Interlingue },
+ { ET9PLIDInuktitut, QLocale::Language::Inuktitut },
+ { ET9PLIDInupiak, QLocale::Language::Inupiak },
+ { ET9PLIDIrish, QLocale::Language::Irish },
+ { ET9PLIDItalian, QLocale::Language::Italian },
+ { ET9PLIDJapanese, QLocale::Language::Japanese },
+ { ET9PLIDJavanese, QLocale::Language::Javanese },
+ { ET9PLIDKannada, QLocale::Language::Kannada },
+ { ET9PLIDKashmiri, QLocale::Language::Kashmiri },
+ { ET9PLIDKazakh, QLocale::Language::Kazakh },
+ { ET9PLIDKhmer, QLocale::Language::Khmer },
+ { ET9PLIDKirghiz, QLocale::Language::Kirghiz },
+ { ET9PLIDKonkani, QLocale::Language::Konkani },
+ { ET9PLIDKorean, QLocale::Language::Korean },
+ { ET9PLIDKurdish, QLocale::Language::Kurdish },
+ { ET9PLIDLao, QLocale::Language::Lao },
+ { ET9PLIDLatin, QLocale::Language::Latin },
+ { ET9PLIDLatvian, QLocale::Language::Latvian },
+ { ET9PLIDLingala, QLocale::Language::Lingala },
+ { ET9PLIDLithuanian, QLocale::Language::Lithuanian },
+ { ET9PLIDMacedonian, QLocale::Language::Macedonian },
+ { ET9PLIDMalagasy, QLocale::Language::Malagasy },
+ { ET9PLIDMalay, QLocale::Language::Malay },
+ { ET9PLIDMalayalam, QLocale::Language::Malayalam },
+ { ET9PLIDMaltese, QLocale::Language::Maltese },
+ { ET9PLIDMaori, QLocale::Language::Maori },
+ { ET9PLIDMarathi, QLocale::Language::Marathi },
+ { ET9PLIDMongolian, QLocale::Language::Mongolian },
+ { ET9PLIDNepali, QLocale::Language::Nepali },
+ { ET9PLIDNorwegian, QLocale::Language::NorwegianBokmal },
+ { ET9PLIDNorwegian, QLocale::Language::NorwegianNynorsk },
+ { ET9PLIDOccitan, QLocale::Language::Occitan },
+ { ET9PLIDOriya, QLocale::Language::Oriya },
+ { ET9PLIDOromo, QLocale::Language::Oromo },
+ { ET9PLIDPashto, QLocale::Language::Pashto },
+ { ET9PLIDPersian, QLocale::Language::Persian },
+ { ET9PLIDPolish, QLocale::Language::Polish },
+ { ET9PLIDPortuguese, QLocale::Language::Portuguese },
+ { ET9PLIDPunjabi, QLocale::Language::Punjabi },
+ { ET9PLIDQuechua, QLocale::Language::Quechua },
+ { ET9PLIDRomanian, QLocale::Language::Romanian },
+ { ET9PLIDRussian, QLocale::Language::Russian },
+ { ET9PLIDSami, QLocale::Language::NorthernSami },
+ { ET9PLIDSamoan, QLocale::Language::Samoan },
+ { ET9PLIDSangho, QLocale::Language::Sango },
+ { ET9PLIDSanskrit, QLocale::Language::Sanskrit },
+ { ET9PLIDSerbian, QLocale::Language::Serbian },
+ { ET9PLIDSesotho, QLocale::Language::NorthernSotho },
+ { ET9PLIDSesotho, QLocale::Language::SouthernSotho },
+ { ET9PLIDShona, QLocale::Language::Shona },
+ { ET9PLIDSindhi, QLocale::Language::Sindhi },
+ { ET9PLIDSinhala, QLocale::Language::Sinhala },
+ { ET9PLIDSiswati, QLocale::Language::Swati },
+ { ET9PLIDSlovak, QLocale::Language::Slovak },
+ { ET9PLIDSlovenian, QLocale::Language::Slovenian },
+ { ET9PLIDSomali, QLocale::Language::Somali },
+ { ET9PLIDSpanish, QLocale::Language::Spanish },
+ { ET9PLIDSundanese, QLocale::Language::Sundanese },
+ { ET9PLIDSwahili, QLocale::Language::Swahili },
+ { ET9PLIDSwedish, QLocale::Language::Swedish },
+ { ET9PLIDTajik, QLocale::Language::Tajik },
+ { ET9PLIDTamil, QLocale::Language::Tamil },
+ { ET9PLIDTatar, QLocale::Language::Tatar },
+ { ET9PLIDTelugu, QLocale::Language::Telugu },
+ { ET9PLIDThai, QLocale::Language::Thai },
+ { ET9PLIDTibetan, QLocale::Language::Tibetan },
+ { ET9PLIDTigrinya, QLocale::Language::Tigrinya },
+ { ET9PLIDTonga, QLocale::Language::Tongan },
+ { ET9PLIDTsonga, QLocale::Language::Tsonga },
+ { ET9PLIDTswana, QLocale::Language::Tswana },
+ { ET9PLIDTurkish, QLocale::Language::Turkish },
+ { ET9PLIDTurkmen, QLocale::Language::Turkmen },
+ { ET9PLIDUkrainian, QLocale::Language::Ukrainian },
+ { ET9PLIDUrdu, QLocale::Language::Urdu },
+ { ET9PLIDUzbek, QLocale::Language::Uzbek },
+ { ET9PLIDVenda, QLocale::Language::Venda },
+ { ET9PLIDVietnamese, QLocale::Language::Vietnamese },
+ { ET9PLIDVolapuk, QLocale::Language::Volapuk },
+ { ET9PLIDWelsh, QLocale::Language::Welsh },
+ { ET9PLIDWolof, QLocale::Language::Wolof },
+ { ET9PLIDXhosa, QLocale::Language::Xhosa },
+ { ET9PLIDYiddish, QLocale::Language::Yiddish },
+ { ET9PLIDYoruba, QLocale::Language::Yoruba },
+ { ET9PLIDZhuang, QLocale::Language::Zhuang },
+ { ET9PLIDZulu, QLocale::Language::Zulu },
+ // End-of-map
+ { ET9PLIDNone, QLocale::Language::AnyLanguage }
+};
+
+const struct Xt9LanguageMap::LanguageMapEntry Xt9LanguageMap::LANGUAGE_MAP[] = {
+ { ET9LIDEnglish_UK, QLocale(QLocale::English, QLocale::UnitedKingdom) },
+ { ET9LIDEnglish_Australia, QLocale(QLocale::English, QLocale::Australia) },
+ { ET9LIDEnglish_India, QLocale(QLocale::English, QLocale::India) },
+ { ET9LIDEnglish_US, QLocale(QLocale::English, QLocale::UnitedStates) },
+ { ET9PLIDEnglish|ET9SLIDDEFAULT, QLocale(QLocale::English, QLocale::UnitedKingdom) },
+ { ET9LIDSpanish_LatinAmerican, QLocale(QLocale::Spanish, QLocale::LatinAmerica) },
+ { ET9PLIDSpanish|ET9SLIDDEFAULT, QLocale(QLocale::Spanish, QLocale::Spain) },
+ { ET9LIDFrench_Canada, QLocale(QLocale::French, QLocale::Canada) },
+ { ET9LIDFrench_Switzerland, QLocale(QLocale::French, QLocale::Switzerland) },
+ { ET9PLIDFrench|ET9SLIDDEFAULT, QLocale(QLocale::French, QLocale::France) },
+ { ET9LIDItalian_Switzerland, QLocale(QLocale::Italian, QLocale::Switzerland) },
+ { ET9LIDDutch_Belgium, QLocale(QLocale::Dutch, QLocale::Belgium) },
+ { ET9PLIDPortuguese|ET9SLIDDEFAULT, QLocale(QLocale::Portuguese, QLocale::Portugal) },
+ { ET9LIDPortuguese_Brazil, QLocale(QLocale::Portuguese, QLocale::Brazil) },
+ { ET9PLIDChineseSimplified, QLocale(QLocale::Chinese, QLocale::SimplifiedHanScript, QLocale::China) },
+ { ET9PLIDChineseTraditional, QLocale(QLocale::Chinese, QLocale::TraditionalHanScript, QLocale::Taiwan) },
+ { ET9PLIDChineseHongkong, QLocale(QLocale::Chinese, QLocale::TraditionalHanScript, QLocale::HongKong) },
+ { ET9PLIDChineseSingapore, QLocale(QLocale::Chinese, QLocale::TraditionalHanScript, QLocale::Singapore) },
+ { ET9LIDJapanese_Hiragana, QLocale(QLocale::Japanese) },
+ // End-of-map
+ { 0, QLocale(QLocale::AnyLanguage) },
+};
+
+ET9U32 Xt9LanguageMap::languageId(const QLocale &locale)
+{
+ const QLocale::Language localeLanguage = locale.language();
+ const QLocale::Territory localeTerritory = locale.territory();
+
+ for (int i = 0; LANGUAGE_MAP[i].languageId != 0; ++i) {
+ const QLocale &item = LANGUAGE_MAP[i].locale;
+ if (item.language() == localeLanguage && item.territory() == localeTerritory)
+ return LANGUAGE_MAP[i].languageId;
+ }
+
+ const SimpleLanguageMapEntry *simpleLanguageMapIterator = SIMPLE_LANGUAGE_MAP;
+ for (; simpleLanguageMapIterator->localeLanguage != QLocale::Language::AnyLanguage;
+ simpleLanguageMapIterator++) {
+ if (simpleLanguageMapIterator->localeLanguage == localeLanguage)
+ return simpleLanguageMapIterator->languageId;
+ }
+
+ return ET9PLIDNone;
+}
+
+QLocale Xt9LanguageMap::locale(ET9U32 languageId)
+{
+ for (int i = 0; LANGUAGE_MAP[i].languageId != 0; ++i) {
+ if (LANGUAGE_MAP[i].languageId == languageId)
+ return LANGUAGE_MAP[i].locale;
+ }
+
+ if (!(languageId & ET9SLIDMASK) &&
+ (languageId < ET9PLIDChineseTraditional || languageId > ET9PLIDChineseSingapore)) {
+
+ languageId |= ET9SLIDDEFAULT;
+
+ for (int i = 0; LANGUAGE_MAP[i].languageId != 0; ++i) {
+ if (LANGUAGE_MAP[i].languageId == languageId)
+ return LANGUAGE_MAP[i].locale;
+ }
+ }
+
+ const ET9U32 plid = languageId & ET9PLIDMASK;
+ const SimpleLanguageMapEntry *simpleLanguageMapIterator = SIMPLE_LANGUAGE_MAP;
+ for (; simpleLanguageMapIterator->localeLanguage != QLocale::Language::AnyLanguage;
+ simpleLanguageMapIterator++) {
+ if (simpleLanguageMapIterator->languageId == plid)
+ return QLocale(simpleLanguageMapIterator->localeLanguage);
+ }
+
+ return QLocale(QLocale::AnyLanguage);
+}
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
diff --git a/src/plugins/cerence/xt9/xt9common/xt9languagemap.h b/src/plugins/cerence/xt9/xt9common/xt9languagemap.h
new file mode 100644
index 00000000..ed14a0fe
--- /dev/null
+++ b/src/plugins/cerence/xt9/xt9common/xt9languagemap.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef XT9LANGUAGEMAP_H
+#define XT9LANGUAGEMAP_H
+
+#include <QLocale>
+#include <et9api.h>
+
+QT_BEGIN_NAMESPACE
+namespace QtVirtualKeyboard {
+
+class Xt9LanguageMap
+{
+private:
+ Xt9LanguageMap();
+
+public:
+ static ET9U32 languageId(const QLocale &locale);
+ static QLocale locale(ET9U32 languageId);
+
+private:
+ struct SimpleLanguageMapEntry {
+ ET9U32 languageId;
+ QLocale::Language localeLanguage;
+ };
+
+ struct LanguageMapEntry {
+ ET9U32 languageId;
+ QLocale locale;
+ };
+
+ static const SimpleLanguageMapEntry SIMPLE_LANGUAGE_MAP[];
+ static const struct LanguageMapEntry LANGUAGE_MAP[];
+};
+
+} // namespace QtVirtualKeyboard
+QT_END_NAMESPACE
+
+#endif // XT9LANGUAGEMAP_H