diff options
author | Jarkko Koivikko <jarkko.koivikko@code-q.fi> | 2017-04-17 11:04:04 +0300 |
---|---|---|
committer | Jarkko Koivikko <jarkko.koivikko@code-q.fi> | 2017-07-08 19:28:08 +0000 |
commit | 7f780a44c0d5ccc65880666dcf388ed6bd3769a8 (patch) | |
tree | 7a139b5c4478154ba3a509c3ca2b4bf0d96203ff /src | |
parent | 0bf1c6ad9e0b5572b8ea99f5fc8945dade5675b7 (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')
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 { |