aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJarkko Koivikko <jarkko.koivikko@code-q.fi>2023-04-24 12:39:40 +0300
committerJarkko Koivikko <jarkko.koivikko@code-q.fi>2023-05-09 14:30:48 +0300
commit310650815c98ab5d87a36e7fb6805cdfc45df2dc (patch)
treeb5be9d1d2b3e18b199739397602be00f06f2694a /src
parentb59f7eeb0cfc4ed89522d24476d942e96c00d606 (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.txt4
-rw-r--r--src/plugins/example/CMakeLists.txt13
-rw-r--r--src/plugins/example/hwr/CMakeLists.txt282
-rw-r--r--src/plugins/example/hwr/examplehwrinputmethod.cpp632
-rw-r--r--src/plugins/example/hwr/examplehwrinputmethod_p.h64
-rw-r--r--src/virtualkeyboard/configure.cmake8
-rw-r--r--src/virtualkeyboard/doc/src/build.qdoc6
-rw-r--r--src/virtualkeyboard/doc/src/handwriting.qdoc6
-rw-r--r--src/virtualkeyboard/qt_cmdline.cmake2
-rw-r--r--src/virtualkeyboard/qvirtualkeyboardfeatures_namespace_p.h2
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