diff options
author | Jarkko Koivikko <jarkko.koivikko@code-q.fi> | 2015-09-08 10:31:55 +0300 |
---|---|---|
committer | Jarkko Koivikko <jarkko.koivikko@code-q.fi> | 2015-10-07 12:53:39 +0000 |
commit | 14be4edfcdc2fcaf23c70fbd6de8952c53702aba (patch) | |
tree | 3371cbef2dbfc20ca35839beb45d07debab95348 /src/virtualkeyboard/t9writeinputmethod.cpp | |
parent | 3c30498468b31cfc9e129e306643000e3257c4a4 (diff) |
Add T9Write implementation of HandwritingInputMethod
This change adds T9Write implementation of HandwritingInputMethod.
To use the T9Write, the contents of T9Write sdk must be extracted
to srv/virtualkeyboard/3rdparty/t9write directory and the qmake
command line must contain CONFIG+=t9write.
Change-Id: Ib56d1d3dc553bb5d5677ab03e213dc8fed43ac68
Reviewed-by: Mitch Curtis <mitch.curtis@theqtcompany.com>
Diffstat (limited to 'src/virtualkeyboard/t9writeinputmethod.cpp')
-rw-r--r-- | src/virtualkeyboard/t9writeinputmethod.cpp | 1192 |
1 files changed, 1192 insertions, 0 deletions
diff --git a/src/virtualkeyboard/t9writeinputmethod.cpp b/src/virtualkeyboard/t9writeinputmethod.cpp new file mode 100644 index 00000000..db71793e --- /dev/null +++ b/src/virtualkeyboard/t9writeinputmethod.cpp @@ -0,0 +1,1192 @@ +/****************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd +** All rights reserved. +** For any questions to The Qt Company, please use contact form at http://qt.io +** +** This file is part of the Qt Virtual Keyboard module. +** +** Licensees holding valid commercial license for Qt may use this file in +** accordance with the Qt License Agreement provided with the Software +** or, alternatively, in accordance with the terms contained in a written +** agreement between you and The Qt Company. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.io +** +******************************************************************************/ + +#include "t9writeinputmethod.h" +#include "declarativeinputengine.h" +#include "declarativeinputcontext.h" +#include "declarativetrace.h" +#include "t9writeworker.h" +#include "virtualkeyboarddebug.h" +#include "QDirIterator" +#ifdef QT_VIRTUALKEYBOARD_DEBUG +#include <QTime> +#endif + +#include "decuma_hwr.h" +#include "decumaStatus.h" +#include "decumaSymbolCategories.h" +#include "decumaLanguages.h" +#include "xxt9wOem.h" + +class T9WriteCaseFormatter +{ +public: + T9WriteCaseFormatter() : + preferLowercase(false) + { + } + + void clear() + { + textCaseList.clear(); + } + + void ensureLength(int length, DeclarativeInputEngine::TextCase textCase) + { + if (length <= 0) { + textCaseList.clear(); + return; + } + while (length < textCaseList.length()) + textCaseList.removeLast(); + while (length > textCaseList.length()) + textCaseList.append(textCase); + } + + QString formatString(const QString &str) const + { + QString result; + DeclarativeInputEngine::TextCase textCase = DeclarativeInputEngine::Lower; + for (int i = 0; i < str.length(); ++i) { + if (i < textCaseList.length()) + textCase = textCaseList.at(i); + result.append(textCase == DeclarativeInputEngine::Upper ? str.at(i).toUpper() : (preferLowercase ? str.at(i).toLower() : str.at(i))); + } + return result; + } + + bool preferLowercase; + +private: + QList<DeclarativeInputEngine::TextCase> textCaseList; +}; + +class T9WriteInputMethodPrivate : public AbstractInputMethodPrivate +{ + Q_DECLARE_PUBLIC(T9WriteInputMethod) +public: + + T9WriteInputMethodPrivate(T9WriteInputMethod *q_ptr) : + AbstractInputMethodPrivate(), + q_ptr(q_ptr), + dictionaryLock(QMutex::Recursive), + convertedDictionary(0), + attachedDictionary(0), + resultId(0), + resultTimer(0), + decumaSession(0), + activeWordIndex(-1), + arcAdditionStarted(false), + ignoreUpdate(false), + textCase(DeclarativeInputEngine::Lower) + { + } + + static void *decumaMalloc(size_t size, void *pPrivate) + { + Q_UNUSED(pPrivate) + return malloc(size); + } + + static void *decumaCalloc(size_t elements, size_t size, void *pPrivate) + { + Q_UNUSED(pPrivate) + return calloc(elements, size); + } + + static void decumaFree(void *ptr, void *pPrivate) + { + Q_UNUSED(pPrivate) + free(ptr); + } + + void initEngine() + { + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::initEngine()"; + + if (decumaSession) + return; + + symbolCategories.clear(); + symbolCategories.append(DECUMA_CATEGORY_ANSI); + languageCategories.clear(); + languageCategories.append(DECUMA_LANG_EN); + + memset(&sessionSettings, 0, sizeof(sessionSettings)); + + QString latinDb = findLatinDb(":/databases/HWR_LatinCG/"); + hwrDbFile.setFileName(latinDb); + if (!hwrDbFile.open(QIODevice::ReadOnly)) { + qWarning() << "Could not open hwr database file" << latinDb; + return; + } + + 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; + } + + sessionSettings.recognitionMode = mcrMode; + sessionSettings.bMinimizeAddArcPreProcessing = 1; + sessionSettings.writingDirection = unknownWriting; + sessionSettings.charSet.pSymbolCategories = symbolCategories.data(); + sessionSettings.charSet.nSymbolCategories = symbolCategories.size(); + sessionSettings.charSet.pLanguages = languageCategories.data(); + sessionSettings.charSet.nLanguages = languageCategories.size(); + + session = QByteArray(decumaGetSessionSize(), 0); + decumaSession = (DECUMA_SESSION *)(!session.isEmpty() ? session.data() : 0); + + DECUMA_STATUS status = decumaBeginSession(decumaSession, &sessionSettings, &memFuncs); + Q_ASSERT(status == decumaNoError); + if (status != decumaNoError) { + qWarning() << "Could not initialize T9Write engine" << status; + } + + worker.reset(new T9WriteWorker(decumaSession)); + worker->start(); + } + + void exitEngine() + { + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::exitEngine()"; + + worker.reset(); + + if (sessionSettings.pStaticDB) { + hwrDbFile.unmap((uchar *)sessionSettings.pStaticDB); + hwrDbFile.close(); + } + + detachDictionary(&attachedDictionary); + destroyConvertedDictionary(&convertedDictionary); + + if (decumaSession) { + decumaEndSession(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(); + + if (!fileEntry.endsWith(QLatin1String(".bin"))) + continue; + + latinDb = fileEntry; + break; + } + return latinDb; + } + + QString findDictionary(const QString &dir, const QLocale &locale) + { + QStringList languageCountry = locale.name().split("_"); + if (languageCountry.length() != 2) + return QString(); + + QString dictionary; + QDirIterator it(dir, QDirIterator::NoIteratorFlags); + while (it.hasNext()) { + QString fileEntry = it.next(); + + if (!fileEntry.endsWith(QLatin1String(".ldb"))) + continue; + + if (!fileEntry.contains("_" + languageCountry[0].toUpper())) + continue; + + dictionary = fileEntry; + break; + } + + return dictionary; + } + + bool attachDictionary(void *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); + return status == decumaNoError; + } + + void detachDictionary(void **dictionary) + { + QMutexLocker dictionaryGuard(&dictionaryLock); + Q_ASSERT(decumaSession != 0); + if (!dictionary || !*dictionary) + return; + + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::detachDictionary():" << *dictionary; + + 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); + Q_UNUSED(status) + Q_ASSERT(status == decumaNoError); + Q_ASSERT(*dictionary == 0); + } + + bool setInputMode(const QLocale &locale, DeclarativeInputEngine::InputMode inputMode) + { + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::setInputMode():" << locale << inputMode; + + Q_Q(T9WriteInputMethod); + DECUMA_UINT32 language = mapToDecumaLanguage(locale); + if (language == DECUMA_LANG_GSMDEFAULT) { + qWarning() << "Handwriting input does not support the language" << locale.name(); + return false; + } + + int isLanguageSupported = 0; + decumaDatabaseIsLanguageSupported(sessionSettings.pStaticDB, language, &isLanguageSupported); + if (language == DECUMA_LANG_GSMDEFAULT) { + qWarning() << "Handwriting input does not support the language" << locale.name(); + return false; + } + + finishRecognition(); + + bool languageChanged = languageCategories.isEmpty() || !languageCategories.contains(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 DeclarativeInputEngine::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; + + 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); + } + } + break; + + case DeclarativeInputEngine::Numeric: + symbolCategories.append(DECUMA_CATEGORY_DIGIT); + if (!inputMethodHints.testFlag(Qt::ImhDigitsOnly)) + symbolCategories.append(DECUMA_CATEGORY_NUM_SUP); + break; + + case DeclarativeInputEngine::Dialable: + symbolCategories.append(DECUMA_CATEGORY_PHONE_NUMBER); + break; + + default: + return false; + } + + 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); + } + } + } + + // Attach existing dictionary, if necessary + if (sessionSettings.recognitionMode == mcrMode && !inputMethodHints.testFlag(Qt::ImhNoPredictiveText) && + convertedDictionary && !attachedDictionary) { + attachDictionary(convertedDictionary); + attachedDictionary = convertedDictionary; + } + } + + // 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); + Q_ASSERT(status == decumaNoError); + + caseFormatter.preferLowercase = inputMethodHints.testFlag(Qt::ImhPreferLowercase); + + return status == decumaNoError; + } + + DECUMA_UINT32 mapToDecumaLanguage(const QLocale &locale) + { + static const QLocale::Language maxLanguage = QLocale::Vietnamese; + static const DECUMA_UINT32 languageMap[maxLanguage + 1] = { + DECUMA_LANG_GSMDEFAULT, // AnyLanguage = 0 + DECUMA_LANG_GSMDEFAULT, // C = 1 + DECUMA_LANG_GSMDEFAULT, // Abkhazian = 2 + DECUMA_LANG_GSMDEFAULT, // Oromo = 3 + DECUMA_LANG_GSMDEFAULT, // Afar = 4 + DECUMA_LANG_AF, // Afrikaans = 5 + DECUMA_LANG_SQ, // Albanian = 6 + DECUMA_LANG_GSMDEFAULT, // Amharic = 7 + DECUMA_LANG_AR, // Arabic = 8 + DECUMA_LANG_GSMDEFAULT, // Armenian = 9 + DECUMA_LANG_GSMDEFAULT, // Assamese = 10 + DECUMA_LANG_GSMDEFAULT, // Aymara = 11 + DECUMA_LANG_AZ, // Azerbaijani = 12 + DECUMA_LANG_GSMDEFAULT, // Bashkir = 13 + DECUMA_LANG_EU, // Basque = 14 + DECUMA_LANG_BN, // Bengali = 15 + DECUMA_LANG_GSMDEFAULT, // Dzongkha = 16 + DECUMA_LANG_GSMDEFAULT, // Bihari = 17 + DECUMA_LANG_GSMDEFAULT, // Bislama = 18 + DECUMA_LANG_GSMDEFAULT, // Breton = 19 + DECUMA_LANG_BG, // Bulgarian = 20 + DECUMA_LANG_GSMDEFAULT, // Burmese = 21 + DECUMA_LANG_BE, // Belarusian = 22 + DECUMA_LANG_KM, // Khmer = 23 + DECUMA_LANG_CA, // Catalan = 24 + DECUMA_LANG_PRC, // Chinese = 25 + DECUMA_LANG_GSMDEFAULT, // Corsican = 26 + DECUMA_LANG_HR, // Croatian = 27 + DECUMA_LANG_CS, // Czech = 28 + DECUMA_LANG_DA, // Danish = 29 + DECUMA_LANG_NL, // Dutch = 30 + DECUMA_LANG_EN, // English = 31 + DECUMA_LANG_GSMDEFAULT, // Esperanto = 32 + DECUMA_LANG_ET, // Estonian = 33 + DECUMA_LANG_GSMDEFAULT, // Faroese = 34 + DECUMA_LANG_GSMDEFAULT, // Fijian = 35 + DECUMA_LANG_FI, // Finnish = 36 + DECUMA_LANG_FR, // French = 37 + DECUMA_LANG_GSMDEFAULT, // WesternFrisian = 38 + DECUMA_LANG_GSMDEFAULT, // Gaelic = 39 + DECUMA_LANG_GL, // Galician = 40 + DECUMA_LANG_GSMDEFAULT, // Georgian = 41 + DECUMA_LANG_DE, // German = 42 + DECUMA_LANG_EL, // Greek = 43 + DECUMA_LANG_GSMDEFAULT, // Greenlandic = 44 + DECUMA_LANG_GSMDEFAULT, // Guarani = 45 + DECUMA_LANG_GU, // Gujarati = 46 + DECUMA_LANG_HA, // Hausa = 47 + DECUMA_LANG_IW, // Hebrew = 48 + DECUMA_LANG_HI, // Hindi = 49 + DECUMA_LANG_HU, // Hungarian = 50 + DECUMA_LANG_IS, // Icelandic = 51 + DECUMA_LANG_IN, // Indonesian = 52 + DECUMA_LANG_GSMDEFAULT, // Interlingua = 53 + DECUMA_LANG_GSMDEFAULT, // Interlingue = 54 + DECUMA_LANG_GSMDEFAULT, // Inuktitut = 55 + DECUMA_LANG_GSMDEFAULT, // Inupiak = 56 + DECUMA_LANG_GSMDEFAULT, // Irish = 57 + DECUMA_LANG_IT, // Italian = 58 + DECUMA_LANG_JP, // Japanese = 59 + DECUMA_LANG_GSMDEFAULT, // Javanese = 60 + DECUMA_LANG_KN, // Kannada = 61 + DECUMA_LANG_GSMDEFAULT, // Kashmiri = 62 + DECUMA_LANG_KK, // Kazakh = 63 + DECUMA_LANG_GSMDEFAULT, // Kinyarwanda = 64 + DECUMA_LANG_KY, // Kirghiz = 65 + DECUMA_LANG_KO, // Korean = 66 + DECUMA_LANG_GSMDEFAULT, // Kurdish = 67 + DECUMA_LANG_GSMDEFAULT, // Rundi = 68 + DECUMA_LANG_GSMDEFAULT, // Lao = 69 + DECUMA_LANG_GSMDEFAULT, // Latin = 70 + DECUMA_LANG_LV, // Latvian = 71 + DECUMA_LANG_GSMDEFAULT, // Lingala = 72 + DECUMA_LANG_LT, // Lithuanian = 73 + DECUMA_LANG_MK, // Macedonian = 74 + DECUMA_LANG_GSMDEFAULT, // Malagasy = 75 + DECUMA_LANG_MS, // Malay = 76 + DECUMA_LANG_ML, // Malayalam = 77 + DECUMA_LANG_GSMDEFAULT, // Maltese = 78 + DECUMA_LANG_GSMDEFAULT, // Maori = 79 + DECUMA_LANG_MR, // Marathi = 80 + DECUMA_LANG_GSMDEFAULT, // Marshallese = 81 + DECUMA_LANG_MN, // Mongolian = 82 + DECUMA_LANG_GSMDEFAULT, // NauruLanguage = 83 + DECUMA_LANG_GSMDEFAULT, // Nepali = 84 + DECUMA_LANG_NO, // NorwegianBokmal = 85 + DECUMA_LANG_GSMDEFAULT, // Occitan = 86 + DECUMA_LANG_GSMDEFAULT, // Oriya = 87 + DECUMA_LANG_GSMDEFAULT, // Pashto = 88 + DECUMA_LANG_FA, // Persian = 89 + DECUMA_LANG_PL, // Polish = 90 + DECUMA_LANG_PT, // Portuguese = 91 + DECUMA_LANG_PA, // Punjabi = 92 + DECUMA_LANG_GSMDEFAULT, // Quechua = 93 + DECUMA_LANG_GSMDEFAULT, // Romansh = 94 + DECUMA_LANG_RO, // Romanian = 95 + DECUMA_LANG_RU, // Russian = 96 + DECUMA_LANG_GSMDEFAULT, // Samoan = 97 + DECUMA_LANG_GSMDEFAULT, // Sango = 98 + DECUMA_LANG_GSMDEFAULT, // Sanskrit = 99 + DECUMA_LANG_SR, // Serbian = 100 + DECUMA_LANG_GSMDEFAULT, // Ossetic = 101 + DECUMA_LANG_ST, // SouthernSotho = 102 + DECUMA_LANG_GSMDEFAULT, // Tswana = 103 + DECUMA_LANG_GSMDEFAULT, // Shona = 104 + DECUMA_LANG_GSMDEFAULT, // Sindhi = 105 + DECUMA_LANG_SI, // Sinhala = 106 + DECUMA_LANG_GSMDEFAULT, // Swati = 107 + DECUMA_LANG_SK, // Slovak = 108 + DECUMA_LANG_SL, // Slovenian = 109 + DECUMA_LANG_GSMDEFAULT, // Somali = 110 + DECUMA_LANG_ES, // Spanish = 111 + DECUMA_LANG_GSMDEFAULT, // Sundanese = 112 + DECUMA_LANG_SW, // Swahili = 113 + DECUMA_LANG_SV, // Swedish = 114 + DECUMA_LANG_GSMDEFAULT, // Sardinian = 115 + DECUMA_LANG_TG, // Tajik = 116 + DECUMA_LANG_TA, // Tamil = 117 + DECUMA_LANG_GSMDEFAULT, // Tatar = 118 + DECUMA_LANG_TE, // Telugu = 119 + DECUMA_LANG_TH, // Thai = 120 + DECUMA_LANG_GSMDEFAULT, // Tibetan = 121 + DECUMA_LANG_GSMDEFAULT, // Tigrinya = 122 + DECUMA_LANG_GSMDEFAULT, // Tongan = 123 + DECUMA_LANG_GSMDEFAULT, // Tsonga = 124 + DECUMA_LANG_TR, // Turkish = 125 + DECUMA_LANG_GSMDEFAULT, // Turkmen = 126 + DECUMA_LANG_GSMDEFAULT, // Tahitian = 127 + DECUMA_LANG_GSMDEFAULT, // Uighur = 128 + DECUMA_LANG_UK, // Ukrainian = 129 + DECUMA_LANG_UR, // Urdu = 130 + DECUMA_LANG_UZ, // Uzbek = 131 + DECUMA_LANG_VI // Vietnamese = 132 + }; + + if (locale.language() > maxLanguage) + return DECUMA_LANG_GSMDEFAULT; + + DECUMA_UINT32 language = languageMap[locale.language()]; + + return language; + } + + DeclarativeTrace *traceBegin(int traceId, DeclarativeInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) + { + Q_UNUSED(traceId) + Q_UNUSED(patternRecognitionMode) + Q_UNUSED(traceCaptureDeviceInfo) + Q_UNUSED(traceScreenInfo) + + stopResultTimer(); + + // Dictionary must be completed before the arc addition can begin + if (dictionaryTask) { + dictionaryTask->wait(); + dictionaryTask.reset(); + } + + // Cancel the current recognition task + worker->removeAllTasks<T9WriteRecognitionResultsTask>(); + if (recognitionTask) { + recognitionTask->cancelRecognition(); + recognitionTask->wait(); + worker->removeTask(recognitionTask); + 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; + + DECUMA_STATUS status; + + if (!arcAdditionStarted) { + status = decumaBeginArcAddition(decumaSession); + Q_ASSERT(status == decumaNoError); + arcAdditionStarted = true; + } + + DECUMA_UINT32 arcID = (DECUMA_UINT32)traceId; + status = decumaStartNewArc(decumaSession, arcID); + Q_ASSERT(status == decumaNoError); + if (status != decumaNoError) { + decumaEndArcAddition(decumaSession); + arcAdditionStarted = false; + return NULL; + } + + DeclarativeTrace *trace = new DeclarativeTrace(); + traceList.append(trace); + + return trace; + } + + void traceEnd(DeclarativeTrace *trace) + { + if (trace->isCanceled()) { + decumaCancelArc(decumaSession, trace->traceId()); + traceList.removeOne(trace); + delete trace; + } else { + addPointsToTraceGroup(trace); + } + if (!traceList.isEmpty()) { + Q_ASSERT(arcAdditionStarted); + if (countActiveTraces() == 0) + restartRecognition(); + } else if (arcAdditionStarted) { + decumaEndArcAddition(decumaSession); + arcAdditionStarted = false; + } + } + + int countActiveTraces() const + { + int count = 0; + foreach (DeclarativeTrace *trace, traceList) { + if (!trace->isFinal()) + count++; + } + return count; + } + + void clearTraces() + { + qDeleteAll(traceList); + traceList.clear(); + } + + void addPointsToTraceGroup(DeclarativeTrace *trace) + { + Q_ASSERT(decumaSession != 0); + + const QVariantList points = trace->points(); + DECUMA_UINT32 arcID = (DECUMA_UINT32)trace->traceId(); + DECUMA_STATUS status; + + foreach (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 = decumaCommitArc(decumaSession, arcID); + Q_ASSERT(status == decumaNoError); + } + + void noteSelected(int index) + { + if (wordCandidatesHwrResultIndex.isEmpty()) + return; + + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::noteSelected():" << index; + Q_ASSERT(index >= 0 && index < wordCandidatesHwrResultIndex.length()); + int resultIndex = wordCandidatesHwrResultIndex[index]; + DECUMA_STATUS status = decumaNoteSelectedCandidate(decumaSession, resultIndex); + Q_UNUSED(status) + Q_ASSERT(status == decumaNoError); + } + + void restartRecognition() + { + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::restartRecognition()"; + + Q_Q(T9WriteInputMethod); + + QSharedPointer<T9WriteRecognitionResult> recognitionResult(new T9WriteRecognitionResult(++resultId, 9, 64)); + recognitionTask.reset(new T9WriteRecognitionTask(recognitionResult, instantGestureSettings, + attachedDictionary ? boostDictWords : noBoost, + stringStart)); + worker->addTask(recognitionTask); + + QSharedPointer<T9WriteRecognitionResultsTask> resultsTask(new T9WriteRecognitionResultsTask(recognitionResult)); + q->connect(resultsTask.data(), SIGNAL(resultsAvailable(const QVariantList &)), SLOT(resultsAvailable(const QVariantList &))); + worker->addTask(resultsTask); + + resetResultTimer(); + } + + bool finishRecognition(bool emitSelectionListChanged = true) + { + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::finishRecognition()"; + + bool result = !traceList.isEmpty(); + + Q_ASSERT(decumaSession != 0); + + stopResultTimer(); + + clearTraces(); + + worker->removeAllTasks<T9WriteRecognitionResultsTask>(); + if (recognitionTask) { + recognitionTask->cancelRecognition(); + recognitionTask->wait(); + worker->removeTask(recognitionTask); + recognitionTask.reset(); + result = true; + } + + if (arcAdditionStarted) { + decumaEndArcAddition(decumaSession); + arcAdditionStarted = false; + } + + if (!wordCandidates.isEmpty()) { + wordCandidates.clear(); + wordCandidatesHwrResultIndex.clear(); + activeWordIndex = -1; + if (emitSelectionListChanged) { + Q_Q(T9WriteInputMethod); + emit q->selectionListChanged(DeclarativeSelectionListModel::WordCandidateList); + emit q->selectionListActiveItemChanged(DeclarativeSelectionListModel::WordCandidateList, activeWordIndex); + } + result = true; + } + + stringStart.clear(); + scrResult.clear(); + caseFormatter.clear(); + + return result; + } + + bool select(int index = -1) + { + if (sessionSettings.recognitionMode == mcrMode && wordCandidates.isEmpty()) { + finishRecognition(); + return false; + } + if (sessionSettings.recognitionMode == scrMode && scrResult.isEmpty()) { + finishRecognition(); + return false; + } + + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::select():" << index; + + Q_Q(T9WriteInputMethod); + if (sessionSettings.recognitionMode == mcrMode) { + index = index >= 0 ? index : activeWordIndex; + noteSelected(index); + QString finalWord = wordCandidates.at(index); + finishRecognition(); + q->inputContext()->commit(finalWord); + } else if (sessionSettings.recognitionMode == scrMode) { + QString finalWord = scrResult; + finishRecognition(); + q->inputContext()->inputEngine()->virtualKeyClick((Qt::Key)finalWord.at(0).unicode(), finalWord, Qt::NoModifier); + } + + return true; + } + + void resetResultTimer() + { + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::resetResultTimer()"; + Q_Q(T9WriteInputMethod); + stopResultTimer(); + resultTimer = q->startTimer(500); + } + + void stopResultTimer() + { + if (resultTimer) { + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::stopResultTimer()"; + Q_Q(T9WriteInputMethod); + q->killTimer(resultTimer); + resultTimer = 0; + } + } + + void resultsAvailable(const QVariantList &resultList) + { + if (!resultList.isEmpty()) { + if (recognitionTask && recognitionTask->resultId() == resultList.first().toMap()["resultId"].toInt()) + processResult(resultList); + } + } + + void processResult(const QVariantList &resultList) + { + if (resultList.isEmpty()) + return; + + if (resultList.first().toMap()["resultId"] != resultId) + return; + + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethodPrivate::processResult()"; + + Q_Q(T9WriteInputMethod); + DeclarativeInputContext *ic = q->inputContext(); + QStringList newWordCandidates; + QList<int> newWordCandidatesHwrResultIndex; + QString resultString; + QString gesture; + QVariantList symbolStrokes; + for (int i = 0; i < resultList.size(); i++) { + QVariantMap result = resultList.at(i).toMap(); + QString resultChars = result["chars"].toString(); + if (i == 0) + caseFormatter.ensureLength(resultChars.length(), textCase); + if (!resultChars.isEmpty()) { + resultChars = caseFormatter.formatString(resultChars); + if (sessionSettings.recognitionMode == mcrMode) { + newWordCandidates.append(resultChars); + newWordCandidatesHwrResultIndex.append(i); + } + } + if (i == 0) { + resultString = resultChars; + if (result.contains("gesture")) + gesture = result["gesture"].toString(); + if (sessionSettings.recognitionMode == mcrMode && result.contains("symbolStrokes")) + symbolStrokes = result["symbolStrokes"].toList(); + if (sessionSettings.recognitionMode == scrMode) + break; + } + } + + bool wordCandidatesChanged = wordCandidates != newWordCandidates; + + // Delete trace history + if (sessionSettings.recognitionMode == mcrMode && !symbolStrokes.isEmpty()) { + int activeTraces = symbolStrokes.at(symbolStrokes.count() - 1).toInt(); + if (symbolStrokes.count() > 1) + activeTraces += symbolStrokes.at(symbolStrokes.count() - 2).toInt(); + while (activeTraces < traceList.count()) + delete traceList.takeFirst(); + } + + // Look for a gesture at the end of first result + if (!gesture.isEmpty()) { + + DECUMA_UNICODE gestureSymbol = gesture.at(0).unicode(); + switch (gestureSymbol) { + case '\b': + ic->inputEngine()->virtualKeyClick(Qt::Key_Backspace, QString(), Qt::NoModifier); + break; + + case '\r': + ic->inputEngine()->virtualKeyClick(Qt::Key_Return, QLatin1String("\n"), Qt::NoModifier); + break; + + case ' ': + ic->inputEngine()->virtualKeyClick(Qt::Key_Space, QLatin1String(" "), Qt::NoModifier); + break; + + default: + finishRecognition(); + ic->commit(ic->preeditText()); + break; + } + + return; + } + + if (sessionSettings.recognitionMode == mcrMode) { + ignoreUpdate = true; + ic->setPreeditText(resultString); + ignoreUpdate = false; + } else if (sessionSettings.recognitionMode == scrMode) { + if (resultTimer == 0 && !resultString.isEmpty()) + ic->inputEngine()->virtualKeyClick((Qt::Key)resultString.at(0).unicode(), resultString, Qt::NoModifier); + else + scrResult = resultString; + } + + if (wordCandidatesChanged) { + wordCandidates = newWordCandidates; + wordCandidatesHwrResultIndex = newWordCandidatesHwrResultIndex; + activeWordIndex = wordCandidates.isEmpty() ? -1 : 0; + emit q->selectionListChanged(DeclarativeSelectionListModel::WordCandidateList); + emit q->selectionListActiveItemChanged(DeclarativeSelectionListModel::WordCandidateList, activeWordIndex); + } + } + + T9WriteInputMethod *q_ptr; + static const DECUMA_MEM_FUNCTIONS memFuncs; + DECUMA_SESSION_SETTINGS sessionSettings; + DECUMA_INSTANT_GESTURE_SETTINGS instantGestureSettings; + QFile hwrDbFile; + QVector<DECUMA_UINT32> languageCategories; + QVector<DECUMA_UINT32> symbolCategories; + QScopedPointer<T9WriteWorker> worker; + QList<DeclarativeTrace *> traceList; + QMutex dictionaryLock; + QString dictionaryFileName; + void *convertedDictionary; + void *attachedDictionary; + QSharedPointer<T9WriteDictionaryTask> dictionaryTask; + QSharedPointer<T9WriteRecognitionTask> recognitionTask; + int resultId; + int resultTimer; + QByteArray session; + DECUMA_SESSION *decumaSession; + QStringList wordCandidates; + QList<int> wordCandidatesHwrResultIndex; + QString stringStart; + QString scrResult; + int activeWordIndex; + bool arcAdditionStarted; + bool ignoreUpdate; + DeclarativeInputEngine::TextCase textCase; + T9WriteCaseFormatter caseFormatter; +}; + +const DECUMA_MEM_FUNCTIONS T9WriteInputMethodPrivate::memFuncs = { + T9WriteInputMethodPrivate::decumaMalloc, + T9WriteInputMethodPrivate::decumaCalloc, + T9WriteInputMethodPrivate::decumaFree, + NULL +}; + +T9WriteInputMethod::T9WriteInputMethod(QObject *parent) : + AbstractInputMethod(*new T9WriteInputMethodPrivate(this), parent) +{ + Q_D(T9WriteInputMethod); + d->initEngine(); +} + +T9WriteInputMethod::~T9WriteInputMethod() +{ + Q_D(T9WriteInputMethod); + d->exitEngine(); +} + +QList<DeclarativeInputEngine::InputMode> T9WriteInputMethod::inputModes(const QString &locale) +{ + Q_UNUSED(locale) + return QList<DeclarativeInputEngine::InputMode>() + << DeclarativeInputEngine::Latin + << DeclarativeInputEngine::Numeric + << DeclarativeInputEngine::Dialable; +} + +bool T9WriteInputMethod::setInputMode(const QString &locale, DeclarativeInputEngine::InputMode inputMode) +{ + Q_D(T9WriteInputMethod); + d->select(); + return d->setInputMode(locale, inputMode); +} + +bool T9WriteInputMethod::setTextCase(DeclarativeInputEngine::TextCase textCase) +{ + Q_D(T9WriteInputMethod); + d->textCase = textCase; + return true; +} + +bool T9WriteInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) +{ + Q_UNUSED(text) + Q_UNUSED(modifiers) + Q_D(T9WriteInputMethod); + switch (key) { + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Tab: + case Qt::Key_Space: + d->select(); + update(); + break; + + case Qt::Key_Backspace: + { + DeclarativeInputContext *ic = inputContext(); + QString preeditText = ic->preeditText(); + if (preeditText.length() > 1) { + preeditText.chop(1); + ic->setPreeditText(preeditText); + d->caseFormatter.ensureLength(preeditText.length(), d->textCase); + T9WriteCaseFormatter caseFormatter(d->caseFormatter); + d->finishRecognition(false); + d->caseFormatter = caseFormatter; + d->stringStart = preeditText; + d->wordCandidates.append(preeditText); + d->activeWordIndex = 0; + emit selectionListChanged(DeclarativeSelectionListModel::WordCandidateList); + emit selectionListActiveItemChanged(DeclarativeSelectionListModel::WordCandidateList, d->activeWordIndex); + return true; + } else { + bool result = !preeditText.isEmpty(); + if (result) + ic->clear(); + else + result = !d->scrResult.isEmpty(); + d->finishRecognition(); + return result; + } + break; + } + + default: + if (d->sessionSettings.recognitionMode == mcrMode && text.length() > 0) { + QChar c = text.at(0); + bool addToWord = !c.isPunct() && !c.isSymbol(); + + DeclarativeInputContext *ic = inputContext(); + Qt::InputMethodHints inputMethodHints = ic->inputMethodHints(); + QString preeditText = inputContext()->preeditText(); + if (!addToWord) { + if (!preeditText.isEmpty()) { + if (inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) || inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly)) + addToWord = QString(QStringLiteral(":/?#[]@!$&'()*+,;=-_.%")).contains(c); + else + addToWord = (c == Qt::Key_Apostrophe || c == Qt::Key_Minus); + } + } + if (addToWord) { + preeditText.append(text); + ic->setPreeditText(preeditText); + d->caseFormatter.ensureLength(preeditText.length(), d->textCase); + T9WriteCaseFormatter caseFormatter(d->caseFormatter); + d->finishRecognition(false); + d->caseFormatter = caseFormatter; + d->stringStart = preeditText; + d->wordCandidates.append(preeditText); + d->activeWordIndex = 0; + emit selectionListChanged(DeclarativeSelectionListModel::WordCandidateList); + emit selectionListActiveItemChanged(DeclarativeSelectionListModel::WordCandidateList, d->activeWordIndex); + return true; + } else { + ic->clear(); + d->finishRecognition(); + } + break; + } else if (d->sessionSettings.recognitionMode == scrMode) { + d->finishRecognition(); + } + } + return false; +} + +void T9WriteInputMethod::reset() +{ + Q_D(T9WriteInputMethod); + d->finishRecognition(); + d->setInputMode(QLocale(inputContext()->locale()), inputEngine()->inputMode()); +} + +void T9WriteInputMethod::update() +{ + Q_D(T9WriteInputMethod); + if (d->ignoreUpdate) + return; + d->select(); +} + +QList<DeclarativeSelectionListModel::Type> T9WriteInputMethod::selectionLists() +{ + return QList<DeclarativeSelectionListModel::Type>() << DeclarativeSelectionListModel::WordCandidateList; +} + +int T9WriteInputMethod::selectionListItemCount(DeclarativeSelectionListModel::Type type) +{ + Q_UNUSED(type) + Q_D(T9WriteInputMethod); + return d->wordCandidates.count(); +} + +QVariant T9WriteInputMethod::selectionListData(DeclarativeSelectionListModel::Type type, int index, int role) +{ + QVariant result; + Q_UNUSED(type) + Q_D(T9WriteInputMethod); + switch (role) { + case DeclarativeSelectionListModel::DisplayRole: + result = QVariant(d->wordCandidates.at(index)); + break; + case DeclarativeSelectionListModel::WordCompletionLengthRole: + result.setValue(0); + break; + default: + result = AbstractInputMethod::selectionListData(type, index, role); + break; + } + return result; +} + +void T9WriteInputMethod::selectionListItemSelected(DeclarativeSelectionListModel::Type type, int index) +{ + Q_UNUSED(type) + Q_D(T9WriteInputMethod); + d->select(index); +} + +QList<DeclarativeInputEngine::PatternRecognitionMode> T9WriteInputMethod::patternRecognitionModes() const +{ + return QList<DeclarativeInputEngine::PatternRecognitionMode>() + << DeclarativeInputEngine::HandwritingRecoginition; +} + +DeclarativeTrace *T9WriteInputMethod::traceBegin(int traceId, DeclarativeInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) +{ + Q_D(T9WriteInputMethod); + return d->traceBegin(traceId, patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo); +} + +bool T9WriteInputMethod::traceEnd(DeclarativeTrace *trace) +{ + Q_D(T9WriteInputMethod); + d->traceEnd(trace); + return true; +} + +void T9WriteInputMethod::timerEvent(QTimerEvent *timerEvent) +{ + Q_D(T9WriteInputMethod); + int timerId = timerEvent->timerId(); + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethod::timerEvent():" << timerId; + if (timerId == d->resultTimer) { + if (d->sessionSettings.recognitionMode == mcrMode) { + d->stopResultTimer(); + d->clearTraces(); + } else if (d->sessionSettings.recognitionMode == scrMode) { + d->select(); + } + } +} + +void T9WriteInputMethod::dictionaryLoadCompleted(const QString &fileUri, void *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; + + if (fileUri == d->dictionaryFileName) { + d->convertedDictionary = dictionary; + if (d->sessionSettings.recognitionMode == mcrMode && + !inputContext()->inputMethodHints().testFlag(Qt::ImhNoPredictiveText) && + !d->attachedDictionary) { + d->attachDictionary(d->convertedDictionary); + d->attachedDictionary = d->convertedDictionary; + } + } else { + d->destroyConvertedDictionary(&dictionary); + } +} + +void T9WriteInputMethod::resultsAvailable(const QVariantList &resultList) +{ +#ifdef QT_VIRTUALKEYBOARD_DEBUG + { + VIRTUALKEYBOARD_DEBUG() << "T9WriteInputMethod::resultsAvailable():"; + for (int i = 0; i < resultList.size(); i++) { + QVariantMap result = resultList.at(i).toMap(); + QString resultPrint = QString("%1: ").arg(i + 1); + QString resultChars = result.value("chars").toString(); + if (!resultChars.isEmpty()) + resultPrint.append(resultChars); + if (result.contains("gesture")) { + if (!resultChars.isEmpty()) + resultPrint.append(", "); + resultPrint.append("gesture = 0x"); + resultPrint.append(result["gesture"].toString().toUtf8().toHex()); + } + VIRTUALKEYBOARD_DEBUG() << resultPrint.toUtf8().constData(); + } + } +#endif + Q_D(T9WriteInputMethod); + d->resultsAvailable(resultList); +} |