aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJarkko Koivikko <jarkko.koivikko@code-q.fi>2017-04-17 11:04:04 +0300
committerJarkko Koivikko <jarkko.koivikko@code-q.fi>2017-07-08 19:28:08 +0000
commit7f780a44c0d5ccc65880666dcf388ed6bd3769a8 (patch)
tree7a139b5c4478154ba3a509c3ca2b4bf0d96203ff /src
parent0bf1c6ad9e0b5572b8ea99f5fc8945dade5675b7 (diff)
Add support for T9 Write CJK
This change adds support for handwriting in Simplified Chinese. The integration is based on T9 Write CJK SDK v7.8.1. [ChangeLog] Added support for CJK (Chinese/Japanese/Korean) handwriting via T9 Write. Change-Id: I18481cfd897987ecb471c49ecfcac62ea0c3489c Reviewed-by: Mitch Curtis <mitch.curtis@qt.io> Reviewed-by: Liang Qi <liang.qi@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/config.pri5
-rw-r--r--src/virtualkeyboard/3rdparty/t9write/t9write-build.pri86
-rw-r--r--src/virtualkeyboard/3rdparty/t9write/t9write.pro11
-rw-r--r--src/virtualkeyboard/3rdparty/t9write/unpack.py231
-rw-r--r--src/virtualkeyboard/content/components/InputModeKey.qml1
-rw-r--r--src/virtualkeyboard/content/components/Keyboard.qml2
-rw-r--r--src/virtualkeyboard/content/layouts/zh_CN/handwriting.qml116
-rw-r--r--src/virtualkeyboard/content/layouts/zh_CN/main.qml3
-rw-r--r--src/virtualkeyboard/content/styles/default/style.qml39
-rw-r--r--src/virtualkeyboard/content/styles/retro/style.qml35
-rw-r--r--src/virtualkeyboard/doc/src/build.qdoc130
-rw-r--r--src/virtualkeyboard/inputengine.cpp1
-rw-r--r--src/virtualkeyboard/inputengine.h3
-rw-r--r--src/virtualkeyboard/shifthandler.cpp2
-rw-r--r--src/virtualkeyboard/t9write.h49
-rw-r--r--src/virtualkeyboard/t9writedictionary.cpp117
-rw-r--r--src/virtualkeyboard/t9writedictionary.h67
-rw-r--r--src/virtualkeyboard/t9writeinputmethod.cpp994
-rw-r--r--src/virtualkeyboard/t9writeinputmethod.h5
-rw-r--r--src/virtualkeyboard/t9writeworker.cpp66
-rw-r--r--src/virtualkeyboard/t9writeworker.h24
-rw-r--r--src/virtualkeyboard/virtualkeyboard.pro62
22 files changed, 1730 insertions, 319 deletions
diff --git a/src/config.pri b/src/config.pri
index 11ae1c0b..c1784b14 100644
--- a/src/config.pri
+++ b/src/config.pri
@@ -4,6 +4,11 @@ handwriting:!lipi-toolkit:!t9write {
equals(T9WRITE_FOUND, 1): CONFIG += t9write
else: CONFIG += lipi-toolkit
}
+t9write {
+ !handwriting: include(virtualkeyboard/3rdparty/t9write/t9write-build.pri)
+ equals(T9WRITE_CJK_FOUND, 1): CONFIG += t9write-cjk
+ equals(T9WRITE_ALPHABETIC_FOUND, 1): CONFIG += t9write-alphabetic
+}
# Disable built-in layouts
disable-layouts {
diff --git a/src/virtualkeyboard/3rdparty/t9write/t9write-build.pri b/src/virtualkeyboard/3rdparty/t9write/t9write-build.pri
index ab519b90..f810badf 100644
--- a/src/virtualkeyboard/3rdparty/t9write/t9write-build.pri
+++ b/src/virtualkeyboard/3rdparty/t9write/t9write-build.pri
@@ -1,25 +1,83 @@
#
# Automatically detects the T9Write build directory and sets the following variables:
#
-# T9WRITE_BUILD_DIR: A base directory for the architecture specific build directory
-# T9WRITE_ALPHABETIC_OBJ: Absolute path to the target object file
+# T9WRITE_FOUND: 0/1 T9Write SDK found
+# T9WRITE_BUILD_STATIC: 0/1 Static libraries found (0 == shared libraries)
+# T9WRITE_ALPHABETIC_FOUND: 0/1 T9 Write Alphabetic API header found
+# T9WRITE_CJK_FOUND: 0/1 T9 Write CJK API header found
+# T9WRITE_INCLUDE_DIRS: T9 Write include directories
+# T9WRITE_ALPHABETIC_LIBS: Absolute path to the target library file
+# T9WRITE_ALPHABETIC_BINS: Absolute path to the target binary file (shared library)
+# T9WRITE_CJK_LIBS: Absolute path to the target library file
+# T9WRITE_CJK_BINS: Absolute path to the target binary file (shared library)
#
+T9WRITE_FOUND = 0
+T9WRITE_ALPHABETIC_FOUND = 0
+T9WRITE_CJK_FOUND = 0
+T9WRITE_INCLUDE_DIRS = $$PWD/api
contains(QT_ARCH, arm) {
- T9WRITE_BUILD_DIR = $$files(build_Android_ARM*)
+ T9WRITE_BUILD_SHARED_DIR = lib/arm/shared
+ T9WRITE_BUILD_STATIC_DIR = lib/arm/static
} else:linux {
- T9WRITE_BUILD_DIR = $$files(build_Android_x86*)
+ T9WRITE_BUILD_SHARED_DIR = lib/linux-x86/shared
+ T9WRITE_BUILD_STATIC_DIR = lib/linux-x86/static
} else:win32 {
- T9WRITE_BUILD_DIR = $$files(build_VC*)
+ T9WRITE_BUILD_SHARED_DIR = lib/win32/shared
+ T9WRITE_BUILD_STATIC_DIR = lib/win32/static
}
-count(T9WRITE_BUILD_DIR, 1) {
- T9WRITE_FOUND = 1
- T9WRITE_INCLUDE_DIRS = \
- $$PWD/$$T9WRITE_BUILD_DIR/api \
- $$PWD/$$T9WRITE_BUILD_DIR/public
- T9WRITE_ALPHABETIC_LIBS = \
- $$PWD/$$files($$T9WRITE_BUILD_DIR/objects/t9write_alphabetic*.o*)
-} else {
- T9WRITE_FOUND = 0
+defineReplace(findStaticLibrary) {
+ win32 {
+ result = $$files($$1/*.obj)
+ isEmpty(result): result = $$files($$1/*.lib)
+ } else {
+ result = $$files($$1/*.o)
+ isEmpty(result): result = $$files($$1/*.a)
+ }
+ return($$result)
+}
+
+defineReplace(findSharedLibrary) {
+ win32 {
+ result = $$files($$1/*.lib)
+ } else {
+ result = $$files($$1/*.so)
+ }
+ return($$result)
+}
+
+defineReplace(findSharedBinary) {
+ win32 {
+ result = $$files($$1/*.dll)
+ } else {
+ result = $$files($$1/*.so)
+ }
+ return($$result)
+}
+
+for(include_dir, T9WRITE_INCLUDE_DIRS) {
+ exists($${include_dir}/decuma_hwr.h): T9WRITE_ALPHABETIC_FOUND = 1
+ exists($${include_dir}/decuma_hwr_cjk.h): T9WRITE_CJK_FOUND = 1
+}
+
+equals(T9WRITE_ALPHABETIC_FOUND, 1)|equals(T9WRITE_CJK_FOUND, 1) {
+ equals(T9WRITE_ALPHABETIC_FOUND, 1) {
+ T9WRITE_ALPHABETIC_LIBS = $$findSharedLibrary($$PWD/$$T9WRITE_BUILD_SHARED_DIR/alphabetic)
+ !isEmpty(T9WRITE_ALPHABETIC_LIBS) {
+ T9WRITE_ALPHABETIC_BINS = $$findSharedBinary($$PWD/$$T9WRITE_BUILD_SHARED_DIR/alphabetic)
+ } else {
+ T9WRITE_ALPHABETIC_LIBS = $$findStaticLibrary($$PWD/$$T9WRITE_BUILD_STATIC_DIR/alphabetic)
+ }
+ }
+ equals(T9WRITE_CJK_FOUND, 1) {
+ T9WRITE_CJK_LIBS = $$findSharedLibrary($$PWD/$$T9WRITE_BUILD_SHARED_DIR/cjk)
+ !isEmpty(T9WRITE_CJK_LIBS) {
+ T9WRITE_CJK_BINS = $$findSharedBinary($$PWD/$$T9WRITE_BUILD_SHARED_DIR/cjk)
+ } else {
+ T9WRITE_CJK_LIBS = $$findStaticLibrary($$PWD/$$T9WRITE_BUILD_STATIC_DIR/cjk)
+ }
+ }
+ equals(T9WRITE_ALPHABETIC_FOUND, 1):!isEmpty(T9WRITE_ALPHABETIC_LIBS): T9WRITE_FOUND = 1
+ equals(T9WRITE_CJK_FOUND, 1):!isEmpty(T9WRITE_CJK_LIBS): T9WRITE_FOUND = 1
}
diff --git a/src/virtualkeyboard/3rdparty/t9write/t9write.pro b/src/virtualkeyboard/3rdparty/t9write/t9write.pro
index 05f723a7..f6dddf1c 100644
--- a/src/virtualkeyboard/3rdparty/t9write/t9write.pro
+++ b/src/virtualkeyboard/3rdparty/t9write/t9write.pro
@@ -2,18 +2,19 @@ TARGET = qtt9write_db
CONFIG += static
-T9WRITE_LDBS = $$files(databases/XT9_LDBs/*.ldb)
-
T9WRITE_RESOURCE_FILES = \
- databases/HWR_LatinCG/_databas_le.bin \
- $$T9WRITE_LDBS
+ $$files(data/*.bin) \
+ $$files(data/*.ldb) \
+ $$files(data/*.hdb) \
+ $$files(data/*.phd)
# Note: Compression is disabled, because the resource is accessed directly from the memory
QMAKE_RESOURCE_FLAGS += -no-compress
+CONFIG += resources_big
include(../../generateresource.pri)
-RESOURCES += $$generate_resource(t9write_db.qrc, $$T9WRITE_RESOURCE_FILES)
+RESOURCES += $$generate_resource(t9write_db.qrc, $$T9WRITE_RESOURCE_FILES, /QtQuick/VirtualKeyboard/T9Write)
load(qt_helper_lib)
diff --git a/src/virtualkeyboard/3rdparty/t9write/unpack.py b/src/virtualkeyboard/3rdparty/t9write/unpack.py
new file mode 100644
index 00000000..1e213656
--- /dev/null
+++ b/src/virtualkeyboard/3rdparty/t9write/unpack.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#############################################################################
+##
+## Copyright (C) 2017 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:GPL$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 3 or (at your option) any later version
+## approved by the KDE Free Qt Foundation. The licenses are as published by
+## the Free Software Foundation and appearing in the file LICENSE.GPL3
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import os
+import sys
+import zipfile
+import tempfile
+import shutil
+import fnmatch
+
+#
+# This utility script unpacks the T9 Write SDK to appropriate directory
+# structure for Qt Virtual Keyboard.
+#
+# Usage: unpack.py <filename.zip> <target dir>
+#
+# The script will happily overwrite existing files, so be careful.
+#
+
+#
+# Unpack rule map
+#
+# Format:
+# 1. <target dir>: [ 'pattern1', 'pattern2', ... ]
+# - 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>: [ [ 'file group pattern', 'sub pattern1', ... ] ]
+# - 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.
+#
+
+UNPACK_RULES = {
+'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',
+ '*/decumaRuntimeMallocData.h',
+ '*/decumaStatus.h',
+ '*/decumaStorageSpecifiers.h',
+ '*/decumaSymbolCategories.h',
+ '*/decumaUnicodeTypes.h',
+ '*/t9write_alpha_version.h',
+ '*/t9write_api_version.h',
+ '*/t9write_cjk_version.h',
+ '*/xxt9wApiOem.h',
+ '*/xxt9wOem.h',
+],
+'data': [
+ '*/_databas_le.bin',
+ '*/*.hdb',
+ '*/*.phd',
+ '*/*.ldb',
+],
+'lib/arm/static/alphabetic': [
+ '*T9Write_Alpha*/*Android_ARM*/*.a',
+ '*T9Write_Alpha*/*Android_ARM*/*.o',
+],
+'lib/arm/shared/alphabetic': [
+ '*T9Write_Alpha*/*Android_ARM*/*.so',
+],
+'lib/arm/static/cjk': [
+ '*T9Write_CJK*/*Android_ARM*/*.a',
+ '*T9Write_CJK*/*Android_ARM*/*.o',
+],
+'lib/arm/shared/cjk': [
+ '*T9Write_CJK*/*Android_ARM*/*.so',
+],
+'lib/linux/static/alphabetic': [
+ '*T9Write_Alpha*/*Android_x86*/*.a',
+ '*T9Write_Alpha*/*Android_x86*/*.o',
+],
+'lib/linux/shared/alphabetic': [
+ '*T9Write_Alpha*/*Android_x86*/*.so',
+],
+'lib/linux/static/cjk': [
+ '*T9Write_CJK*/*Android_x86*/*.a',
+ '*T9Write_CJK*/*Android_x86*/*.o',
+],
+'lib/linux/shared/cjk': [
+ '*T9Write_CJK*/*Android_x86*/*.so',
+],
+'lib/win32/static/alphabetic': [
+ '*T9Write_Alpha*/*.obj',
+],
+'lib/win32/shared/alphabetic': [
+ [ '*T9Write_Alpha*/*.dll', '*.lib' ],
+],
+'lib/win32/static/cjk': [
+ '*T9Write_CJK*/*.obj',
+],
+'lib/win32/shared/cjk': [
+ [ '*T9Write_CJK*/*.dll', '*.lib' ],
+],
+'lib/win32/shared/alphabetic': [
+ [ '*T9Write_Alpha*/*.dll', '*.lib' ],
+],
+}
+
+#
+# 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 = []
+ 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):
+ return [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))]
+
+def unpack(zip_list, zip_dir, out_dir):
+ if not zip_list:
+ return
+
+ 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) != 3:
+ print("Usage: %s <filename.zip> <target dir>" % os.path.basename(__file__))
+ exit()
+
+ out_dir = sys.argv[2]
+ zip_dir = tempfile.mkdtemp()
+
+ try:
+ unpack(unzip(sys.argv[1], zip_dir), zip_dir, out_dir)
+ finally:
+ shutil.rmtree(zip_dir)
diff --git a/src/virtualkeyboard/content/components/InputModeKey.qml b/src/virtualkeyboard/content/components/InputModeKey.qml
index dcc10d54..d880867f 100644
--- a/src/virtualkeyboard/content/components/InputModeKey.qml
+++ b/src/virtualkeyboard/content/components/InputModeKey.qml
@@ -68,6 +68,7 @@ Key {
"あ", // InputEngine.Hiragana
"カ", // InputEngine.Katakana
"全角", // InputEngine.FullwidthLatin
+ "中文", // InputEngine.ChineseHandwriting
]
function __nextInputMode(inputMode) {
diff --git a/src/virtualkeyboard/content/components/Keyboard.qml b/src/virtualkeyboard/content/components/Keyboard.qml
index 2bd2ff2e..8dfd7f7d 100644
--- a/src/virtualkeyboard/content/components/Keyboard.qml
+++ b/src/virtualkeyboard/content/components/Keyboard.qml
@@ -1152,7 +1152,7 @@ Item {
inputMode = InputEngine.Dialable
else if ((InputContext.inputMethodHints & (Qt.ImhFormattedNumbersOnly | Qt.ImhDigitsOnly)) && inputModes.indexOf(InputEngine.Numeric) !== -1)
inputMode = InputEngine.Numeric
- else
+ else if (keyboardLayoutLoader.item.inputMode === -1)
inputMode = inputModes[0]
}
diff --git a/src/virtualkeyboard/content/layouts/zh_CN/handwriting.qml b/src/virtualkeyboard/content/layouts/zh_CN/handwriting.qml
new file mode 100644
index 00000000..598980d1
--- /dev/null
+++ b/src/virtualkeyboard/content/layouts/zh_CN/handwriting.qml
@@ -0,0 +1,116 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Layouts 1.0
+import QtQuick.VirtualKeyboard 2.3
+
+KeyboardLayout {
+ function createInputMethod() {
+ return Qt.createQmlObject('import QtQuick 2.0; import QtQuick.VirtualKeyboard 2.3; HandwritingInputMethod {}', parent)
+ }
+ sharedLayouts: ['symbols']
+ inputMode: preferredInputMode()
+
+ Connections {
+ target: InputContext
+ 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.Latin : InputEngine.ChineseHandwriting
+ }
+
+ KeyboardRow {
+ Layout.preferredHeight: 3
+ KeyboardColumn {
+ Layout.preferredWidth: bottomRow.width - hideKeyboardKey.width
+ KeyboardRow {
+ TraceInputKey {
+ objectName: "hwrInputArea"
+ patternRecognitionMode: InputEngine.HandwritingRecoginition
+ horizontalRulers:
+ InputContext.inputEngine.inputMode !== InputEngine.ChineseHandwriting ? [] :
+ [Math.round(boundingBox.height / 4), Math.round(boundingBox.height / 4) * 2, Math.round(boundingBox.height / 4) * 3]
+
+ }
+ }
+ }
+ KeyboardColumn {
+ Layout.preferredWidth: hideKeyboardKey.width
+ KeyboardRow {
+ BackspaceKey {}
+ }
+ KeyboardRow {
+ EnterKey {}
+ }
+ KeyboardRow {
+ ShiftKey { }
+ }
+ }
+ }
+ KeyboardRow {
+ id: bottomRow
+ Layout.preferredHeight: 1
+ keyWeight: 154
+ InputModeKey {
+ weight: 217
+ }
+ ChangeLanguageKey {
+ weight: 154
+ customLayoutsOnly: true
+ }
+ HandwritingModeKey {
+ weight: 154
+ }
+ SpaceKey {
+ weight: 864
+ }
+ Key {
+ key: Qt.Key_Apostrophe
+ text: "‘"
+ alternativeKeys: "《》〈〉•…々〆‘’“”「」¥"
+ }
+ Key {
+ key: Qt.Key_Period
+ text: "."
+ alternativeKeys: ":;,.、。?!"
+ }
+ HideKeyboardKey {
+ id: hideKeyboardKey
+ weight: 204
+ }
+ }
+}
diff --git a/src/virtualkeyboard/content/layouts/zh_CN/main.qml b/src/virtualkeyboard/content/layouts/zh_CN/main.qml
index c8e21c84..44b0ceb7 100644
--- a/src/virtualkeyboard/content/layouts/zh_CN/main.qml
+++ b/src/virtualkeyboard/content/layouts/zh_CN/main.qml
@@ -181,6 +181,9 @@ KeyboardLayout {
ChangeLanguageKey {
weight: 154
}
+ HandwritingModeKey {
+ weight: 154
+ }
SpaceKey {
weight: 864
}
diff --git a/src/virtualkeyboard/content/styles/default/style.qml b/src/virtualkeyboard/content/styles/default/style.qml
index 03f0344f..9d00bb11 100644
--- a/src/virtualkeyboard/content/styles/default/style.qml
+++ b/src/virtualkeyboard/content/styles/default/style.qml
@@ -757,7 +757,17 @@ KeyboardStyle {
Text {
id: hwrInputModeIndicator
visible: control.patternRecognitionMode === InputEngine.HandwritingRecoginition
- text: InputContext.inputEngine.inputMode === InputEngine.Latin ? "Abc" : "123"
+ text: {
+ switch (InputContext.inputEngine.inputMode) {
+ case InputEngine.Numeric:
+ case InputEngine.Dialable:
+ return "123"
+ case InputEngine.ChineseHandwriting:
+ return "中文"
+ default:
+ return "Abc"
+ }
+ }
color: "white"
anchors.left: parent.left
anchors.top: parent.top
@@ -786,23 +796,42 @@ KeyboardStyle {
ctx.strokeStyle = Qt.rgba(0xFF, 0xFF, 0xFF)
ctx.clearRect(0, 0, width, height)
var i
+ var margin = Math.round(30 * scaleHint)
if (control.horizontalRulers) {
for (i = 0; i < control.horizontalRulers.length; i++) {
ctx.beginPath()
- ctx.moveTo(0, control.horizontalRulers[i])
- ctx.lineTo(width, control.horizontalRulers[i])
+ var y = Math.round(control.horizontalRulers[i])
+ var rightMargin = Math.round(width - margin)
+ if (i + 1 === control.horizontalRulers.length) {
+ ctx.moveTo(margin, y)
+ ctx.lineTo(rightMargin, y)
+ } else {
+ var dashLen = Math.round(20 * scaleHint)
+ for (var dash = margin, dashCount = 0;
+ dash < rightMargin; dash += dashLen, dashCount++) {
+ if ((dashCount & 1) === 0) {
+ ctx.moveTo(dash, y)
+ ctx.lineTo(Math.min(dash + dashLen, rightMargin), y)
+ }
+ }
+ }
ctx.stroke()
}
}
if (control.verticalRulers) {
for (i = 0; i < control.verticalRulers.length; i++) {
ctx.beginPath()
- ctx.moveTo(control.verticalRulers[i], 0)
- ctx.lineTo(control.verticalRulers[i], height)
+ ctx.moveTo(control.verticalRulers[i], margin)
+ ctx.lineTo(control.verticalRulers[i], Math.round(height - margin))
ctx.stroke()
}
}
}
+ Connections {
+ target: control
+ onHorizontalRulersChanged: traceInputKeyGuideLines.requestPaint()
+ onVerticalRulersChanged: traceInputKeyGuideLines.requestPaint()
+ }
}
}
diff --git a/src/virtualkeyboard/content/styles/retro/style.qml b/src/virtualkeyboard/content/styles/retro/style.qml
index a1cb3ffc..3751608b 100644
--- a/src/virtualkeyboard/content/styles/retro/style.qml
+++ b/src/virtualkeyboard/content/styles/retro/style.qml
@@ -871,7 +871,17 @@ KeyboardStyle {
Text {
id: hwrInputModeIndicator
visible: control.patternRecognitionMode === InputEngine.HandwritingRecoginition
- text: InputContext.inputEngine.inputMode === InputEngine.Latin ? "Abc" : "123"
+ text: {
+ switch (InputContext.inputEngine.inputMode) {
+ case InputEngine.Numeric:
+ case InputEngine.Dialable:
+ return "123"
+ case InputEngine.ChineseHandwriting:
+ return "中文"
+ default:
+ return "Abc"
+ }
+ }
color: "black"
anchors.left: parent.left
anchors.top: parent.top
@@ -899,11 +909,25 @@ KeyboardStyle {
ctx.strokeStyle = Qt.rgba(0xFF, 0xFF, 0xFF)
ctx.clearRect(0, 0, width, height)
var i
+ var margin = Math.round(30 * scaleHint)
if (control.horizontalRulers) {
for (i = 0; i < control.horizontalRulers.length; i++) {
ctx.beginPath()
- ctx.moveTo(0, control.horizontalRulers[i])
- ctx.lineTo(width, control.horizontalRulers[i])
+ var y = Math.round(control.horizontalRulers[i])
+ var rightMargin = Math.round(width - margin)
+ if (i + 1 === control.horizontalRulers.length) {
+ ctx.moveTo(margin, y)
+ ctx.lineTo(rightMargin, y)
+ } else {
+ var dashLen = Math.round(20 * scaleHint)
+ for (var dash = margin, dashCount = 0;
+ dash < rightMargin; dash += dashLen, dashCount++) {
+ if ((dashCount & 1) === 0) {
+ ctx.moveTo(dash, y)
+ ctx.lineTo(Math.min(dash + dashLen, rightMargin), y)
+ }
+ }
+ }
ctx.stroke()
}
}
@@ -916,6 +940,11 @@ KeyboardStyle {
}
}
}
+ Connections {
+ target: control
+ onHorizontalRulersChanged: traceInputKeyGuideLines.requestPaint()
+ onVerticalRulersChanged: traceInputKeyGuideLines.requestPaint()
+ }
}
}
diff --git a/src/virtualkeyboard/doc/src/build.qdoc b/src/virtualkeyboard/doc/src/build.qdoc
index 3d9d9eac..9b18a54d 100644
--- a/src/virtualkeyboard/doc/src/build.qdoc
+++ b/src/virtualkeyboard/doc/src/build.qdoc
@@ -281,6 +281,136 @@ Hunspell sources and dictionary files is listed below:
(etc.)
\endcode
+\section2 T9 Write Integration
+
+T9 Write integration supports the T9 Write Alphabetic and T9 Write CJK engines. Both
+engines are integrated via T9WriteInputMethod. The input method can be initialized
+with either of the engines at runtime. The engine selection happens automatically
+based on input locale and input mode from the keyboard.
+
+\section3 T9 Write Compatibility
+
+Qt Virtual Keyboard is compatible with T9 Write v7.5.0 onward.
+
+The latest tested version is v7.8.1.
+
+\section3 T9 Write Build Preparations
+
+The contents of the SDK must be either manually copied to the directory structure
+described below, or by using the \e unpack.py script found in the t9write directory.
+
+To unpack the SDK using the script:
+
+\badcode
+$ cd src/virtualkeyboard/3rdparty/t9write/
+$ python unpack.py T9Write_Alpha_v7-8-0_SDK.zip .
+\endcode
+
+\badcode
+3rdparty
+└── t9write
+ ├─── api
+ │ ├─── decuma*.h
+ │ ├─── t9write*.h
+ │ └─── xxt9w*.h
+ ├─── data
+ │ ├─── *.bin [T9 Write Alphabetic]
+ │ ├─── *.hdb
+ │ ├─── *.phd
+ │ └─── *.ldb [T9 Write v7.5]
+ └─── lib
+ ├─── arm
+ │ ├─── shared
+ │ │ ├─── alphabetic
+ │ │ │ └─── *.so
+ │ │ └─── cjk
+ │ │ └─── *.so
+ │ └─── static
+ │ ├─── alphabetic
+ │ │ └─── *.a / *.o
+ │ └─── cjk
+ │ └─── *.a / *.o
+ ├─── linux-x86
+ │ ├─── shared
+ │ │ ├─── alphabetic
+ │ │ │ └─── *.so
+ │ │ └─── cjk
+ │ │ └─── *.so
+ │ └─── static
+ │ ├─── alphabetic
+ │ │ └─── *.a / *.o
+ │ └─── cjk
+ │ └─── *.a / *.o
+ └─── win32
+ ├─── shared
+ │ ├─── alphabetic
+ │ │ ├─── *.dll
+ │ │ └─── *.lib
+ │ └─── cjk
+ │ ├─── *.dll
+ │ └─── *.lib
+ └─── static
+ ├─── alphabetic
+ │ └─── *.lib / *.obj
+ └─── cjk
+ └─── *.lib / *.obj
+\endcode
+
+\note The above files are from the T9 Write demo SDK for Windows; the contents may vary for other
+ platforms.
+
+Where the contents of each directory are:
+
+\table
+\header
+ \li Directory
+ \li Description
+ \li Remarks
+\row
+ \li \e api
+ \li This directory should contain all of the API files
+ \li The API files usually located in the "api" and "public" directories
+ of the SDK, but sometimes in the "demo" directory.
+
+ When using both Alphabetic and CJK engines at the same time, any
+ overlapping files can be copied from either SDK.
+\row
+ \li \e data
+ \li This directory should contain all HWR databases and optionally
+ XT9 databases.
+ \li HWR database for the T9 Write Alphabetic:
+ \list
+ \li \e _databas_le.bin
+ \endlist
+
+ HWR database for the T9 Write CJK:
+ \list
+ \li \e cjk_HK_std_le.hdb HongKong Chinese
+ \li \e cjk_J_std_le.hdb Japanese
+ \li \e cjk_K_mkt_le.hdb Korean
+ \li \e cjk_S_gb18030_le.hdb Simplified Chinese
+ \li \e cjk_T_std_le.hdb Traditional Chinese
+ \endlist
+
+ Language database:
+ \list
+ \li File extension is either \e .ldb or \e .phd
+ \endlist
+\row
+ \li \e lib/<target>/<linkage>/<engine-variant>
+ \li Directory structure holding supported target builds.
+ \li These directories should hold the desired target libraries.
+ If both shared and static libraries are found, shared libraries
+ are preferred.
+
+ For example, to enable a static win32 build, copy
+ \e t9write_alphabetic_rel.obj to \e lib/win32/static/alphabetic
+ directory.
+\endtable
+
+Finally, the SDK is included in the build by adding CONFIG+=t9write to the
+qmake command line.
+
\section2 Static builds
The virtual keyboard can be built and linked statically against the application.
diff --git a/src/virtualkeyboard/inputengine.cpp b/src/virtualkeyboard/inputengine.cpp
index 10f7dc7b..dd04f645 100644
--- a/src/virtualkeyboard/inputengine.cpp
+++ b/src/virtualkeyboard/inputengine.cpp
@@ -764,6 +764,7 @@ void InputEngine::timerEvent(QTimerEvent *timerEvent)
\li \c InputEngine.Hiragana Hiragana input mode for Japanese.
\li \c InputEngine.Katakana Katakana input mode for Japanese.
\li \c InputEngine.FullwidthLatin Fullwidth latin input mode for East Asian languages.
+ \li \c InputEngine.ChineseHandwriting Chinese handwriting.
\endlist
*/
diff --git a/src/virtualkeyboard/inputengine.h b/src/virtualkeyboard/inputengine.h
index ce0122ac..d1b99ae4 100644
--- a/src/virtualkeyboard/inputengine.h
+++ b/src/virtualkeyboard/inputengine.h
@@ -73,7 +73,8 @@ public:
Hangul,
Hiragana,
Katakana,
- FullwidthLatin
+ FullwidthLatin,
+ ChineseHandwriting
};
enum PatternRecognitionMode {
PatternRecognitionDisabled,
diff --git a/src/virtualkeyboard/shifthandler.cpp b/src/virtualkeyboard/shifthandler.cpp
index 376410e3..a5f80d05 100644
--- a/src/virtualkeyboard/shifthandler.cpp
+++ b/src/virtualkeyboard/shifthandler.cpp
@@ -51,7 +51,7 @@ public:
resetWhenVisible(false),
manualShiftLanguageFilter(QSet<QLocale::Language>() << QLocale::Arabic << QLocale::Persian << QLocale::Hindi << QLocale::Korean),
manualCapsInputModeFilter(QSet<InputEngine::InputMode>() << InputEngine::Cangjie << InputEngine::Zhuyin),
- noAutoUppercaseInputModeFilter(QSet<InputEngine::InputMode>() << InputEngine::FullwidthLatin << InputEngine::Pinyin << InputEngine::Cangjie << InputEngine::Zhuyin),
+ noAutoUppercaseInputModeFilter(QSet<InputEngine::InputMode>() << InputEngine::FullwidthLatin << InputEngine::Pinyin << InputEngine::Cangjie << InputEngine::Zhuyin << InputEngine::ChineseHandwriting),
allCapsInputModeFilter(QSet<InputEngine::InputMode>() << InputEngine::Hiragana << InputEngine::Katakana)
{
timer.start();
diff --git a/src/virtualkeyboard/t9write.h b/src/virtualkeyboard/t9write.h
new file mode 100644
index 00000000..621d2312
--- /dev/null
+++ b/src/virtualkeyboard/t9write.h
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef T9WRITE_H
+#define T9WRITE_H
+
+#include "t9write_api_version.h"
+#ifdef HAVE_T9WRITE_ALPHABETIC
+#include "decuma_hwr.h"
+#endif
+#ifdef HAVE_T9WRITE_CJK
+#include "decuma_hwr_cjk.h"
+#endif
+
+#if defined(HAVE_T9WRITE_CJK) && defined(HAVE_T9WRITE_ALPHABETIC)
+#define DECUMA_API(FUNC_NAME) (cjk ? decumaCJK ## FUNC_NAME : decuma ## FUNC_NAME)
+#elif defined(HAVE_T9WRITE_CJK)
+#define DECUMA_API(FUNC_NAME) (decumaCJK ## FUNC_NAME)
+#else // defined(HAVE_T9WRITE_ALPHABETIC)
+#define DECUMA_API(FUNC_NAME) (decuma ## FUNC_NAME)
+#endif
+
+#endif // T9WRITE_H
diff --git a/src/virtualkeyboard/t9writedictionary.cpp b/src/virtualkeyboard/t9writedictionary.cpp
new file mode 100644
index 00000000..d15b16e3
--- /dev/null
+++ b/src/virtualkeyboard/t9writedictionary.cpp
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "t9writedictionary.h"
+#include "virtualkeyboarddebug.h"
+
+namespace QtVirtualKeyboard {
+
+T9WriteDictionary::T9WriteDictionary(DECUMA_SESSION *decumaSession,
+ const DECUMA_MEM_FUNCTIONS &memFuncs,
+ bool cjk) :
+ decumaSession(decumaSession),
+ memFuncs(memFuncs),
+ cjk(cjk),
+ sourceData(0),
+ sourceSize(0),
+ convertedData(0),
+ convertedSize(0)
+{
+}
+
+T9WriteDictionary::~T9WriteDictionary()
+{
+ if (convertedData) {
+ DECUMA_STATUS status = DECUMA_API(DestroyConvertedDictionary)(&convertedData, &memFuncs);
+ Q_ASSERT(status == decumaNoError);
+ Q_ASSERT(convertedData == 0);
+ }
+}
+
+bool T9WriteDictionary::load(const QString &fileName)
+{
+ if (sourceData || convertedData)
+ return false;
+
+ file.setFileName(fileName);
+ if (file.open(QIODevice::ReadOnly)) {
+ sourceSize = file.size();
+ sourceData = file.map(0, sourceSize, QFile::NoOptions);
+ if (!sourceData) {
+ sourceSize = 0;
+ qWarning() << "Could not read dictionary file" << fileName;
+ }
+ file.close();
+ } else {
+ qWarning() << "Could not open dictionary file" << fileName;
+ }
+
+ return sourceData != 0;
+}
+
+bool T9WriteDictionary::convert(const DECUMA_SRC_DICTIONARY_INFO &dictionaryInfo)
+{
+ if (!sourceData || convertedData)
+ return false;
+
+ DECUMA_STATUS status;
+ status = DECUMA_API(ConvertDictionary)(&convertedData, sourceData, (DECUMA_UINT32)sourceSize,
+ &dictionaryInfo, &convertedSize, &memFuncs);
+
+ if (status != decumaNoError) {
+ qWarning() << "Could not convert dictionary" << file.fileName();
+ file.unmap((uchar *)sourceData);
+ sourceSize = 0;
+ sourceData = 0;
+ }
+
+ return status == decumaNoError;
+}
+
+QString T9WriteDictionary::fileName() const
+{
+ return file.fileName();
+}
+
+const void *T9WriteDictionary::data() const
+{
+ return convertedData ? convertedData : sourceData;
+}
+
+qint64 T9WriteDictionary::size() const
+{
+ return convertedData ? convertedSize : sourceSize;
+}
+
+bool T9WriteDictionary::isConverted() const
+{
+ return convertedData != 0;
+}
+
+} // namespace QtVirtualKeyboard
diff --git a/src/virtualkeyboard/t9writedictionary.h b/src/virtualkeyboard/t9writedictionary.h
new file mode 100644
index 00000000..dc2d9475
--- /dev/null
+++ b/src/virtualkeyboard/t9writedictionary.h
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef T9WRITEDICTIONARY_H
+#define T9WRITEDICTIONARY_H
+
+#include <QtGlobal>
+#include <QFile>
+#include "t9write.h"
+
+namespace QtVirtualKeyboard {
+
+class T9WriteDictionary
+{
+ Q_DISABLE_COPY(T9WriteDictionary)
+public:
+ explicit T9WriteDictionary(DECUMA_SESSION *decumaSession, const DECUMA_MEM_FUNCTIONS &memFuncs, bool cjk);
+ ~T9WriteDictionary();
+
+ bool load(const QString &fileName);
+ bool convert(const DECUMA_SRC_DICTIONARY_INFO &dictionaryInfo);
+
+ QString fileName() const;
+ const void *data() const;
+ qint64 size() const;
+ bool isConverted() const;
+
+private:
+ QFile file;
+ DECUMA_SESSION *decumaSession;
+ const DECUMA_MEM_FUNCTIONS &memFuncs;
+ bool cjk;
+ void *sourceData;
+ qint64 sourceSize;
+ void *convertedData;
+ DECUMA_UINT32 convertedSize;
+};
+
+}
+
+#endif // T9WRITEDICTIONARY_H
diff --git a/src/virtualkeyboard/t9writeinputmethod.cpp b/src/virtualkeyboard/t9writeinputmethod.cpp
index eb4cf9fb..3028d7d0 100644
--- a/src/virtualkeyboard/t9writeinputmethod.cpp
+++ b/src/virtualkeyboard/t9writeinputmethod.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
@@ -34,15 +34,27 @@
#include "t9writeworker.h"
#include "virtualkeyboarddebug.h"
#include <QDirIterator>
+#include <QCryptographicHash>
#ifdef QT_VIRTUALKEYBOARD_DEBUG
#include <QTime>
#endif
+#include "handwritinggesturerecognizer.h"
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+#include "unipentrace.h"
+#include <QStandardPaths>
+#endif
-#include "decuma_hwr.h"
#include "decumaStatus.h"
#include "decumaSymbolCategories.h"
#include "decumaLanguages.h"
-#include "xxt9wOem.h"
+
+/* 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_T9WRITE_LOG 0
namespace QtVirtualKeyboard {
@@ -94,12 +106,26 @@ class T9WriteInputMethodPrivate : public AbstractInputMethodPrivate
Q_DECLARE_PUBLIC(T9WriteInputMethod)
public:
+ enum EngineMode {
+ EngineUninitialized,
+ Alphabetic,
+ SimplifiedChinese,
+ TraditionalChinese,
+ HongKongChinese,
+ Japanese,
+ Korean
+ };
+
T9WriteInputMethodPrivate(T9WriteInputMethod *q_ptr) :
AbstractInputMethodPrivate(),
q_ptr(q_ptr),
+ cjk(false),
+ engineMode(EngineUninitialized),
+ defaultHwrDbPath(QLatin1String(":/QtQuick/VirtualKeyboard/T9Write/data/")),
+ defaultDictionaryDbPath(defaultHwrDbPath),
dictionaryLock(QMutex::Recursive),
- convertedDictionary(0),
attachedDictionary(0),
+ traceListHardLimit(32),
resultId(0),
resultTimer(0),
decumaSession(0),
@@ -107,6 +133,9 @@ public:
arcAdditionStarted(false),
ignoreUpdate(false),
textCase(InputEngine::Lower)
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ , unipenTrace()
+#endif
{
}
@@ -128,34 +157,74 @@ public:
free(ptr);
}
- void initEngine()
+#if QT_VIRTUALKEYBOARD_T9WRITE_LOG
+ static void decumaLogString(void *pUserData, const char *pLogString, DECUMA_UINT32 nLogStringLength)
+ {
+ static QMutex s_logMutex;
+ static QByteArray s_logString;
+ Q_UNUSED(pUserData)
+ Q_UNUSED(nLogStringLength)
+ 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
+
+ bool initEngine(EngineMode newEngineMode)
{
- VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::initEngine()";
+ if (engineMode == newEngineMode)
+ return engineMode != EngineUninitialized;
+
+ VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::initEngine()" << newEngineMode;
if (decumaSession)
- return;
+ exitEngine();
- symbolCategories.clear();
- symbolCategories.append(DECUMA_CATEGORY_ANSI);
- languageCategories.clear();
- languageCategories.append(DECUMA_LANG_EN);
+ if (newEngineMode == EngineUninitialized)
+ return false;
+
+ switch (newEngineMode) {
+ case Alphabetic:
+ cjk = false;
+ break;
+ case SimplifiedChinese:
+ case TraditionalChinese:
+ case HongKongChinese:
+ case Japanese:
+ case Korean:
+ cjk = true;
+ break;
+ default:
+ Q_ASSERT(0 && "Invalid EngineMode!");
+ return false;
+ }
+ engineMode = newEngineMode;
memset(&sessionSettings, 0, sizeof(sessionSettings));
- QString latinDb = findLatinDb(":/databases/HWR_LatinCG/");
- hwrDbFile.setFileName(latinDb);
+ QString hwrDb = findHwrDb(engineMode, defaultHwrDbPath);
+ hwrDbFile.setFileName(hwrDb);
if (!hwrDbFile.open(QIODevice::ReadOnly)) {
- qWarning() << "Could not open hwr database file" << latinDb;
- return;
+ qCritical() << "Could not open HWR database" << hwrDb;
+ exitEngine();
+ return false;
}
sessionSettings.pStaticDB = (DECUMA_STATIC_DB_PTR)hwrDbFile.map(0, hwrDbFile.size(), QFile::NoOptions);
if (!sessionSettings.pStaticDB) {
- hwrDbFile.close();
- qWarning() << "Could not map hwr database" << latinDb;
- return;
+ qCritical() << "Could not read HWR database" << hwrDb;
+ exitEngine();
+ return false;
}
+ symbolCategories.append(DECUMA_CATEGORY_ANSI);
+ languageCategories.append(DECUMA_LANG_EN);
+
sessionSettings.recognitionMode = mcrMode;
sessionSettings.bMinimizeAddArcPreProcessing = 1;
sessionSettings.writingDirection = unknownWriting;
@@ -164,17 +233,25 @@ public:
sessionSettings.charSet.pLanguages = languageCategories.data();
sessionSettings.charSet.nLanguages = languageCategories.size();
- session = QByteArray(decumaGetSessionSize(), 0);
+ session = QByteArray(DECUMA_API(GetSessionSize)(), 0);
decumaSession = (DECUMA_SESSION *)(!session.isEmpty() ? session.data() : 0);
- DECUMA_STATUS status = decumaBeginSession(decumaSession, &sessionSettings, &memFuncs);
+ DECUMA_STATUS status = DECUMA_API(BeginSession)(decumaSession, &sessionSettings, &memFuncs);
Q_ASSERT(status == decumaNoError);
if (status != decumaNoError) {
- qWarning() << "Could not initialize T9Write engine" << status;
+ qCritical() << "Could not initialize T9Write engine" << status;
+ exitEngine();
+ return false;
}
- worker.reset(new T9WriteWorker(decumaSession));
+#if QT_VIRTUALKEYBOARD_T9WRITE_LOG
+ DECUMA_API(StartLogging)(decumaSession, 0, decumaLogString);
+#endif
+
+ worker.reset(new T9WriteWorker(decumaSession, cjk));
worker->start();
+
+ return true;
}
void exitEngine()
@@ -188,36 +265,62 @@ public:
hwrDbFile.close();
}
- detachDictionary(&attachedDictionary);
- destroyConvertedDictionary(&convertedDictionary);
+ if (attachedDictionary) {
+ detachDictionary(attachedDictionary);
+ attachedDictionary.reset();
+ }
+ loadedDictionary.reset();
if (decumaSession) {
- decumaEndSession(decumaSession);
+#if QT_VIRTUALKEYBOARD_T9WRITE_LOG
+ DECUMA_API(StopLogging)(decumaSession);
+#endif
+ DECUMA_API(EndSession)(decumaSession);
decumaSession = 0;
session.clear();
}
memset(&sessionSettings, 0, sizeof(sessionSettings));
- }
- QString findLatinDb(const QString &dir)
- {
- QString latinDb;
- QDirIterator it(dir, QDirIterator::NoIteratorFlags);
- while (it.hasNext()) {
- QString fileEntry = it.next();
+ symbolCategories.clear();
+ languageCategories.clear();
- if (!fileEntry.endsWith(QLatin1String(".bin")))
- continue;
+ engineMode = EngineUninitialized;
+ cjk = false;
+ }
- latinDb = fileEntry;
+ QString findHwrDb(EngineMode mode, const QString &dir) const
+ {
+ QString hwrDbPath(dir);
+ switch (mode) {
+ case Alphabetic:
+ hwrDbPath.append(QLatin1String("_databas_le.bin"));
break;
+ case SimplifiedChinese:
+ hwrDbPath.append(QLatin1String("cjk_S_gb18030_le.hdb"));
+ break;
+ case TraditionalChinese:
+ hwrDbPath.append(QLatin1String("cjk_T_std_le.hdb"));
+ break;
+ case HongKongChinese:
+ hwrDbPath.append(QLatin1String("cjk_HK_std_le.hdb"));
+ break;
+ case Japanese:
+ hwrDbPath.append(QLatin1String("cjk_J_std_le.hdb"));
+ break;
+ case Korean:
+ hwrDbPath.append(QLatin1String("cjk_K_mkt_le.hdb"));
+ break;
+ default:
+ return QString();
}
- return latinDb;
+ return QFileInfo::exists(hwrDbPath) ? hwrDbPath : QString();
}
- QString findDictionary(const QString &dir, const QLocale &locale)
+ QString findDictionary(const QString &dir, const QLocale &locale, DECUMA_SRC_DICTIONARY_TYPE &srcType)
{
+ srcType = numberOfSrcDictionaryTypes;
+
QStringList languageCountry = locale.name().split("_");
if (languageCountry.length() != 2)
return QString();
@@ -227,11 +330,27 @@ public:
while (it.hasNext()) {
QString fileEntry = it.next();
- if (!fileEntry.endsWith(QLatin1String(".ldb")))
+ if (!fileEntry.contains("_" + languageCountry[0].toUpper()))
continue;
- if (!fileEntry.contains("_" + languageCountry[0].toUpper()))
+ if (fileEntry.endsWith(QLatin1String(".ldb"))) {
+#if T9WRITEAPIMAJORVERNUM >= 20
+ qCritical() << "Incompatible T9 Write dictionary" << fileEntry;
+ continue;
+#else
+ srcType = decumaXT9LDB;
+#endif
+ } else if (fileEntry.endsWith(QLatin1String(".phd"))) {
+#if T9WRITEAPIMAJORVERNUM >= 20
+ srcType = decumaPortableHWRDictionary;
+#else
+ qCritical() << "Incompatible T9 Write dictionary" << fileEntry;
+ continue;
+#endif
+ } else {
+ qWarning() << "Incompatible T9 Write dictionary" << fileEntry;
continue;
+ }
dictionary = fileEntry;
break;
@@ -240,201 +359,117 @@ public:
return dictionary;
}
- bool attachDictionary(void *dictionary)
+ bool attachDictionary(const QSharedPointer<T9WriteDictionary> &dictionary)
{
- VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::attachDictionary():" << dictionary;
-
QMutexLocker dictionaryGuard(&dictionaryLock);
Q_ASSERT(decumaSession != 0);
Q_ASSERT(dictionary != 0);
-
- DECUMA_STATUS status = decumaAttachConvertedDictionary(decumaSession, dictionary);
- Q_ASSERT(status == decumaNoError);
+ VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::attachDictionary():" << dictionary->fileName();
+#if T9WRITEAPIMAJORVERNUM >= 20
+ DECUMA_STATUS status = DECUMA_API(AttachDictionary)(decumaSession, dictionary->data(), dictionary->size());
+#else
+ DECUMA_STATUS status = DECUMA_API(AttachConvertedDictionary)(decumaSession, dictionary->data());
+#endif
return status == decumaNoError;
}
- void detachDictionary(void **dictionary)
+ void detachDictionary(const QSharedPointer<T9WriteDictionary> &dictionary)
{
QMutexLocker dictionaryGuard(&dictionaryLock);
- Q_ASSERT(decumaSession != 0);
- if (!dictionary || !*dictionary)
+ if (!dictionary)
return;
- VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::detachDictionary():" << *dictionary;
+ VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::detachDictionary():" << dictionary->fileName();
- DECUMA_STATUS status = decumaDetachDictionary(decumaSession, *dictionary);
- Q_UNUSED(status)
- Q_ASSERT(status == decumaNoError);
- *dictionary = 0;
- }
-
- void destroyConvertedDictionary(void **dictionary)
- {
- QMutexLocker dictionaryGuard(&dictionaryLock);
Q_ASSERT(decumaSession != 0);
- if (!dictionary || !*dictionary)
- return;
-
- VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::destroyConvertedDictionary():" << *dictionary;
-
- DECUMA_STATUS status = decumaDestroyConvertedDictionary(dictionary, &memFuncs);
+ DECUMA_STATUS status = DECUMA_API(DetachDictionary)(decumaSession, dictionary->data());
Q_UNUSED(status)
Q_ASSERT(status == decumaNoError);
- Q_ASSERT(*dictionary == 0);
}
bool setInputMode(const QLocale &locale, InputEngine::InputMode inputMode)
{
+ Q_Q(T9WriteInputMethod);
VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::setInputMode():" << locale << inputMode;
- Q_Q(T9WriteInputMethod);
- DECUMA_UINT32 language = mapToDecumaLanguage(locale);
+ finishRecognition();
+
+ if (!initEngine(mapLocaleToEngineMode(locale)))
+ return false;
+
+ DECUMA_UINT32 language = mapToDecumaLanguage(locale, inputMode);
if (language == DECUMA_LANG_GSMDEFAULT) {
- qWarning() << "Handwriting input does not support the language" << locale.name();
+ qWarning() << "Handwriting is not supported in" << locale.name();
return false;
}
int isLanguageSupported = 0;
- decumaDatabaseIsLanguageSupported(sessionSettings.pStaticDB, language, &isLanguageSupported);
+ DECUMA_API(DatabaseIsLanguageSupported)(sessionSettings.pStaticDB, language, &isLanguageSupported);
if (!isLanguageSupported) {
- qWarning() << "Handwriting input does not support the language" << locale.name();
+ qWarning() << "Handwriting is not supported in" << locale.name();
return false;
}
- finishRecognition();
-
- bool languageChanged = languageCategories.isEmpty() || !languageCategories.contains(language);
+ bool languageChanged = languageCategories.isEmpty() || languageCategories.first() != language;
if (languageChanged) {
languageCategories.clear();
languageCategories.append(language);
- }
- // Choose the symbol categories by input mode, script and input method hints
- bool leftToRightGestures = true;
- Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints();
- symbolCategories.clear();
- switch (inputMode) {
- case InputEngine::Latin:
- if (inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly)) {
- symbolCategories.append(DECUMA_CATEGORY_EMAIL);
- } else if (inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly)) {
- symbolCategories.append(DECUMA_CATEGORY_URL);
- } else {
- bool includeDigits = true;
- bool includeBasicPunctuation = true;
- switch (locale.script()) {
- case QLocale::LatinScript:
- if (language == DECUMA_LANG_EN)
- symbolCategories.append(DECUMA_CATEGORY_ANSI);
- else
- symbolCategories.append(DECUMA_CATEGORY_ISO8859_1);
- break;
-
- case QLocale::CyrillicScript:
- symbolCategories.append(DECUMA_CATEGORY_CYRILLIC);
- break;
-
- case QLocale::GreekScript:
- symbolCategories.append(DECUMA_CATEGORY_GREEK);
- break;
-
- default:
- qWarning() << "Handwriting input does not support the language" << locale.name();
- return false;
- }
-
- if (includeDigits)
- symbolCategories.append(DECUMA_CATEGORY_DIGIT);
-
- if (includeBasicPunctuation) {
- symbolCategories.append(DECUMA_CATEGORY_BASIC_PUNCTUATIONS);
- symbolCategories.append(DECUMA_CATEGORY_CONTRACTION_MARK);
- }
-
- if (language == DECUMA_LANG_ES)
- symbolCategories.append(DECUMA_CATEGORY_SPANISH_PUNCTUATIONS);
- }
- break;
-
- case InputEngine::Numeric:
- symbolCategories.append(DECUMA_CATEGORY_DIGIT);
- if (!inputMethodHints.testFlag(Qt::ImhDigitsOnly))
- symbolCategories.append(DECUMA_CATEGORY_NUM_SUP);
- break;
-
- case InputEngine::Dialable:
- symbolCategories.append(DECUMA_CATEGORY_PHONE_NUMBER);
- break;
-
- default:
- return false;
+ // Add English as secondary language for punctuation
+ if (language == DECUMA_LANG_PRC)
+ languageCategories.append(DECUMA_LANG_EN);
}
- if (leftToRightGestures) {
- symbolCategories.append(DECUMA_CATEGORY_BACKSPACE_STROKE);
- symbolCategories.append(DECUMA_CATEGORY_RETURN_STROKE);
- symbolCategories.append(DECUMA_CATEGORY_WHITESPACE_STROKE);
- }
-
- /* 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.
- */
- {
- QMutexLocker dictionaryGuard(&dictionaryLock);
-
- // Select recognition mode
- // Note: MCR mode is preferred in all cases, since it eliminates the need
- // for recognition timer, thus provides better user experience.
- sessionSettings.recognitionMode = inputMethodHints.testFlag(Qt::ImhHiddenText) ? scrMode : mcrMode;
-
- // Detach previous dictionary if the language is being changed
- // or the recognizer mode is single-character mode
- if ((languageChanged || inputMethodHints.testFlag(Qt::ImhNoPredictiveText) || sessionSettings.recognitionMode == scrMode) && attachedDictionary) {
- detachDictionary(&attachedDictionary);
- }
-
- // Check if a dictionary needs to be loaded
- if (languageChanged || !convertedDictionary) {
- destroyConvertedDictionary(&convertedDictionary);
- dictionaryFileName = findDictionary(":/databases/XT9_LDBs/", locale);
- if (!dictionaryFileName.isEmpty()) {
- if (dictionaryTask.isNull() || dictionaryTask->fileUri != dictionaryFileName) {
- dictionaryTask.reset(new T9WriteDictionaryTask(dictionaryFileName, memFuncs));
- q->connect(dictionaryTask.data(), SIGNAL(completed(QString,void*)),
- SLOT(dictionaryLoadCompleted(QString,void*)), Qt::DirectConnection);
- worker->addTask(dictionaryTask);
- }
- }
- }
+ if (!updateSymbolCategories(language, locale, inputMode))
+ return false;
+ updateRecognitionMode(language, locale, inputMode);
+ updateDictionary(language, locale, languageChanged);
+ 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 : unknownWriting;
- // Attach existing dictionary, if necessary
- if (sessionSettings.recognitionMode == mcrMode && !inputMethodHints.testFlag(Qt::ImhNoPredictiveText) &&
- convertedDictionary && !attachedDictionary) {
- attachDictionary(convertedDictionary);
- attachedDictionary = convertedDictionary;
- }
- }
+ VIRTUALKEYBOARD_DEBUG() << " -> language categories:" << languageCategories;
+ VIRTUALKEYBOARD_DEBUG() << " -> symbol categories:" << symbolCategories;
+ VIRTUALKEYBOARD_DEBUG() << " -> recognition mode:" << sessionSettings.recognitionMode;
// Change session settings
sessionSettings.charSet.pSymbolCategories = symbolCategories.data();
sessionSettings.charSet.nSymbolCategories = symbolCategories.size();
sessionSettings.charSet.pLanguages = languageCategories.data();
sessionSettings.charSet.nLanguages = languageCategories.size();
- DECUMA_STATUS status = decumaChangeSessionSettings(decumaSession, &sessionSettings);
+ DECUMA_STATUS status = DECUMA_API(ChangeSessionSettings)(decumaSession, &sessionSettings);
Q_ASSERT(status == decumaNoError);
- caseFormatter.preferLowercase = inputMethodHints.testFlag(Qt::ImhPreferLowercase);
+ caseFormatter.preferLowercase = q->inputContext()->inputMethodHints().testFlag(Qt::ImhPreferLowercase);
return status == decumaNoError;
}
- DECUMA_UINT32 mapToDecumaLanguage(const QLocale &locale)
+ EngineMode mapLocaleToEngineMode(const QLocale &locale)
+ {
+#ifdef HAVE_T9WRITE_CJK
+ switch (locale.language()) {
+ case QLocale::Chinese: {
+ if (locale.script() == QLocale::TraditionalChineseScript)
+ return locale.country() == QLocale::HongKong ? HongKongChinese : TraditionalChinese;
+ return SimplifiedChinese;
+ break;
+ }
+ default:
+ break;
+ }
+#else
+ Q_UNUSED(locale)
+#endif
+
+#ifdef HAVE_T9WRITE_ALPHABETIC
+ return T9WriteInputMethodPrivate::Alphabetic;
+#else
+ return T9WriteInputMethodPrivate::EngineUninitialized;
+#endif
+ }
+
+ DECUMA_UINT32 mapToDecumaLanguage(const QLocale &locale, InputEngine::InputMode inputMode)
{
static const QLocale::Language maxLanguage = QLocale::Vietnamese;
static const DECUMA_UINT32 languageMap[maxLanguage + 1] = {
@@ -573,22 +608,319 @@ public:
DECUMA_LANG_VI // Vietnamese = 132
};
+ int localeLanguage = locale.language();
if (locale.language() > maxLanguage)
return DECUMA_LANG_GSMDEFAULT;
- DECUMA_UINT32 language = languageMap[locale.language()];
+ DECUMA_UINT32 language = languageMap[localeLanguage];
+ if (language == DECUMA_LANG_PRC) {
+ if (inputMode != InputEngine::ChineseHandwriting)
+ language = DECUMA_LANG_EN;
+ }
return language;
}
+ void updateRecognitionMode(DECUMA_UINT32 language, const QLocale &locale,
+ InputEngine::InputMode inputMode)
+ {
+ Q_Q(T9WriteInputMethod);
+ Q_UNUSED(language)
+ Q_UNUSED(locale)
+ Q_UNUSED(inputMode)
+
+ // Select recognition mode
+ // Note: MCR mode is preferred, as it does not require recognition
+ // timer and provides better user experience.
+ sessionSettings.recognitionMode = mcrMode;
+
+ // Use scrMode with hidden text or with no predictive mode
+ if (inputMode != InputEngine::ChineseHandwriting) {
+ 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,
+ InputEngine::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 InputEngine::Latin:
+ if (inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly)) {
+ symbolCategories.append(DECUMA_CATEGORY_EMAIL);
+ } else if (inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly)) {
+ symbolCategories.append(DECUMA_CATEGORY_URL);
+ } else {
+ bool includeDigits = true;
+ bool includeBasicPunctuation = true;
+ switch (locale.script()) {
+ case QLocale::LatinScript:
+ if (language == DECUMA_LANG_EN)
+ symbolCategories.append(DECUMA_CATEGORY_ANSI);
+ else
+ symbolCategories.append(DECUMA_CATEGORY_ISO8859_1);
+ break;
+
+ case QLocale::CyrillicScript:
+ symbolCategories.append(DECUMA_CATEGORY_CYRILLIC);
+ break;
+
+ case QLocale::GreekScript:
+ symbolCategories.append(DECUMA_CATEGORY_GREEK);
+ break;
+
+ default:
+ qWarning() << "Handwriting is not supported in" << locale.name();
+ return false;
+ }
+
+ if (includeDigits)
+ symbolCategories.append(DECUMA_CATEGORY_DIGIT);
+
+ if (includeBasicPunctuation) {
+ symbolCategories.append(DECUMA_CATEGORY_BASIC_PUNCTUATIONS);
+ symbolCategories.append(DECUMA_CATEGORY_CONTRACTION_MARK);
+ }
+
+ if (language == DECUMA_LANG_ES)
+ symbolCategories.append(DECUMA_CATEGORY_SPANISH_PUNCTUATIONS);
+ }
+ break;
+
+ case InputEngine::Numeric:
+ symbolCategories.append(DECUMA_CATEGORY_DIGIT);
+ if (!inputMethodHints.testFlag(Qt::ImhDigitsOnly))
+ symbolCategories.append(DECUMA_CATEGORY_NUM_SUP);
+ break;
+
+ case InputEngine::Dialable:
+ symbolCategories.append(DECUMA_CATEGORY_PHONE_NUMBER);
+ break;
+
+ default:
+ qWarning() << "Handwriting is not supported in" << locale.name();
+ 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,
+ InputEngine::InputMode inputMode)
+ {
+ Q_UNUSED(language)
+ Q_ASSERT(cjk);
+
+ symbolCategories.clear();
+
+ switch (inputMode) {
+ case InputEngine::Latin:
+ symbolCategories.append(DECUMA_CATEGORY_ANSI);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ symbolCategories.append(DECUMA_CATEGORY_PUNCTUATIONS);
+ break;
+
+ case InputEngine::Numeric:
+ symbolCategories.append(DECUMA_CATEGORY_DIGIT);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ symbolCategories.append(DECUMA_CATEGORY_PUNCTUATIONS);
+ break;
+
+ case InputEngine::Dialable:
+ symbolCategories.append(DECUMA_CATEGORY_DIGIT);
+ symbolCategories.append(DECUMA_CATEGORY_CJK_SYMBOL);
+ break;
+
+ case InputEngine::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);
+ 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:
+ qWarning() << "Chinese handwriting is not supported in" << locale.name();
+ return false;
+ }
+ 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.
+ */
+ QMutexLocker 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 (QLocale tryLocale : decumaLocales) {
+ dictionaryFileName = findDictionary(defaultDictionaryDbPath, tryLocale, dictionaryInfo.srcType);
+ if (!dictionaryFileName.isEmpty()) {
+ decumaLocale = tryLocale;
+ break;
+ }
+ }
+ if (!dictionaryFileName.isEmpty()) {
+ if (dictionaryTask.isNull() || dictionaryTask->dictionaryFileName != dictionaryFileName) {
+ VIRTUALKEYBOARD_DEBUG() << " -> load dictionary:" << dictionaryFileName;
+
+ bool convertDictionary = true;
+#if defined(HAVE_T9WRITE_CJK) && T9WRITEAPIMAJORVERNUM >= 20
+ // Chinese dictionary cannot be converted (PHD)
+ if (dictionaryInfo.srcType == decumaPortableHWRDictionary && decumaLocale.language() == QLocale::Chinese)
+ convertDictionary = false;
+#endif
+
+ QSharedPointer<T9WriteDictionary> newDictionary(new T9WriteDictionary(decumaSession, memFuncs, cjk));
+ dictionaryTask.reset(new T9WriteDictionaryTask(newDictionary, dictionaryFileName, convertDictionary, dictionaryInfo));
+
+ QObject::connect(dictionaryTask.data(), &T9WriteDictionaryTask::completed,
+ q, &T9WriteInputMethod::dictionaryLoadCompleted, Qt::DirectConnection);
+ worker->addTask(dictionaryTask);
+ }
+ }
+ }
+
+ // Attach existing dictionary, if available
+ if (sessionSettings.recognitionMode == mcrMode && !inputMethodHints.testFlag(Qt::ImhNoPredictiveText) &&
+ loadedDictionary && !attachedDictionary) {
+ if (attachDictionary(loadedDictionary))
+ attachedDictionary = loadedDictionary;
+ }
+ }
+
+ QByteArray getContext(InputEngine::PatternRecognitionMode patternRecognitionMode,
+ const QVariantMap &traceCaptureDeviceInfo,
+ const QVariantMap &traceScreenInfo) const
+ {
+ QCryptographicHash hash(QCryptographicHash::Md5);
+
+ hash.addData((const char *)&patternRecognitionMode, sizeof(patternRecognitionMode));
+
+ QByteArray mapData;
+ QDataStream ds(&mapData, QIODevice::WriteOnly);
+ ds << traceCaptureDeviceInfo;
+ ds << traceScreenInfo;
+ hash.addData(mapData);
+
+ return hash.result();
+ }
+
+ void setContext(InputEngine::PatternRecognitionMode patternRecognitionMode,
+ const QVariantMap &traceCaptureDeviceInfo,
+ const QVariantMap &traceScreenInfo)
+ {
+ QByteArray context = getContext(patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo);
+ if (context == currentContext)
+ return;
+ currentContext = context;
+
+ VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::setContext():" << QString(context.toHex());
+
+ finishRecognition();
+
+ const int dpi = traceCaptureDeviceInfo.value("dpi", 96).toInt();
+ static const int INSTANT_GESTURE_WIDTH_THRESHOLD_MM = 25;
+ static const int INSTANT_GESTURE_HEIGHT_THRESHOLD_MM = 25;
+ instantGestureSettings.widthThreshold = INSTANT_GESTURE_WIDTH_THRESHOLD_MM / 25.4 * dpi;
+ instantGestureSettings.heightThreshold = INSTANT_GESTURE_HEIGHT_THRESHOLD_MM / 25.4 * dpi;
+
+ gestureRecognizer.setDpi(dpi);
+
+ QVariantList horizontalRulers(traceScreenInfo.value("horizontalRulers", QVariantList()).toList());
+ if (horizontalRulers.count() >= 2) {
+ sessionSettings.baseline = horizontalRulers.last().toInt();
+ sessionSettings.helpline = 0;
+ sessionSettings.topline = horizontalRulers.first().toInt();
+ sessionSettings.supportLineSet = baselineAndTopline;
+ } else {
+ sessionSettings.baseline = 0;
+ sessionSettings.helpline = 0;
+ sessionSettings.topline = 0;
+ sessionSettings.supportLineSet = baselineAndTopline;
+ }
+
+ DECUMA_STATUS status = DECUMA_API(ChangeSessionSettings)(decumaSession, &sessionSettings);
+ Q_ASSERT(status == decumaNoError);
+ }
+
Trace *traceBegin(int traceId, InputEngine::PatternRecognitionMode patternRecognitionMode,
const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo)
{
Q_UNUSED(traceId)
Q_UNUSED(patternRecognitionMode)
- Q_UNUSED(traceCaptureDeviceInfo)
Q_UNUSED(traceScreenInfo)
+ if (!worker)
+ return 0;
+
stopResultTimer();
// Dictionary must be completed before the arc addition can begin
@@ -606,30 +938,34 @@ public:
recognitionTask.reset();
}
- const int dpi = traceCaptureDeviceInfo.value("dpi", 96).toInt();
- static const int INSTANT_GESTURE_WIDTH_THRESHOLD_MM = 25;
- static const int INSTANT_GESTURE_HEIGHT_THRESHOLD_MM = 25;
- instantGestureSettings.widthThreshold = INSTANT_GESTURE_WIDTH_THRESHOLD_MM / 25.4 * dpi;
- instantGestureSettings.heightThreshold = INSTANT_GESTURE_HEIGHT_THRESHOLD_MM / 25.4 * dpi;
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ if (!unipenTrace)
+ unipenTrace.reset(new UnipenTrace(traceCaptureDeviceInfo, traceScreenInfo));
+#endif
+
+ setContext(patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo);
DECUMA_STATUS status;
if (!arcAdditionStarted) {
- status = decumaBeginArcAddition(decumaSession);
+ status = DECUMA_API(BeginArcAddition)(decumaSession);
Q_ASSERT(status == decumaNoError);
arcAdditionStarted = true;
}
DECUMA_UINT32 arcID = (DECUMA_UINT32)traceId;
- status = decumaStartNewArc(decumaSession, arcID);
+ status = DECUMA_API(StartNewArc)(decumaSession, arcID);
Q_ASSERT(status == decumaNoError);
if (status != decumaNoError) {
- decumaEndArcAddition(decumaSession);
+ DECUMA_API(EndArcAddition)(decumaSession);
arcAdditionStarted = false;
return NULL;
}
Trace *trace = new Trace();
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ trace->setChannels(QStringList("t"));
+#endif
traceList.append(trace);
return trace;
@@ -638,7 +974,7 @@ public:
void traceEnd(Trace *trace)
{
if (trace->isCanceled()) {
- decumaCancelArc(decumaSession, trace->traceId());
+ DECUMA_API(CancelArc)(decumaSession, trace->traceId());
traceList.removeOne(trace);
delete trace;
} else {
@@ -646,10 +982,16 @@ public:
}
if (!traceList.isEmpty()) {
Q_ASSERT(arcAdditionStarted);
- if (countActiveTraces() == 0)
+ if (countActiveTraces() == 0) {
restartRecognition();
+ if (cjk) {
+ // For some reason gestures don't seem to work in CJK mode
+ // Using our own gesture recognizer as fallback
+ handleGesture();
+ }
+ }
} else if (arcAdditionStarted) {
- decumaEndArcAddition(decumaSession);
+ DECUMA_API(EndArcAddition)(decumaSession);
arcAdditionStarted = false;
}
}
@@ -675,17 +1017,26 @@ public:
Q_ASSERT(decumaSession != 0);
const QVariantList points = trace->points();
+ Q_ASSERT(!points.isEmpty());
DECUMA_UINT32 arcID = (DECUMA_UINT32)trace->traceId();
DECUMA_STATUS status;
for (const QVariant &p : points) {
const QPoint pt(p.toPointF().toPoint());
- status = decumaAddPoint(decumaSession, (DECUMA_COORD)pt.x(),(DECUMA_COORD)pt.y(), arcID);
- Q_ASSERT(status == decumaNoError);
+ status = DECUMA_API(AddPoint)(decumaSession, (DECUMA_COORD)pt.x(),(DECUMA_COORD)pt.y(), arcID);
+ if (status != decumaNoError) {
+ VIRTUALKEYBOARD_DEBUG() << "decumaAddPoint failed" << status;
+ finishRecognition();
+ return;
+ }
}
- status = decumaCommitArc(decumaSession, arcID);
- Q_ASSERT(status == decumaNoError);
+ status = DECUMA_API(CommitArc)(decumaSession, arcID);
+ if (status != decumaNoError) {
+ VIRTUALKEYBOARD_DEBUG() << "decumaCommitArc failed" << status;
+ finishRecognition();
+ return;
+ }
}
void noteSelected(int index)
@@ -696,7 +1047,7 @@ public:
VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::noteSelected():" << index;
Q_ASSERT(index >= 0 && index < wordCandidatesHwrResultIndex.length());
int resultIndex = wordCandidatesHwrResultIndex[index];
- DECUMA_STATUS status = decumaNoteSelectedCandidate(decumaSession, resultIndex);
+ DECUMA_STATUS status = DECUMA_API(NoteSelectedCandidate)(decumaSession, resultIndex);
Q_UNUSED(status)
Q_ASSERT(status == decumaNoError);
}
@@ -723,6 +1074,8 @@ public:
bool finishRecognition(bool emitSelectionListChanged = true)
{
VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::finishRecognition()";
+ if (!worker)
+ return false;
bool result = !traceList.isEmpty();
@@ -742,7 +1095,7 @@ public:
}
if (arcAdditionStarted) {
- decumaEndArcAddition(decumaSession);
+ DECUMA_API(EndArcAddition)(decumaSession);
arcAdditionStarted = false;
}
@@ -762,11 +1115,18 @@ public:
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 == mcrMode && wordCandidates.isEmpty()) {
finishRecognition();
return false;
@@ -783,6 +1143,25 @@ public:
index = index >= 0 ? index : activeWordIndex;
noteSelected(index);
QString finalWord = wordCandidates.at(index);
+
+#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 == InputEngine::Upper))) {
+ QStringList homeLocations = QStandardPaths::standardLocations(QStandardPaths::HomeLocation);
+ if (!homeLocations.isEmpty()) {
+ unipenTrace->setDirectory(QStringLiteral("%1/%2").arg(homeLocations.at(0)).arg("VIRTUAL_KEYBOARD_TRACES"));
+ unipenTrace->record(traceList);
+ unipenTrace->save(ch.unicode(), 100);
+ }
+ }
+ }
+ }
+#endif
+
finishRecognition();
q->inputContext()->commit(finalWord);
} else if (sessionSettings.recognitionMode == scrMode) {
@@ -865,8 +1244,11 @@ public:
bool wordCandidatesChanged = wordCandidates != newWordCandidates;
+#ifndef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
// Delete trace history
- if (sessionSettings.recognitionMode == mcrMode && !symbolStrokes.isEmpty()) {
+ const InputEngine::InputMode inputMode = q->inputEngine()->inputMode();
+ if (sessionSettings.recognitionMode == mcrMode && !symbolStrokes.isEmpty() &&
+ inputMode != InputEngine::ChineseHandwriting) {
int activeTraces = symbolStrokes.at(symbolStrokes.count() - 1).toInt();
if (symbolStrokes.count() > 1)
activeTraces += symbolStrokes.at(symbolStrokes.count() - 2).toInt();
@@ -874,7 +1256,14 @@ public:
delete traceList.takeFirst();
}
- // Look for a gesture at the end of first result
+ // Enforce hard limit for number of traces
+ if (traceList.count() >= traceListHardLimit) {
+ VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::processResult(): Clearing traces (hard limit):" << traceList.count();
+ clearTraces();
+ }
+#endif
+
+ // Find a gesture at the end of the first result
if (!gesture.isEmpty()) {
DECUMA_UNICODE gestureSymbol = gesture.at(0).unicode();
@@ -892,8 +1281,8 @@ public:
break;
default:
- finishRecognition();
ic->commit(ic->preeditText());
+ finishRecognition();
break;
}
@@ -920,6 +1309,85 @@ public:
}
}
+ bool handleGesture()
+ {
+ if (countActiveTraces() > 0)
+ return false;
+
+ QVariantMap gesture(gestureRecognizer.recognize(traceList.mid(traceList.length() - 1, 1)));
+ if (gesture.isEmpty())
+ return false;
+
+ VIRTUALKEYBOARD_DEBUG() << "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 >= instantGestureSettings.widthThreshold) {
+
+ Q_Q(T9WriteInputMethod);
+ InputContext *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 InputEngine::InputMode inputMode = q->inputEngine()->inputMode();
+ if (inputMode != InputEngine::ChineseHandwriting) {
+ if (swipeAngle <= SWIPE_ANGLE_THRESHOLD || swipeAngle >= 360 - SWIPE_ANGLE_THRESHOLD) {
+ if (swipeTouchCount == 1) {
+ // Single swipe: space
+ ic->inputEngine()->virtualKeyClick(Qt::Key_Space, QString(" "), 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(InputEngine::Numeric);
+ int indexOfDialableInputMode = inputModes.indexOf(InputEngine::Dialable);
+ if (indexOfNumericInputMode != -1 && indexOfDialableInputMode != -1)
+ inputModes.removeAt(inputMode != InputEngine::Dialable ?
+ indexOfDialableInputMode :
+ indexOfNumericInputMode);
+ if (inputModes.count() > 1) {
+ int inputModeIndex = inputModes.indexOf((int)inputMode) + 1;
+ if (inputModeIndex >= inputModes.count())
+ inputModeIndex = 0;
+ ic->inputEngine()->setInputMode((InputEngine::InputMode)inputModes.at(inputModeIndex));
+ }
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
bool isValidInputChar(const QChar &c) const
{
if (c.isLetterOrNumber())
@@ -948,17 +1416,23 @@ public:
T9WriteInputMethod *q_ptr;
static const DECUMA_MEM_FUNCTIONS memFuncs;
+ bool cjk;
+ EngineMode engineMode;
+ QByteArray currentContext;
DECUMA_SESSION_SETTINGS sessionSettings;
DECUMA_INSTANT_GESTURE_SETTINGS instantGestureSettings;
+ QString defaultHwrDbPath;
+ QString defaultDictionaryDbPath;
QFile hwrDbFile;
QVector<DECUMA_UINT32> languageCategories;
QVector<DECUMA_UINT32> symbolCategories;
QScopedPointer<T9WriteWorker> worker;
QList<Trace *> traceList;
+ int traceListHardLimit;
QMutex dictionaryLock;
QString dictionaryFileName;
- void *convertedDictionary;
- void *attachedDictionary;
+ QSharedPointer<T9WriteDictionary> loadedDictionary;
+ QSharedPointer<T9WriteDictionary> attachedDictionary;
QSharedPointer<T9WriteDictionaryTask> dictionaryTask;
QSharedPointer<T9WriteRecognitionTask> recognitionTask;
int resultId;
@@ -974,6 +1448,10 @@ public:
bool ignoreUpdate;
InputEngine::TextCase textCase;
T9WriteCaseFormatter caseFormatter;
+ HandwritingGestureRecognizer gestureRecognizer;
+#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ QScopedPointer<UnipenTrace> unipenTrace;
+#endif
};
const DECUMA_MEM_FUNCTIONS T9WriteInputMethodPrivate::memFuncs = {
@@ -991,8 +1469,6 @@ const DECUMA_MEM_FUNCTIONS T9WriteInputMethodPrivate::memFuncs = {
T9WriteInputMethod::T9WriteInputMethod(QObject *parent) :
AbstractInputMethod(*new T9WriteInputMethodPrivate(this), parent)
{
- Q_D(T9WriteInputMethod);
- d->initEngine();
}
T9WriteInputMethod::~T9WriteInputMethod()
@@ -1003,18 +1479,58 @@ T9WriteInputMethod::~T9WriteInputMethod()
QList<InputEngine::InputMode> T9WriteInputMethod::inputModes(const QString &locale)
{
- Q_UNUSED(locale)
- return QList<InputEngine::InputMode>()
- << InputEngine::Latin
- << InputEngine::Numeric
- << InputEngine::Dialable;
+ Q_D(T9WriteInputMethod);
+ QList<InputEngine::InputMode> availableInputModes;
+ const Qt::InputMethodHints inputMethodHints(inputContext()->inputMethodHints());
+ T9WriteInputMethodPrivate::EngineMode mode = d->mapLocaleToEngineMode(QLocale(locale));
+
+ // Add primary input mode
+ switch (mode) {
+#ifdef HAVE_T9WRITE_ALPHABETIC
+ case T9WriteInputMethodPrivate::Alphabetic:
+ if (d->findHwrDb(T9WriteInputMethodPrivate::Alphabetic, d->defaultHwrDbPath).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly)))
+ availableInputModes.append(InputEngine::Latin);
+ break;
+#endif
+#ifdef HAVE_T9WRITE_CJK
+ case T9WriteInputMethodPrivate::SimplifiedChinese:
+ case T9WriteInputMethodPrivate::TraditionalChinese:
+ case T9WriteInputMethodPrivate::HongKongChinese:
+ if (d->findHwrDb(mode, d->defaultHwrDbPath).isEmpty())
+ return availableInputModes;
+ if (!(inputMethodHints & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly | Qt::ImhLatinOnly)))
+ availableInputModes.append(InputEngine::ChineseHandwriting);
+ break;
+#endif
+ default:
+ return availableInputModes;
+ }
+
+ // Add exclusive input modes
+ if (inputMethodHints.testFlag(Qt::ImhDialableCharactersOnly) || inputMethodHints.testFlag(Qt::ImhDigitsOnly)) {
+ availableInputModes.append(InputEngine::Dialable);
+ } else if (inputMethodHints.testFlag(Qt::ImhFormattedNumbersOnly)) {
+ availableInputModes.append(InputEngine::Numeric);
+ } else if (inputMethodHints.testFlag(Qt::ImhLatinOnly)) {
+ availableInputModes.append(InputEngine::Latin);
+ } else {
+ // Add other input modes
+ Q_ASSERT(!availableInputModes.isEmpty());
+ if (!availableInputModes.contains(InputEngine::Latin))
+ availableInputModes.append(InputEngine::Latin);
+ availableInputModes.append(InputEngine::Numeric);
+ }
+
+ return availableInputModes;
}
bool T9WriteInputMethod::setInputMode(const QString &locale, InputEngine::InputMode inputMode)
{
Q_D(T9WriteInputMethod);
d->select();
- return d->setInputMode(locale, inputMode);
+ return d->setInputMode(QLocale(locale), inputMode);
}
bool T9WriteInputMethod::setTextCase(InputEngine::TextCase textCase)
@@ -1045,6 +1561,13 @@ bool T9WriteInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::Keyboard
if (preeditText.length() > 1) {
preeditText.chop(1);
ic->setPreeditText(preeditText);
+ // WA: T9Write CJK may crash in some cases with long stringStart.
+ // Therefore we commit the current input and finish the recognition.
+ if (d->cjk) {
+ ic->commit();
+ d->finishRecognition();
+ return true;
+ }
d->caseFormatter.ensureLength(preeditText.length(), d->textCase);
T9WriteCaseFormatter caseFormatter(d->caseFormatter);
d->finishRecognition(false);
@@ -1182,11 +1705,13 @@ bool T9WriteInputMethod::reselect(int cursorPosition, const InputEngine::Reselec
if (!ic)
return false;
+ const InputEngine::InputMode inputMode = inputEngine()->inputMode();
+ const int maxLength = inputMode == InputEngine::ChineseHandwriting ? 0 : 32;
const QString surroundingText = ic->surroundingText();
int replaceFrom = 0;
if (reselectFlags.testFlag(InputEngine::WordBeforeCursor)) {
- for (int i = cursorPosition - 1; i >= 0; --i) {
+ for (int i = cursorPosition - 1; i >= 0 && d->stringStart.length() < maxLength; --i) {
QChar c = surroundingText.at(i);
if (!d->isValidInputChar(c))
break;
@@ -1206,7 +1731,7 @@ bool T9WriteInputMethod::reselect(int cursorPosition, const InputEngine::Reselec
}
if (reselectFlags.testFlag(InputEngine::WordAfterCursor)) {
- for (int i = cursorPosition; i < surroundingText.length(); ++i) {
+ for (int i = cursorPosition; i < surroundingText.length() && d->stringStart.length() < maxLength; ++i) {
QChar c = surroundingText.at(i);
if (!d->isValidInputChar(c))
break;
@@ -1224,7 +1749,7 @@ bool T9WriteInputMethod::reselect(int cursorPosition, const InputEngine::Reselec
if (d->stringStart.isEmpty())
return false;
- if (reselectFlags.testFlag(InputEngine::WordAtCursor) && replaceFrom == -d->stringStart.length()) {
+ if (reselectFlags.testFlag(InputEngine::WordAtCursor) && replaceFrom == -d->stringStart.length() && d->stringStart.length() < maxLength) {
d->stringStart.clear();
return false;
}
@@ -1258,35 +1783,38 @@ void T9WriteInputMethod::timerEvent(QTimerEvent *timerEvent)
if (timerId == d->resultTimer) {
if (d->sessionSettings.recognitionMode == mcrMode) {
d->stopResultTimer();
- d->clearTraces();
+#ifndef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT
+ const InputEngine::InputMode inputMode = inputEngine()->inputMode();
+ if (inputMode != InputEngine::ChineseHandwriting)
+ d->clearTraces();
+#endif
} else if (d->sessionSettings.recognitionMode == scrMode) {
d->select();
}
}
}
-void T9WriteInputMethod::dictionaryLoadCompleted(const QString &fileUri, void *dictionary)
+void T9WriteInputMethod::dictionaryLoadCompleted(QSharedPointer<T9WriteDictionary> dictionary)
{
Q_D(T9WriteInputMethod);
// Note: This method is called in worker thread context
QMutexLocker dictionaryGuard(&d->dictionaryLock);
- VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethod::dictionaryLoadCompleted():" << fileUri << dictionary;
-
if (!dictionary)
return;
+ VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethod::dictionaryLoadCompleted():"
+ << dictionary->fileName() << dictionary->data() << dictionary->size();
+
InputContext *ic = inputContext();
- if (ic && fileUri == d->dictionaryFileName) {
- d->convertedDictionary = dictionary;
+ if (ic && dictionary->fileName() == d->dictionaryFileName) {
+ d->loadedDictionary = dictionary;
if (d->sessionSettings.recognitionMode == mcrMode &&
!ic->inputMethodHints().testFlag(Qt::ImhNoPredictiveText) &&
!d->attachedDictionary) {
- d->attachDictionary(d->convertedDictionary);
- d->attachedDictionary = d->convertedDictionary;
+ if (d->attachDictionary(d->loadedDictionary))
+ d->attachedDictionary = d->loadedDictionary;
}
- } else {
- d->destroyConvertedDictionary(&dictionary);
}
}
@@ -1315,4 +1843,10 @@ void T9WriteInputMethod::resultsAvailable(const QVariantList &resultList)
d->resultsAvailable(resultList);
}
+void T9WriteInputMethod::recognitionError(int status)
+{
+ VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethod::recognitionError():" << status;
+ reset();
+}
+
} // namespace QtVirtualKeyboard
diff --git a/src/virtualkeyboard/t9writeinputmethod.h b/src/virtualkeyboard/t9writeinputmethod.h
index 9b87ebdf..173abd99 100644
--- a/src/virtualkeyboard/t9writeinputmethod.h
+++ b/src/virtualkeyboard/t9writeinputmethod.h
@@ -31,10 +31,12 @@
#define T9WRITEINPUTMETHOD_H
#include "abstractinputmethod.h"
+#include <QSharedPointer>
namespace QtVirtualKeyboard {
class T9WriteInputMethodPrivate;
+class T9WriteDictionary;
class T9WriteInputMethod : public AbstractInputMethod
{
@@ -70,8 +72,9 @@ protected:
void timerEvent(QTimerEvent *timerEvent);
protected slots:
- void dictionaryLoadCompleted(const QString &fileUri, void *dictionary);
+ void dictionaryLoadCompleted(QSharedPointer<T9WriteDictionary> dictionary);
void resultsAvailable(const QVariantList &resultList);
+ void recognitionError(int status);
};
}
diff --git a/src/virtualkeyboard/t9writeworker.cpp b/src/virtualkeyboard/t9writeworker.cpp
index 733033ce..c2f33124 100644
--- a/src/virtualkeyboard/t9writeworker.cpp
+++ b/src/virtualkeyboard/t9writeworker.cpp
@@ -58,10 +58,14 @@ void T9WriteTask::wait()
\internal
*/
-T9WriteDictionaryTask::T9WriteDictionaryTask(const QString &fileUri,
- const DECUMA_MEM_FUNCTIONS &memFuncs) :
- fileUri(fileUri),
- memFuncs(memFuncs)
+T9WriteDictionaryTask::T9WriteDictionaryTask(QSharedPointer<T9WriteDictionary> dictionary,
+ const QString &dictionaryFileName,
+ bool convertDictionary,
+ const DECUMA_SRC_DICTIONARY_INFO &dictionaryInfo) :
+ dictionary(dictionary),
+ dictionaryFileName(dictionaryFileName),
+ convertDictionary(convertDictionary),
+ dictionaryInfo(dictionaryInfo)
{
}
@@ -74,33 +78,19 @@ void T9WriteDictionaryTask::run()
perf.start();
#endif
- void *dictionary = 0;
-
- QFile dictionaryFile(fileUri);
- if (dictionaryFile.open(QIODevice::ReadOnly)) {
- uchar *dictionaryData = dictionaryFile.map(0, dictionaryFile.size(), QFile::NoOptions);
- if (dictionaryData) {
-
- DECUMA_SRC_DICTIONARY_INFO dictionaryInfo;
- memset(&dictionaryInfo, 0, sizeof(dictionaryInfo));
- dictionaryInfo.srcType = decumaXT9LDB;
- DECUMA_UINT32 dictionarySize = 0;
- DECUMA_STATUS status = decumaConvertDictionary(&dictionary, dictionaryData, dictionaryFile.size(), &dictionaryInfo, &dictionarySize, &memFuncs);
- Q_UNUSED(status)
- Q_ASSERT(status == decumaNoError);
- dictionaryFile.unmap(dictionaryData);
- } else {
- qWarning() << "Could not map dictionary file" << fileUri;
- }
- } else {
- qWarning() << "Could not open dictionary file" << fileUri;
+ bool result = false;
+ if (dictionary) {
+ result = dictionary->load(dictionaryFileName);
+ if (result && convertDictionary)
+ result = dictionary->convert(dictionaryInfo);
}
#ifdef QT_VIRTUALKEYBOARD_DEBUG
VIRTUALKEYBOARD_DEBUG() << "T9WriteDictionaryTask::run(): time:" << perf.elapsed() << "ms";
#endif
- emit completed(fileUri, dictionary);
+ if (result)
+ emit completed(dictionary);
}
/*!
@@ -109,6 +99,7 @@ void T9WriteDictionaryTask::run()
*/
T9WriteRecognitionResult::T9WriteRecognitionResult(int id, int maxResults, int maxCharsPerWord) :
+ status(decumaNoError),
numResults(0),
instantGesture(0),
id(id),
@@ -176,16 +167,18 @@ void T9WriteRecognitionTask::run()
perf.start();
#endif
- DECUMA_STATUS status = decumaIndicateInstantGesture(decumaSession, &result->instantGesture, &instantGestureSettings);
- Q_ASSERT(status == decumaNoError);
+ DECUMA_STATUS status;
+ if (!cjk) {
+ status = DECUMA_API(IndicateInstantGesture)(decumaSession, &result->instantGesture, &instantGestureSettings);
+ Q_ASSERT(status == decumaNoError);
+ }
DECUMA_INTERRUPT_FUNCTIONS interruptFunctions;
interruptFunctions.pShouldAbortRecognize = shouldAbortRecognize;
interruptFunctions.pUserData = (void *)this;
- status = decumaRecognize(decumaSession, result->results.data(), result->results.size(), &result->numResults, result->maxCharsPerWord, &recSettings, &interruptFunctions);
- if (status == decumaAbortRecognitionUnsupported)
- status = decumaRecognize(decumaSession, result->results.data(), result->results.size(), &result->numResults, result->maxCharsPerWord, &recSettings, NULL);
- Q_ASSERT(status == decumaNoError);
+ result->status = DECUMA_API(Recognize)(decumaSession, result->results.data(), result->results.size(), &result->numResults, result->maxCharsPerWord, &recSettings, &interruptFunctions);
+ if (result->status == decumaAbortRecognitionUnsupported)
+ result->status = DECUMA_API(Recognize)(decumaSession, result->results.data(), result->results.size(), &result->numResults, result->maxCharsPerWord, &recSettings, NULL);
QStringList resultList;
QString gesture;
@@ -261,6 +254,11 @@ 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++)
{
@@ -306,12 +304,13 @@ void T9WriteRecognitionResultsTask::run()
\internal
*/
-T9WriteWorker::T9WriteWorker(DECUMA_SESSION *decumaSession, QObject *parent) :
+T9WriteWorker::T9WriteWorker(DECUMA_SESSION *decumaSession, const bool cjk, QObject *parent) :
QThread(parent),
taskSema(),
taskLock(),
decumaSession(decumaSession),
- abort(false)
+ abort(false),
+ cjk(cjk)
{
}
@@ -369,6 +368,7 @@ void T9WriteWorker::run()
}
if (currentTask) {
currentTask->decumaSession = decumaSession;
+ currentTask->cjk = cjk;
currentTask->run();
currentTask->runSema.release();
}
diff --git a/src/virtualkeyboard/t9writeworker.h b/src/virtualkeyboard/t9writeworker.h
index 23a4cf02..5dc8c0d9 100644
--- a/src/virtualkeyboard/t9writeworker.h
+++ b/src/virtualkeyboard/t9writeworker.h
@@ -41,7 +41,8 @@
#include <QMap>
#include <QVector>
-#include "decuma_hwr.h"
+#include "t9write.h"
+#include "t9writedictionary.h"
namespace QtVirtualKeyboard {
@@ -59,6 +60,7 @@ public:
protected:
DECUMA_SESSION *decumaSession;
+ bool cjk;
private:
QSemaphore runSema;
@@ -68,16 +70,20 @@ class T9WriteDictionaryTask : public T9WriteTask
{
Q_OBJECT
public:
- explicit T9WriteDictionaryTask(const QString &fileUri,
- const DECUMA_MEM_FUNCTIONS &memFuncs);
+ explicit T9WriteDictionaryTask(QSharedPointer<T9WriteDictionary> dictionary,
+ const QString &dictionaryFileName,
+ bool convertDictionary,
+ const DECUMA_SRC_DICTIONARY_INFO &dictionaryInfo);
void run();
- const QString fileUri;
- const DECUMA_MEM_FUNCTIONS &memFuncs;
+ QSharedPointer<T9WriteDictionary> dictionary;
+ const QString dictionaryFileName;
+ bool convertDictionary;
+ const DECUMA_SRC_DICTIONARY_INFO dictionaryInfo;
signals:
- void completed(const QString &fileUri, void *dictionary);
+ void completed(QSharedPointer<T9WriteDictionary> dictionary);
};
class T9WriteRecognitionResult
@@ -87,12 +93,14 @@ class T9WriteRecognitionResult
public:
explicit T9WriteRecognitionResult(int id, int maxResults, int maxCharsPerWord);
+ DECUMA_STATUS status;
QVector<DECUMA_HWR_RESULT> results;
DECUMA_UINT16 numResults;
int instantGesture;
const int id;
const int maxResults;
const int maxCharsPerWord;
+
private:
QVector<DECUMA_UNICODE> _chars;
QVector<DECUMA_INT16> _symbolChars;
@@ -135,6 +143,7 @@ public:
signals:
void resultsAvailable(const QVariantList &resultList);
+ void recognitionError(int status);
private:
QSharedPointer<T9WriteRecognitionResult> result;
@@ -144,7 +153,7 @@ class T9WriteWorker : public QThread
{
Q_OBJECT
public:
- explicit T9WriteWorker(DECUMA_SESSION *decumaSession, QObject *parent = 0);
+ explicit T9WriteWorker(DECUMA_SESSION *decumaSession, const bool cjk, QObject *parent = 0);
~T9WriteWorker();
void addTask(QSharedPointer<T9WriteTask> task);
@@ -176,6 +185,7 @@ private:
QMutex taskLock;
DECUMA_SESSION *decumaSession;
QAtomicInteger<bool> abort;
+ const bool cjk;
};
} // namespace QtVirtualKeyboard
diff --git a/src/virtualkeyboard/virtualkeyboard.pro b/src/virtualkeyboard/virtualkeyboard.pro
index 25b1586f..e16eaba9 100644
--- a/src/virtualkeyboard/virtualkeyboard.pro
+++ b/src/virtualkeyboard/virtualkeyboard.pro
@@ -74,8 +74,9 @@ LAYOUT_FILES += \
contains(CONFIG, lang-en.*) {
LAYOUT_FILES += \
content/layouts/en_GB/main.qml \
- content/layouts/en_GB/handwriting.qml \
content/layouts/en_GB/symbols.qml
+t9write-alphabetic|lipi-toolkit: LAYOUT_FILES += \
+ content/layouts/en_GB/handwriting.qml
}
contains(CONFIG, lang-ar.*) {
LAYOUT_FILES += \
@@ -88,21 +89,21 @@ contains(CONFIG, lang-da.*) {
LAYOUT_FILES += \
content/layouts/da_DK/main.qml \
content/layouts/da_DK/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/da_DK/handwriting.qml
}
contains(CONFIG, lang-de.*) {
LAYOUT_FILES += \
content/layouts/de_DE/main.qml \
content/layouts/de_DE/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/de_DE/handwriting.qml
}
contains(CONFIG, lang-es.*) {
LAYOUT_FILES += \
content/layouts/es_ES/main.qml \
content/layouts/es_ES/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/es_ES/handwriting.qml
}
contains(CONFIG, lang-fa.*) {
@@ -116,14 +117,14 @@ contains(CONFIG, lang-fi.*) {
LAYOUT_FILES += \
content/layouts/fi_FI/main.qml \
content/layouts/fi_FI/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/fi_FI/handwriting.qml
}
contains(CONFIG, lang-fr.*) {
LAYOUT_FILES += \
content/layouts/fr_FR/main.qml \
content/layouts/fr_FR/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/fr_FR/handwriting.qml
}
contains(CONFIG, lang-hi.*) {
@@ -135,7 +136,7 @@ contains(CONFIG, lang-it.*) {
LAYOUT_FILES += \
content/layouts/it_IT/main.qml \
content/layouts/it_IT/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/it_IT/handwriting.qml
}
contains(CONFIG, lang-ja.*) {
@@ -152,48 +153,50 @@ contains(CONFIG, lang-nb.*) {
LAYOUT_FILES += \
content/layouts/nb_NO/main.qml \
content/layouts/nb_NO/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/nb_NO/handwriting.qml
}
contains(CONFIG, lang-pl.*) {
LAYOUT_FILES += \
content/layouts/pl_PL/main.qml \
content/layouts/pl_PL/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/pl_PL/handwriting.qml
}
contains(CONFIG, lang-pt.*) {
LAYOUT_FILES += \
content/layouts/pt_PT/main.qml \
content/layouts/pt_PT/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/pt_PT/handwriting.qml
}
contains(CONFIG, lang-ro.*) {
LAYOUT_FILES += \
content/layouts/ro_RO/main.qml \
content/layouts/ro_RO/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/ro_RO/handwriting.qml
}
contains(CONFIG, lang-ru.*) {
LAYOUT_FILES += \
content/layouts/ru_RU/main.qml \
content/layouts/ru_RU/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/ru_RU/handwriting.qml
}
contains(CONFIG, lang-sv.*) {
LAYOUT_FILES += \
content/layouts/sv_SE/main.qml \
content/layouts/sv_SE/symbols.qml
-t9write: LAYOUT_FILES += \
+t9write-alphabetic: LAYOUT_FILES += \
content/layouts/sv_SE/handwriting.qml
}
contains(CONFIG, lang-zh(_CN)?) {
LAYOUT_FILES += \
content/layouts/zh_CN/main.qml \
content/layouts/zh_CN/symbols.qml
+t9write-cjk: LAYOUT_FILES += \
+ content/layouts/zh_CN/handwriting.qml
}
contains(CONFIG, lang-zh(_TW)?) {
LAYOUT_FILES += \
@@ -340,18 +343,41 @@ lipi-toolkit {
t9write {
include(3rdparty/t9write/t9write-build.pri)
equals(T9WRITE_FOUND, 0): \
- error("T9Write SDK could not be found. Please make sure you have extracted" \
- "the contents of the T9Write SDK to $$PWD/3rdparty/t9write")
+ error("T9Write SDK could not be found. For more information, see" \
+ "the documentation in Building Qt Virtual Keyboard")
SOURCES += \
t9writeinputmethod.cpp \
- t9writeworker.cpp
+ t9writeworker.cpp \
+ t9writedictionary.cpp
HEADERS += \
t9writeinputmethod.h \
- t9writeworker.h
+ t9writeworker.h \
+ t9writedictionary.h \
+ t9write.h
DEFINES += HAVE_T9WRITE
QMAKE_USE += t9write_db
INCLUDEPATH += $$T9WRITE_INCLUDE_DIRS
- LIBS += $$T9WRITE_ALPHABETIC_LIBS
+ t9write-alphabetic {
+ LIBS += $$T9WRITE_ALPHABETIC_LIBS
+ DEFINES += HAVE_T9WRITE_ALPHABETIC
+ !isEmpty(T9WRITE_ALPHABETIC_BINS) {
+ t9write_alphabetic_bins.files = $$T9WRITE_ALPHABETIC_BINS
+ t9write_alphabetic_bins.path = $$[QT_INSTALL_BINS]
+ INSTALLS += t9write_alphabetic_bins
+ !prefix_build: COPIES += t9write_alphabetic_bins
+ }
+ }
+ t9write-cjk {
+ LIBS += $$T9WRITE_CJK_LIBS
+ DEFINES += HAVE_T9WRITE_CJK
+ !isEmpty(T9WRITE_CJK_BINS) {
+ t9write_cjk_bins.files = $$T9WRITE_CJK_BINS
+ t9write_cjk_bins.path = $$[QT_INSTALL_BINS]
+ INSTALLS += t9write_cjk_bins
+ !prefix_build: COPIES += t9write_cjk_bins
+ }
+ }
+ DEFINES += QT_VIRTUALKEYBOARD_DEBUG
}
record-trace-input {