diff options
Diffstat (limited to 'src/plugins/cerence')
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 |