diff options
author | Jarkko Koivikko <jarkko.koivikko@code-q.fi> | 2023-04-24 12:39:40 +0300 |
---|---|---|
committer | Jarkko Koivikko <jarkko.koivikko@code-q.fi> | 2023-05-09 14:30:48 +0300 |
commit | 310650815c98ab5d87a36e7fb6805cdfc45df2dc (patch) | |
tree | b5be9d1d2b3e18b199739397602be00f06f2694a /src | |
parent | b59f7eeb0cfc4ed89522d24476d942e96c00d606 (diff) |
Add example handwriting plugin
This can be used for demonstration and development purposes.
The plugin is enabled by defining
-DINPUT_vkb_handwriting:STRING=example-hwr
In the cmake command line.
Task-number: QTBUG-113024
Change-Id: I05fd8dab88ea4f0d040c39836f55dcd52bc3e332
Reviewed-by: Jarkko Koivikko <jarkko.koivikko@code-q.fi>
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/plugins/example/CMakeLists.txt | 13 | ||||
-rw-r--r-- | src/plugins/example/hwr/CMakeLists.txt | 282 | ||||
-rw-r--r-- | src/plugins/example/hwr/examplehwrinputmethod.cpp | 632 | ||||
-rw-r--r-- | src/plugins/example/hwr/examplehwrinputmethod_p.h | 64 | ||||
-rw-r--r-- | src/virtualkeyboard/configure.cmake | 8 | ||||
-rw-r--r-- | src/virtualkeyboard/doc/src/build.qdoc | 6 | ||||
-rw-r--r-- | src/virtualkeyboard/doc/src/handwriting.qdoc | 6 | ||||
-rw-r--r-- | src/virtualkeyboard/qt_cmdline.cmake | 2 | ||||
-rw-r--r-- | src/virtualkeyboard/qvirtualkeyboardfeatures_namespace_p.h | 2 |
10 files changed, 1015 insertions, 4 deletions
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 5b8e8b54..adacd194 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -6,6 +6,10 @@ if(QT_FEATURE_cerence_sdk) add_subdirectory(cerence) list(APPEND plugins_imports QtQuick.VirtualKeyboard.Plugins.Cerence/auto) endif() +if(QT_FEATURE_example_hwr) + add_subdirectory(example) + list(APPEND plugins_imports QtQuick.VirtualKeyboard.Plugins.Example/auto) +endif() if(QT_FEATURE_hangul) add_subdirectory(hangul) list(APPEND plugins_imports QtQuick.VirtualKeyboard.Plugins.Hangul/auto) diff --git a/src/plugins/example/CMakeLists.txt b/src/plugins/example/CMakeLists.txt new file mode 100644 index 00000000..acb5bc7a --- /dev/null +++ b/src/plugins/example/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(hwr) +list(APPEND example_imports QtQuick.VirtualKeyboard.Plugins.Example.HWR/auto) + +qt_internal_add_qml_module(qtvkbexampleplugin + URI "QtQuick.VirtualKeyboard.Plugins.Example" + VERSION "${PROJECT_VERSION}" + PLUGIN_TARGET qtvkbexampleplugin + IMPORTS + ${example_imports} +) diff --git a/src/plugins/example/hwr/CMakeLists.txt b/src/plugins/example/hwr/CMakeLists.txt new file mode 100644 index 00000000..9c624531 --- /dev/null +++ b/src/plugins/example/hwr/CMakeLists.txt @@ -0,0 +1,282 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_qml_module(qtvkbexamplehwrplugin + URI "QtQuick.VirtualKeyboard.Plugins.Example.HWR" + VERSION "${PROJECT_VERSION}" + PLUGIN_TARGET qtvkbexamplehwrplugin + NO_PLUGIN_OPTIONAL + DEPENDENCIES + QtQuick.VirtualKeyboard/auto + SOURCES + examplehwrinputmethod.cpp examplehwrinputmethod_p.h + DEFINES + QT_ASCII_CAST_WARNINGS + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_FROM_BYTEARRAY + QT_NO_CAST_TO_ASCII + LIBRARIES + Qt::Core + Qt::Gui + Qt::Qml + Qt::VirtualKeyboardPrivate +) + +set(qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/fallback/handwriting.qml" +) + +if (QT_FEATURE_vkb_lang_en_GB) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/en_GB/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_en_US) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/en_US/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_ar_AR) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/ar_AR/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_bg_BG) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/bg_BG/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_cs_CZ) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/cs_CZ/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_da_DK) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/da_DK/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_de_DE) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/de_DE/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_el_GR) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/el_GR/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_es_ES) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/es_ES/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_es_MX) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/es_MX/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_et_EE) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/et_EE/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_fa_FA) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/fa_FA/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_fi_FI) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/fi_FI/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_fr_FR) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/fr_FR/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_fr_CA) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/fr_CA/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_he_IL) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/he_IL/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_hr_HR) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/hr_HR/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_hu_HU) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/hu_HU/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_id_ID) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/id_ID/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_it_IT) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/it_IT/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_ms_MY) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/ms_MY/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_nb_NO) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/nb_NO/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_nl_NL) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/nl_NL/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_pl_PL) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/pl_PL/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_pt_BR) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/pt_BR/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_pt_PT) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/pt_PT/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_ro_RO) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/ro_RO/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_ru_RU) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/ru_RU/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_sk_SK) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/sk_SK/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_sl_SI) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/sl_SI/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_sq_AL) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/sq_AL/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_sr_SP) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/sr_SP/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_sv_SE) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/sv_SE/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_th_TH) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/th_TH/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_tr_TR) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/tr_TR/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_uk_UA) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/uk_UA/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_vi_VN) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/vi_VN/handwriting.fallback" + ) +endif() + +if (QT_FEATURE_vkb_lang_ja_JP) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/ja_JP/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_ko_KR) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/ko_KR/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_zh_CN) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/zh_CN/handwriting.qml" + ) +endif() + +if (QT_FEATURE_vkb_lang_zh_TW) + list(APPEND qt_virtualkeyboard_example_hwr_layouts_resource_files + "${VKB_LAYOUTS_BASE}/zh_TW/handwriting.qml" + ) +endif() + +qt_internal_add_resource(qtvkbexamplehwrplugin "qt_virtualkeyboard_example_hwr_layouts" + PREFIX + "${VKB_LAYOUTS_PREFIX}" + BASE + "${VKB_LAYOUTS_BASE}" + FILES + ${qt_virtualkeyboard_example_hwr_layouts_resource_files} +) diff --git a/src/plugins/example/hwr/examplehwrinputmethod.cpp b/src/plugins/example/hwr/examplehwrinputmethod.cpp new file mode 100644 index 00000000..58d1d2c8 --- /dev/null +++ b/src/plugins/example/hwr/examplehwrinputmethod.cpp @@ -0,0 +1,632 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "examplehwrinputmethod_p.h" +#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h> +#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h> +#include <QtVirtualKeyboard/qvirtualkeyboardtrace.h> +#include <QtVirtualKeyboard/private/handwritinggesturerecognizer_p.h> +#include <QtVirtualKeyboard/private/settings_p.h> +#include <QtVirtualKeyboard/private/qvirtualkeyboardabstractinputmethod_p.h> +#include <QCryptographicHash> +#include <QRandomGenerator> +#include <QLoggingCategory> +#include <QLocale> + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +Q_LOGGING_CATEGORY(lcExampleHwr, "qt.virtualkeyboard.example.hwr") + +class ExampleHwrInputMethodPrivate : public QVirtualKeyboardAbstractInputMethodPrivate +{ +public: + Q_DECLARE_PUBLIC(ExampleHwrInputMethod) + + ExampleHwrInputMethodPrivate(ExampleHwrInputMethod *q_ptr) : + QVirtualKeyboardAbstractInputMethodPrivate(), + q_ptr(q_ptr) + { + } + + bool setInputMode(const QLocale &locale, QVirtualKeyboardInputEngine::InputMode inputMode) + { + Q_UNUSED(locale); + finishRecognition(); + this->inputMode = inputMode; + return true; + } + + 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(lcExampleHwr) << "setContext:" << QLatin1String((context.toHex())); + + // Finish recognition, but preserve current input + const int dpi = traceCaptureDeviceInfo.value(QLatin1String("dpi"), 96).toInt(); + static const int INSTANT_GESTURE_WIDTH_THRESHOLD_MM = 25; + gestureWidthThreshold = qRound(INSTANT_GESTURE_WIDTH_THRESHOLD_MM / 25.4 * dpi); + + gestureRecognizer.setDpi(dpi); + } + + QVirtualKeyboardTrace *traceBegin( + int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) + { + // The result id follows the trace id so that the (previous) + // results completed during the handwriting can be rejected. + resultId = traceId; + + QByteArray context = getContext(patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo); + if (context != currentContext) { + setContext(patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo, context); + } + + Q_Q(ExampleHwrInputMethod); + QVirtualKeyboardTrace *trace = new QVirtualKeyboardTrace(q); + traceList.append(trace); + + return trace; + } + + void traceEnd(QVirtualKeyboardTrace *trace) + { + if (trace->isCanceled()) { + traceList.removeOne(trace); + delete trace; + } else if (handleGesture()) { + finishRecognition(); + return; + } + if (!traceList.isEmpty()) { + 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() + { + qDeleteAll(traceList); + traceList.clear(); + } + + bool applyGesture(const QChar &gesture) + { + Q_Q(ExampleHwrInputMethod); + 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(lcExampleHwr) << "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(ExampleHwrInputMethod); + 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 + 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<int>(QVirtualKeyboardInputEngine::InputMode::Numeric)); + int indexOfDialableInputMode = inputModes.indexOf(static_cast<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<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 ExampleHwrInputMethod); + 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 restartRecognition() + { + qCDebug(lcExampleHwr) << "restartRecognition"; + + resetResultTimer(Settings::instance()->hwrTimeoutForAlphabetic()); + } + + bool finishRecognition(bool emitSelectionListChanged = true) + { + qCDebug(lcExampleHwr) << "finishRecognition"; + bool result = !traceList.isEmpty(); + + stopResultTimer(); + clearTraces(); + + if (!wordCandidates.isEmpty()) { + wordCandidates.clear(); + activeWordIndex = -1; + if (emitSelectionListChanged) { + Q_Q(ExampleHwrInputMethod); + emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); + emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex); + } + result = true; + } + + return result; + } + + void select(int index = -1) + { + Q_Q(ExampleHwrInputMethod); + QVirtualKeyboardInputContext *ic = q->inputContext(); + if (!ic) + return; + + if (!wordCandidates.isEmpty()) + ic->commit(wordCandidates.at(index != -1 ? index : 0)); + + finishRecognition(); + } + + void processResult() + { + qCDebug(lcExampleHwr) << "processResult"; + + Q_Q(ExampleHwrInputMethod); + QVirtualKeyboardInputContext *ic = q->inputContext(); + if (!ic) + return; + + QStringList newWordCandidates; + QString word = !wordCandidates.isEmpty() ? wordCandidates.at(0) : QString(); + switch (inputMode) { + case QVirtualKeyboardInputEngine::InputMode::Latin: + appendRandomChar(word); + break; + case QVirtualKeyboardInputEngine::InputMode::Numeric: + case QVirtualKeyboardInputEngine::InputMode::Dialable: + appendRandomDigit(word); + break; + default: + break; + } + newWordCandidates.append(word); + activeWordIndex = 0; + wordCandidates = newWordCandidates; + qCDebug(lcExampleHwr) << "wordCandidates" << wordCandidates; + ic->setPreeditText(word); + + emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); + emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex); + } + + static void appendRandomChar(QString& word) + { + word.append(QChar('a' + QRandomGenerator::global()->bounded(26))); + } + + static void appendRandomDigit(QString& word) + { + word.append(QChar('0' + QRandomGenerator::global()->bounded(10))); + } + + void resetResultTimer(int interval = 500) + { + qCDebug(lcExampleHwr) << "resetResultTimer:" << interval; + Q_Q(ExampleHwrInputMethod); + stopResultTimer(); + resultTimer = q->startTimer(interval); + } + + void stopResultTimer() + { + if (resultTimer) { + qCDebug(lcExampleHwr) << "stopResultTimer"; + Q_Q(ExampleHwrInputMethod); + q->killTimer(resultTimer); + resultTimer = 0; + } + } + + ExampleHwrInputMethod *q_ptr = nullptr; + QVirtualKeyboardInputEngine::InputMode inputMode = QVirtualKeyboardInputEngine::InputMode::Latin; + QByteArray currentContext; + int gestureWidthThreshold = 0; + int resultId = 0; + int lastResultId = 0; + int resultTimer = 0; + QList<QVirtualKeyboardTrace *> traceList; + HandwritingGestureRecognizer gestureRecognizer; + QStringList wordCandidates; + int activeWordIndex = -1; +}; + +/*! + \class QtVirtualKeyboard::ExampleHwrInputMethod + \internal +*/ + +ExampleHwrInputMethod::ExampleHwrInputMethod(QObject *parent) : + QVirtualKeyboardAbstractInputMethod(*new ExampleHwrInputMethodPrivate(this), parent) +{ +} + +ExampleHwrInputMethod::~ExampleHwrInputMethod() +{ +} + +QList<QVirtualKeyboardInputEngine::InputMode> ExampleHwrInputMethod::inputModes(const QString &locale) +{ + Q_UNUSED(locale); + QList<QVirtualKeyboardInputEngine::InputMode> availableInputModes = { + QVirtualKeyboardInputEngine::InputMode::Latin, + QVirtualKeyboardInputEngine::InputMode::Numeric, + }; + return availableInputModes; +} + +bool ExampleHwrInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) +{ + Q_D(ExampleHwrInputMethod); + d->select(); + return d->setInputMode(QLocale(locale), inputMode); +} + +bool ExampleHwrInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase) +{ + Q_UNUSED(textCase); + return true; +} + +bool ExampleHwrInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) +{ + Q_UNUSED(modifiers); + Q_D(ExampleHwrInputMethod); + 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); + emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); + emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->activeWordIndex); + return true; + } else { + bool result = !preeditText.isEmpty(); + if (result) + ic->clear(); + d->finishRecognition(); + return result; + } + } + + default: + if (text.length() > 0) { + 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->finishRecognition(false); + 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; + } + } + return false; +} + +void ExampleHwrInputMethod::reset() +{ + Q_D(ExampleHwrInputMethod); + qCDebug(lcExampleHwr) << "reset"; + d->finishRecognition(); +} + +void ExampleHwrInputMethod::update() +{ + Q_D(ExampleHwrInputMethod); + qCDebug(lcExampleHwr) << "update"; + d->select(); +} + +QList<QVirtualKeyboardSelectionListModel::Type> ExampleHwrInputMethod::selectionLists() +{ + return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList; +} + +int ExampleHwrInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type) +{ + Q_UNUSED(type); + Q_D(ExampleHwrInputMethod); + return d->wordCandidates.size(); +} + +QVariant ExampleHwrInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role) +{ + QVariant result; + Q_D(ExampleHwrInputMethod); + switch (role) { + case QVirtualKeyboardSelectionListModel::Role::Display: + result = QVariant(d->wordCandidates.at(index)); + break; + case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength: + result.setValue(0); + break; + case QVirtualKeyboardSelectionListModel::Role::Dictionary: + { + QVirtualKeyboardSelectionListModel::DictionaryType dictionaryType = + QVirtualKeyboardSelectionListModel::DictionaryType::Default; + result = QVariant(static_cast<int>(dictionaryType)); + break; + } + case QVirtualKeyboardSelectionListModel::Role::CanRemoveSuggestion: + result = QVariant(false); + break; + default: + result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role); + break; + } + return result; +} + +void ExampleHwrInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index) +{ + Q_UNUSED(type); + Q_D(ExampleHwrInputMethod); + d->select(index); +} + +QList<QVirtualKeyboardInputEngine::PatternRecognitionMode> ExampleHwrInputMethod::patternRecognitionModes() const +{ + return QList<QVirtualKeyboardInputEngine::PatternRecognitionMode>() + << QVirtualKeyboardInputEngine::PatternRecognitionMode::Handwriting; +} + +QVirtualKeyboardTrace *ExampleHwrInputMethod::traceBegin( + int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) +{ + Q_D(ExampleHwrInputMethod); + return d->traceBegin(traceId, patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo); +} + +bool ExampleHwrInputMethod::traceEnd(QVirtualKeyboardTrace *trace) +{ + Q_D(ExampleHwrInputMethod); + d->traceEnd(trace); + return true; +} + +bool ExampleHwrInputMethod::reselect(int cursorPosition, const QVirtualKeyboardInputEngine::ReselectFlags &reselectFlags) +{ + Q_D(ExampleHwrInputMethod); + + QVirtualKeyboardInputContext *ic = inputContext(); + if (!ic) + return false; + + const int maxLength = 32; + const QString surroundingText = ic->surroundingText(); + int replaceFrom = 0; + QString stringStart; + + if (cursorPosition > surroundingText.length()) + return false; + + if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordBeforeCursor)) { + for (int i = cursorPosition - 1; i >= 0 && stringStart.length() < maxLength; --i) { + QChar c = surroundingText.at(i); + if (!d->isValidInputChar(c)) + break; + stringStart.insert(0, c); + --replaceFrom; + } + + while (replaceFrom < 0 && d->isJoiner(stringStart.at(0))) { + stringStart.remove(0, 1); + ++replaceFrom; + } + } + + if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor) && replaceFrom == 0) { + stringStart.clear(); + return false; + } + + if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAfterCursor)) { + for (int i = cursorPosition; i < surroundingText.length() && stringStart.length() < maxLength; ++i) { + QChar c = surroundingText.at(i); + if (!d->isValidInputChar(c)) + break; + stringStart.append(c); + } + + while (replaceFrom > -stringStart.length()) { + int lastPos = stringStart.length() - 1; + if (!d->isJoiner(stringStart.at(lastPos))) + break; + stringStart.remove(lastPos, 1); + } + } + + if (stringStart.isEmpty()) + return false; + + if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor) && replaceFrom == -stringStart.length() && stringStart.length() < maxLength) { + stringStart.clear(); + return false; + } + + if (d->isJoiner(stringStart.at(0))) { + stringStart.clear(); + return false; + } + + if (d->isJoiner(stringStart.at(stringStart.length() - 1))) { + stringStart.clear(); + return false; + } + + ic->setPreeditText(stringStart, QList<QInputMethodEvent::Attribute>(), replaceFrom, stringStart.length()); + d->activeWordIndex = 0; + d->wordCandidates = {stringStart}; + emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); + emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->activeWordIndex); + + return true; +} + +void ExampleHwrInputMethod::timerEvent(QTimerEvent *timerEvent) +{ + Q_D(ExampleHwrInputMethod); + int timerId = timerEvent->timerId(); + qCDebug(lcExampleHwr) << "timerEvent():" << timerId; + if (timerId == d->resultTimer) { + if (!d->countActiveTraces()) { + d->stopResultTimer(); + d->processResult(); + d->clearTraces(); + } + } +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE diff --git a/src/plugins/example/hwr/examplehwrinputmethod_p.h b/src/plugins/example/hwr/examplehwrinputmethod_p.h new file mode 100644 index 00000000..d0307701 --- /dev/null +++ b/src/plugins/example/hwr/examplehwrinputmethod_p.h @@ -0,0 +1,64 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef EXAMPLEHWRINPUTMETHOD_P_H +#define EXAMPLEHWRINPUTMETHOD_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 ExampleHwrInputMethodPrivate; + +class ExampleHwrInputMethod : public QVirtualKeyboardAbstractInputMethod +{ + Q_OBJECT + Q_DECLARE_PRIVATE(ExampleHwrInputMethod) + QML_NAMED_ELEMENT(HandwritingInputMethod) + +public: + explicit ExampleHwrInputMethod(QObject *parent = nullptr); + ~ExampleHwrInputMethod(); + + QList<QVirtualKeyboardInputEngine::InputMode> inputModes(const QString &locale) override; + bool setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) override; + bool setTextCase(QVirtualKeyboardInputEngine::TextCase textCase) override; + + bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) override; + + void reset() override; + void update() override; + + QList<QVirtualKeyboardSelectionListModel::Type> selectionLists() override; + int selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type) override; + QVariant selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role) override; + void selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index) override; + + QList<QVirtualKeyboardInputEngine::PatternRecognitionMode> patternRecognitionModes() const override; + QVirtualKeyboardTrace *traceBegin( + int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) override; + bool traceEnd(QVirtualKeyboardTrace *trace) override; + + bool reselect(int cursorPosition, const QVirtualKeyboardInputEngine::ReselectFlags &reselectFlags) override; + +protected: + void timerEvent(QTimerEvent *timerEvent) override; +}; + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE + +#endif diff --git a/src/virtualkeyboard/configure.cmake b/src/virtualkeyboard/configure.cmake index 81befaa8..5cd0d540 100644 --- a/src/virtualkeyboard/configure.cmake +++ b/src/virtualkeyboard/configure.cmake @@ -180,6 +180,13 @@ qt_feature("thai" PRIVATE AUTODETECT ( NOT INPUT_lang_th_TH STREQUAL 'no' ) DISABLE QT_FEATURE_cerence_xt9 ) +qt_feature("example-hwr" PRIVATE + LABEL "Example HWR" + ENABLE INPUT_vkb_handwriting STREQUAL 'example-hwr' + AUTODETECT ( FALSE ) + DISABLE NOT INPUT_vkb_handwriting STREQUAL '' AND NOT INPUT_vkb_handwriting STREQUAL 'example-hwr' + PURPOSE "Generates random characters in response to handwriting input. For development and demonstration purposes only." +) qt_feature("vkb-lang-ar_AR" PRIVATE LABEL "Arabic" AUTODETECT ( NOT INPUT_lang_ar_AR STREQUAL 'no' ) @@ -376,6 +383,7 @@ qt_configure_add_summary_entry(ARGS "hunspell") qt_configure_add_summary_entry(ARGS "3rdparty-hunspell") qt_configure_add_summary_entry(ARGS "openwnn") qt_configure_add_summary_entry(ARGS "myscript") +qt_configure_add_summary_entry(ARGS "example-hwr") qt_configure_add_summary_section(NAME "Language support enabled for") qt_configure_add_summary_entry(ARGS "vkb-lang-ar_AR") qt_configure_add_summary_entry(ARGS "vkb-lang-bg_BG") diff --git a/src/virtualkeyboard/doc/src/build.qdoc b/src/virtualkeyboard/doc/src/build.qdoc index 51957cfd..999a4c35 100644 --- a/src/virtualkeyboard/doc/src/build.qdoc +++ b/src/virtualkeyboard/doc/src/build.qdoc @@ -73,12 +73,14 @@ keyboard features. These options are passed to the \e configure tool. if no other languages are specified. \row \li \e -vkb-handwriting - \li \e [no|myscript-hwr|cerence-hwr] + \li \e [no|example-hwr|myscript-hwr|cerence-hwr] \li Enables or disabled handwriting input \li This flag enables handwriting input. By default, the engine is automatically activated if it is located in the proper plugins folder even without using of this option. But, in case MyScript and Cerence SDK - co-exist, one of [no|myscript-hwr|cerence-hwr] must be configured. + co-exist, one of [no|myscript-hwr|cerence-hwr] must be configured. The + \l {Example Handwriting}{example-hwr} option needs to be explicitly + activated. This can be done for development and testing purposes. \row \li \e [-no]-vkb-arrow-keynavigation \li diff --git a/src/virtualkeyboard/doc/src/handwriting.qdoc b/src/virtualkeyboard/doc/src/handwriting.qdoc index 0f6192c9..d205ca06 100644 --- a/src/virtualkeyboard/doc/src/handwriting.qdoc +++ b/src/virtualkeyboard/doc/src/handwriting.qdoc @@ -21,6 +21,12 @@ For instructions on how to activate and use the handwriting input mode, see the For information about building Qt Virtual Keyboard with a particular handwriting engine, see \l {Configuration Options}. +\section1 Example Handwriting + +The Example Handwriting Plugin offers a simulated handwriting recognition experience +producing random characters regardless of what is written. It serves as a tool for +examining handwriting layouts and as a foundation for developing new plugins. + \section1 Cerence Handwriting \l {https://cerence.com}{Cerence Handwriting} diff --git a/src/virtualkeyboard/qt_cmdline.cmake b/src/virtualkeyboard/qt_cmdline.cmake index 43fb77b4..e66b46c8 100644 --- a/src/virtualkeyboard/qt_cmdline.cmake +++ b/src/virtualkeyboard/qt_cmdline.cmake @@ -8,7 +8,7 @@ qt_commandline_option(vkb-disable TYPE disableLang) qt_commandline_option(vkb-layouts TYPE boolean) qt_commandline_option(vkb-desktop TYPE boolean) qt_commandline_option(vkb-hunspell TYPE enum VALUES no 3rdparty system) -qt_commandline_option(vkb-handwriting TYPE optionalString VALUES no myscript-hwr cerence-hwr) +qt_commandline_option(vkb-handwriting TYPE optionalString VALUES no myscript-hwr cerence-hwr example-hwr) qt_commandline_option(vkb-cerence-sdk TYPE string) qt_commandline_option(vkb-style TYPE string VALUES standard retro none) qt_commandline_option(vkb-no-bundle-pinyin TYPE boolean) diff --git a/src/virtualkeyboard/qvirtualkeyboardfeatures_namespace_p.h b/src/virtualkeyboard/qvirtualkeyboardfeatures_namespace_p.h index 14f37e1f..0fdba36e 100644 --- a/src/virtualkeyboard/qvirtualkeyboardfeatures_namespace_p.h +++ b/src/virtualkeyboard/qvirtualkeyboardfeatures_namespace_p.h @@ -29,7 +29,7 @@ QML_NAMED_ELEMENT(VirtualKeyboardFeatures) enum Feature { Handwriting = -#if QT_CONFIG(cerence_hwr) || QT_CONFIG(myscript) +#if QT_CONFIG(cerence_hwr) || QT_CONFIG(myscript) || QT_CONFIG(example_hwr) 1 #else 0 |