diff options
Diffstat (limited to 'src/plugins/lipi-toolkit/plugin')
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipi.json | 6 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp | 639 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipiinputmethod_p.h | 97 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipiplugin.cpp | 43 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipiplugin.h | 49 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipisharedrecognizer.cpp | 408 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipisharedrecognizer_p.h | 105 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipiworker.cpp | 250 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/lipiworker_p.h | 161 | ||||
-rw-r--r-- | src/plugins/lipi-toolkit/plugin/plugin.pro | 68 |
10 files changed, 1826 insertions, 0 deletions
diff --git a/src/plugins/lipi-toolkit/plugin/lipi.json b/src/plugins/lipi-toolkit/plugin/lipi.json new file mode 100644 index 00000000..8e22b4e9 --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipi.json @@ -0,0 +1,6 @@ +{ + "Name": "handwriting", + "Provider": "Qt Lipi-Toolkit Extension", + "InputMethod": "HandwritingInputMethod", + "Version": 100 +} diff --git a/src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp b/src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp new file mode 100644 index 00000000..ca23c491 --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp @@ -0,0 +1,639 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "lipiinputmethod_p.h" +#include "lipisharedrecognizer_p.h" +#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h> +#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h> +#include <QtVirtualKeyboard/private/qvirtualkeyboardinputcontext_p.h> +#include <QtVirtualKeyboard/private/shifthandler_p.h> +#include <QLoggingCategory> +#include <QtVirtualKeyboard/qvirtualkeyboardtrace.h> +#include <QtVirtualKeyboard/private/handwritinggesturerecognizer_p.h> + +#ifdef QT_HUNSPELLINPUTMETHOD_LIB +#include <QtHunspellInputMethod/private/hunspellinputmethod_p_p.h> +#endif + +#include "LTKCaptureDevice.h" +#include "LTKScreenContext.h" +#include "LTKTraceGroup.h" +#include "LTKChannel.h" +#include "LTKTraceFormat.h" +#include "LTKTrace.h" +#include "LTKShapeRecoResult.h" + +#include <QCryptographicHash> + +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT +#include <QtVirtualKeyboard/private/unipentrace_p.h> +#include <QStandardPaths> +#endif + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +#ifdef QT_HUNSPELLINPUTMETHOD_LIB +#define LipiInputMethodPrivateBase HunspellInputMethodPrivate +#else +#define LipiInputMethodPrivateBase DummyPrivate +class DummyPrivate {}; +#endif + +Q_LOGGING_CATEGORY(lcLipi, "qt.virtualkeyboard.lipi") + +class LipiInputMethodPrivate : public LipiInputMethodPrivateBase +{ + Q_DECLARE_PUBLIC(LipiInputMethod) +public: + LipiInputMethodPrivate(LipiInputMethod *q_ptr) : +#ifdef QT_HUNSPELLINPUTMETHOD_LIB + LipiInputMethodPrivateBase(static_cast<HunspellInputMethod *>(q_ptr)), +#else + LipiInputMethodPrivateBase(), +#endif + q_ptr(q_ptr), + recognizeTimer(0), + textCase(QVirtualKeyboardInputEngine::TextCase::Lower) +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + , unipenTrace(0) +#endif + { + } + + ~LipiInputMethodPrivate() + { + cancelRecognition(); + } + + QByteArray getContext(QVirtualKeyboardInputEngine::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(QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, + const QVariantMap &traceScreenInfo) + { + QByteArray context = getContext(patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo); + if (context == currentContext) + return; + + qCDebug(lcLipi) << "LipiInputMethodPrivate::setContext():" << QLatin1String(context.toHex()); + + clearTraces(); + + deviceInfo.reset(new LTKCaptureDevice()); + deviceInfo->setSamplingRate(traceCaptureDeviceInfo.value(QLatin1String("sampleRate"), 60).toInt()); + deviceInfo->setXDPI(traceCaptureDeviceInfo.value(QLatin1String("dpi"), 96).toInt()); + deviceInfo->setYDPI(deviceInfo->getXDPI()); + deviceInfo->setLatency(traceCaptureDeviceInfo.value(QLatin1String("latency"), 0.0).toFloat()); + deviceInfo->setUniformSampling(traceCaptureDeviceInfo.value(QLatin1String("uniform"), false).toBool()); + + screenContext.reset(new LTKScreenContext()); + QRectF boundingBox(traceScreenInfo.value(QLatin1String("boundingBox")).toRectF()); + if (!boundingBox.isEmpty()) { + screenContext->setBboxLeft(boundingBox.left()); + screenContext->setBboxTop(boundingBox.top()); + screenContext->setBboxRight(boundingBox.right()); + screenContext->setBboxBottom(boundingBox.bottom()); + } + + QVariantList horizontalRulers(traceScreenInfo.value(QLatin1String("horizontalRulers"), QVariantList()).toList()); + if (!horizontalRulers.isEmpty()) { + for (QVariantList::ConstIterator i = horizontalRulers.constBegin(); + i != horizontalRulers.constEnd(); i++) { + screenContext->addHLine(i->toFloat()); + } + } + + QVariantList verticalRulers(traceScreenInfo.value(QLatin1String("verticalRulers"), QVariantList()).toList()); + if (!horizontalRulers.isEmpty()) { + for (QVariantList::ConstIterator i = verticalRulers.constBegin(); + i != verticalRulers.constEnd(); i++) { + screenContext->addVLine(i->toFloat()); + } + } + + gestureRecognizer.setDpi(deviceInfo->getXDPI()); + + currentContext = context; + } + + QVirtualKeyboardTrace *traceBegin( + int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) + { + Q_UNUSED(traceId) + + stopRecognizeTimer(); + + setContext(patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo); + + if (recognitionTask) { + recognizer.cancelRecognitionTask(recognitionTask); + recognitionTask.reset(); + delayedResult.clear(); + } + +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + if (!unipenTrace) { + Q_Q(LipiInputMethod); + unipenTrace = new UnipenTrace(traceCaptureDeviceInfo, traceScreenInfo, q); + } +#endif + + QVirtualKeyboardTrace *trace = new QVirtualKeyboardTrace(); + trace->setChannels(QStringList(QLatin1String("t"))); + traceList.append(trace); + + return trace; + } + + void traceEnd(QVirtualKeyboardTrace *trace) + { + if (trace->isCanceled()) { + qCDebug(lcLipi) << "LipiInputMethodPrivate::traceEnd(): discarded" << trace; + traceList.removeOne(trace); + delete trace; + } else { + addPointsToTraceGroup(trace); + } + handleGesture(); + if (!traceList.isEmpty() && countActiveTraces() == 0) + restartRecognition(); + } + + int countActiveTraces() const + { + int count = 0; + for (QVirtualKeyboardTrace *trace : qAsConst(traceList)) { + if (!trace->isFinal()) + count++; + } + return count; + } + + void handleGesture() + { + if (countActiveTraces() > 0) + return; + + QVariantMap gesture = gestureRecognizer.recognize(traceList); + if (gesture.isEmpty()) + return; + + qCDebug(lcLipi) << "LipiInputMethodPrivate::handleGesture():" << gesture; + + if (gesture[QLatin1String("type")].toString() == QLatin1String("swipe")) { + + static const int SWIPE_MIN_LENGTH = 25; // mm + static const int SWIPE_ANGLE_THRESHOLD = 15; // degrees +- + + qreal swipeLength = gesture[QLatin1String("length_mm")].toReal(); + if (swipeLength >= SWIPE_MIN_LENGTH) { + + Q_Q(LipiInputMethod); + QVirtualKeyboardInputContext *ic = q->inputContext(); + if (!ic) + return; + + 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 +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + dumpTraces(); + saveTraces(Qt::Key_Backspace, 100); +#endif + cancelRecognition(); + ic->inputEngine()->virtualKeyClick(Qt::Key_Backspace, QString(), Qt::NoModifier); + } else if (swipeTouchCount == 2) { + // Double swipe: reset word, or backspace + cancelRecognition(); + if (!ic->preeditText().isEmpty()) { + q->reset(); + ic->setPreeditText(QString()); + } else { + ic->inputEngine()->virtualKeyClick(Qt::Key_Backspace, QString(), Qt::NoModifier); + } + } + return; + } + + // Swipe right + if (swipeAngle <= SWIPE_ANGLE_THRESHOLD || swipeAngle >= 360 - SWIPE_ANGLE_THRESHOLD) { + if (swipeTouchCount == 1) { + // Single swipe: space +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + dumpTraces(); + saveTraces(Qt::Key_Space, 100); +#endif + cancelRecognition(); + ic->inputEngine()->virtualKeyClick(Qt::Key_Space, QLatin1String(" "), Qt::NoModifier); + } else if (swipeTouchCount == 2) { + // Double swipe: commit word, or insert space + cancelRecognition(); +#ifdef QT_HUNSPELLINPUTMETHOD_LIB + int activeWordIndex = wordCandidates.index(); + if (activeWordIndex != -1) { + q->selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, activeWordIndex); + return; + } +#endif + ic->inputEngine()->virtualKeyClick(Qt::Key_Space, QLatin1String(" "), Qt::NoModifier); + } + return; + } + + // Swipe up + if (swipeAngle <= 270 + SWIPE_ANGLE_THRESHOLD && swipeAngle >= 270 - SWIPE_ANGLE_THRESHOLD) { + if (swipeTouchCount == 1) { + // Single swipe: toggle input mode +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + dumpTraces(); + saveTraces(Qt::Key_Mode_switch, 100); +#endif + cancelRecognition(); + if (!(ic->inputMethodHints() & (Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly | Qt::ImhDigitsOnly))) { + QVirtualKeyboardInputEngine::InputMode inputMode = ic->inputEngine()->inputMode(); + inputMode = inputMode == QVirtualKeyboardInputEngine::InputMode::Latin ? + QVirtualKeyboardInputEngine::InputMode::Numeric : QVirtualKeyboardInputEngine::InputMode::Latin; + ic->inputEngine()->setInputMode(inputMode); + } + } else if (swipeTouchCount == 2) { + // Double swipe: toggle text case + cancelRecognition(); + ic->priv()->shiftHandler()->toggleShift(); + } + return; + } + } + } + } + + void clearTraces() + { + qDeleteAll(traceList); + traceList.clear(); + traceGroup.emptyAllTraces(); + } + + void addPointsToTraceGroup(QVirtualKeyboardTrace *trace) + { + vector<LTKChannel> channels; + channels.push_back(LTKChannel("X", DT_INT, true)); + channels.push_back(LTKChannel("Y", DT_INT, true)); + bool hasTime = trace->channels().contains(QLatin1String("t")); + if (hasTime) + channels.push_back(LTKChannel("T", DT_FLOAT, true)); + LTKTraceFormat traceFormat(channels); + LTKTrace ltktrace(traceFormat); + + const QVariantList points = trace->points(); + const QVariantList timeData = hasTime ? trace->channelData(QLatin1String("t")) : QVariantList(); + QVariantList::ConstIterator t = timeData.constBegin(); + for (const QVariant &p : points) { + const QPointF pt(p.toPointF()); + vector<float> point; + point.push_back(pt.x()); + point.push_back(pt.y()); + if (hasTime) { + point.push_back(t->toFloat()); + t++; + } + ltktrace.addPoint(point); + } + traceGroup.addTrace(ltktrace); + } + + void finishRecognition() + { +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + dumpTraces(); +#endif + stopRecognizeTimer(); + clearTraces(); + if (recognitionTask && !delayedResult.isEmpty() && recognitionTask->resultId() == delayedResult[QLatin1String("resultId")].toInt()) + processResult(delayedResult); + delayedResult.clear(); + recognitionTask.reset(); + } + + void restartRecognition() + { + recognitionTask = recognizer.newRecognition(*deviceInfo, *screenContext, subsetOfClasses, 0.0f, 4); + if (recognitionTask) { + Q_Q(LipiInputMethod); + + recognitionTask->traceGroup = traceGroup; + + QSharedPointer<LipiRecognitionResultsTask> resultsTask = recognizer.startRecognition(recognitionTask); + q->connect(resultsTask.data(), SIGNAL(resultsAvailable(const QVariantList &)), SLOT(resultsAvailable(const QVariantList &))); + + resetRecognizeTimer(); + } else { + stopRecognizeTimer(); + } + } + + bool cancelRecognition() + { + stopRecognizeTimer(); + clearTraces(); + delayedResult.clear(); + bool result = !recognitionTask.isNull(); + recognitionTask.reset(); + return recognizer.cancelRecognition() || result; + } + + void resetRecognizeTimer() + { + Q_Q(LipiInputMethod); + stopRecognizeTimer(); + recognizeTimer = q->startTimer(300); + } + + void stopRecognizeTimer() + { + if (recognizeTimer) { + Q_Q(LipiInputMethod); + q->killTimer(recognizeTimer); + recognizeTimer = 0; + } + } + + void resultsAvailable(const QVariantList &resultList) + { + if (!resultList.isEmpty()) { + const QVariantMap result = resultList.at(0).toMap(); + if (recognitionTask && recognitionTask->resultId() == result[QLatin1String("resultId")].toInt()) + delayedResult = result; + else + processResult(result); + } + } + + void processResult(const QVariantMap &result) + { + const QChar ch = result[QLatin1String("unicode")].toChar(); + const QChar chUpper = ch.toUpper(); +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + // In recording mode, the text case must match with the current text case + if (unipenTrace) { + if (!ch.isLetter() || (ch.isUpper() == (textCase == QVirtualKeyboardInputEngine::TextCase::Upper))) + saveTraces(ch.unicode(), qRound(result[QLatin1String("confidence")].toDouble() * 100)); + delete unipenTrace; + unipenTrace = 0; + } +#endif + Q_Q(LipiInputMethod); + q->inputContext()->inputEngine()->virtualKeyClick((Qt::Key)chUpper.unicode(), + textCase == QVirtualKeyboardInputEngine::TextCase::Lower ? QString(ch.toLower()) : QString(chUpper), + Qt::NoModifier); + } + +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + void dumpTraces() + { + if (unipenTrace) + unipenTrace->record(traceList); + } + + void saveTraces(uint unicode, uint confidence) + { + if (!unipenTrace) + return; + + QStringList homeLocations = QStandardPaths::standardLocations(QStandardPaths::HomeLocation); + if (!homeLocations.isEmpty()) { + QString filePath = QStringLiteral("%1/%2").arg(homeLocations.at(0)).arg(QLatin1String("VIRTUAL_KEYBOARD_TRACES")); + unipenTrace->setDirectory(filePath); + unipenTrace->save(unicode, confidence); + } + } +#endif + + LipiInputMethod *q_ptr; + LipiSharedRecognizer recognizer; + QByteArray currentContext; + QScopedPointer<LTKCaptureDevice> deviceInfo; + QScopedPointer<LTKScreenContext> screenContext; + QSharedPointer<LipiRecognitionTask> recognitionTask; + LTKTraceGroup traceGroup; + QList<QVirtualKeyboardTrace *> traceList; + int recognizeTimer; + QVirtualKeyboardInputEngine::TextCase textCase; + vector<int> subsetOfClasses; + QVariantMap delayedResult; + HandwritingGestureRecognizer gestureRecognizer; +#ifdef QT_VIRTUALKEYBOARD_RECORD_TRACE_INPUT + UnipenTrace *unipenTrace; +#endif +}; + +/*! + \class QtVirtualKeyboard::LipiInputMethod + \internal +*/ + +LipiInputMethod::LipiInputMethod(QObject *parent) : +#ifdef QT_HUNSPELLINPUTMETHOD_LIB + LipiInputMethodBase(new LipiInputMethodPrivate(this), parent) +#else + LipiInputMethodBase(parent), + d_ptr(new LipiInputMethodPrivate(this)) +#endif +{ +} + +LipiInputMethod::~LipiInputMethod() +{ +} + +QList<QVirtualKeyboardInputEngine::InputMode> LipiInputMethod::inputModes(const QString &locale) +{ + Q_UNUSED(locale) + QList<QVirtualKeyboardInputEngine::InputMode> availableInputModes; + const Qt::InputMethodHints inputMethodHints(inputContext()->inputMethodHints()); + + if (inputMethodHints.testFlag(Qt::ImhDialableCharactersOnly) || inputMethodHints.testFlag(Qt::ImhDigitsOnly)) { + availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Dialable); + } else if (inputMethodHints.testFlag(Qt::ImhFormattedNumbersOnly)) { + availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Numeric); + } else { + availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Latin); + availableInputModes.append(QVirtualKeyboardInputEngine::InputMode::Numeric); + } + + return availableInputModes; +} + +bool LipiInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) +{ + Q_D(LipiInputMethod); +#ifdef QT_HUNSPELLINPUTMETHOD_LIB + HunspellInputMethod::setInputMode(locale, inputMode); +#else + Q_UNUSED(locale) +#endif + bool result = d->recognizer.setModel(QStringLiteral("SHAPEREC_ALPHANUM")); + if (!result) + return false; + d->subsetOfClasses.clear(); + switch (inputMode) { + case QVirtualKeyboardInputEngine::InputMode::Latin: + d->recognizer.subsetOfClasses(QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?,.@"), d->subsetOfClasses); + break; + case QVirtualKeyboardInputEngine::InputMode::Numeric: + case QVirtualKeyboardInputEngine::InputMode::Dialable: + d->recognizer.subsetOfClasses(QStringLiteral("1234567890,.+"), d->subsetOfClasses); + break; + default: + break; + } + return true; +} + +bool LipiInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase) +{ + Q_D(LipiInputMethod); + d->textCase = textCase; +#ifdef QT_HUNSPELLINPUTMETHOD_LIB + HunspellInputMethod::setTextCase(textCase); +#endif + return true; +} + +bool LipiInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) +{ +#ifdef QT_HUNSPELLINPUTMETHOD_LIB + Q_D(LipiInputMethod); + switch (key) { + case Qt::Key_Enter: + case Qt::Key_Return: + d->cancelRecognition(); + break; + case Qt::Key_Backspace: + if (d->cancelRecognition()) + return true; + break; + default: + break; + } + return HunspellInputMethod::keyEvent(key, text, modifiers); +#else + Q_UNUSED(key) + Q_UNUSED(text) + Q_UNUSED(modifiers) + return false; +#endif +} + +void LipiInputMethod::reset() +{ + LipiInputMethodBase::reset(); + Q_D(LipiInputMethod); + d->cancelRecognition(); +} + +void LipiInputMethod::update() +{ + LipiInputMethodBase::update(); +} + +void LipiInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index) +{ + LipiInputMethodBase::selectionListItemSelected(type, index); + Q_D(LipiInputMethod); + d->cancelRecognition(); +} + +QList<QVirtualKeyboardInputEngine::PatternRecognitionMode> LipiInputMethod::patternRecognitionModes() const +{ + return QList<QVirtualKeyboardInputEngine::PatternRecognitionMode>() + << QVirtualKeyboardInputEngine::PatternRecognitionMode::Handwriting; +} + +QVirtualKeyboardTrace *LipiInputMethod::traceBegin( + int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) +{ + Q_D(LipiInputMethod); + return d->traceBegin(traceId, patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo); +} + +bool LipiInputMethod::traceEnd(QVirtualKeyboardTrace *trace) +{ + Q_D(LipiInputMethod); + d->traceEnd(trace); + return true; +} + +void LipiInputMethod::timerEvent(QTimerEvent *timerEvent) +{ + Q_D(LipiInputMethod); + if (timerEvent->timerId() == d->recognizeTimer) { + d->finishRecognition(); + } +} + +void LipiInputMethod::resultsAvailable(const QVariantList &resultList) +{ + if (lcLipi().isDebugEnabled()) { + qCDebug(lcLipi) << "LipiInputMethod::resultsAvailable():"; + for (int i = 0; i < resultList.size(); i++) { + QVariantMap result = resultList.at(i).toMap(); + const QChar unicode = result[QLatin1String("unicode")].toChar(); + const double confidence = result[QLatin1String("confidence")].toDouble(); + qCDebug(lcLipi) << QStringLiteral("%1: %2 (%3)").arg(i + 1) + .arg(unicode).arg(confidence).toUtf8().constData(); + } + } + Q_D(LipiInputMethod); + d->resultsAvailable(resultList); +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE diff --git a/src/plugins/lipi-toolkit/plugin/lipiinputmethod_p.h b/src/plugins/lipi-toolkit/plugin/lipiinputmethod_p.h new file mode 100644 index 00000000..abbccf90 --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipiinputmethod_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 LIPIINPUTMETHOD_P_H +#define LIPIINPUTMETHOD_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifdef QT_HUNSPELLINPUTMETHOD_LIB +#include <QtHunspellInputMethod/private/hunspellinputmethod_p.h> +#define LipiInputMethodBase HunspellInputMethod +#else +#include <QtVirtualKeyboard/qvirtualkeyboardabstractinputmethod.h> +#define LipiInputMethodBase QVirtualKeyboardAbstractInputMethod +#endif + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +class LipiInputMethodPrivate; + +class LipiInputMethod : public LipiInputMethodBase +{ + Q_OBJECT + Q_DECLARE_PRIVATE(LipiInputMethod) +public: + explicit LipiInputMethod(QObject *parent = nullptr); + ~LipiInputMethod(); + + QList<QVirtualKeyboardInputEngine::InputMode> inputModes(const QString &locale); + bool setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode); + bool setTextCase(QVirtualKeyboardInputEngine::TextCase textCase); + + bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers); + + void reset(); + void update(); + + void selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index); + + QList<QVirtualKeyboardInputEngine::PatternRecognitionMode> patternRecognitionModes() const; + QVirtualKeyboardTrace *traceBegin( + int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, + const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo); + bool traceEnd(QVirtualKeyboardTrace *trace); + +protected: + void timerEvent(QTimerEvent *timerEvent); + +protected slots: + void resultsAvailable(const QVariantList &resultList); + +#ifndef QT_HUNSPELLINPUTMETHOD_LIB +private: + QScopedPointer<LipiInputMethodPrivate> d_ptr; +#endif +}; + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/lipi-toolkit/plugin/lipiplugin.cpp b/src/plugins/lipi-toolkit/plugin/lipiplugin.cpp new file mode 100644 index 00000000..8878f45a --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipiplugin.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "lipiplugin.h" +#include "lipiinputmethod_p.h" +#include <QtQml> + +QT_BEGIN_NAMESPACE + +using namespace QtVirtualKeyboard; + +void QtVirtualKeyboardLipiPlugin::registerTypes(const char *uri) const +{ + qmlRegisterType<LipiInputMethod>(uri, 2, 0, "HandwritingInputMethod"); +} + +QT_END_NAMESPACE diff --git a/src/plugins/lipi-toolkit/plugin/lipiplugin.h b/src/plugins/lipi-toolkit/plugin/lipiplugin.h new file mode 100644 index 00000000..79e5b20d --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipiplugin.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 LIPIPLUGIN_H +#define LIPIPLUGIN_H + +#include <QVirtualKeyboardExtensionPlugin> + +QT_BEGIN_NAMESPACE + +class QtVirtualKeyboardLipiPlugin : public QVirtualKeyboardExtensionPlugin +{ + Q_OBJECT + Q_INTERFACES(QVirtualKeyboardExtensionPlugin) + Q_PLUGIN_METADATA(IID QVirtualKeyboardExtensionPluginFactoryInterface_iid + FILE "lipi.json") +public: + void registerTypes(const char *uri) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/lipi-toolkit/plugin/lipisharedrecognizer.cpp b/src/plugins/lipi-toolkit/plugin/lipisharedrecognizer.cpp new file mode 100644 index 00000000..090f129c --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipisharedrecognizer.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "lipisharedrecognizer_p.h" +#include <QLoggingCategory> +#include "lipiworker_p.h" + +#include "LTKMacros.h" +#include "LTKInc.h" +#include "LTKTypes.h" +#include "LTKOSUtil.h" +#include "LTKOSUtilFactory.h" +#include "LTKErrorsList.h" +#include "LTKErrors.h" +#include "LTKLogger.h" +#include "LTKConfigFileReader.h" +#include "LTKException.h" +#include "LTKLipiEngineInterface.h" + +#include <QDir> +#include <QtCore/QLibraryInfo> + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +Q_DECLARE_LOGGING_CATEGORY(lcLipi) + +int LipiSharedRecognizer::s_lipiEngineRefCount = 0; +QString LipiSharedRecognizer::s_lipiRoot; +QString LipiSharedRecognizer::s_lipiLib; +void *LipiSharedRecognizer::s_lipiEngineHandle = nullptr; +LipiSharedRecognizer::FN_PTR_CREATELTKLIPIENGINE LipiSharedRecognizer::s_createLTKLipiEngine = nullptr; +LipiSharedRecognizer::FN_PTR_DELETELTKLIPIENGINE LipiSharedRecognizer::s_deleteLTKLipiEngine = nullptr; +LTKLipiEngineInterface *LipiSharedRecognizer::s_lipiEngine = nullptr; +LTKShapeRecognizer *LipiSharedRecognizer::s_shapeRecognizer = nullptr; +LipiWorker *LipiSharedRecognizer::s_lipiWorker = nullptr; +QMap<int, QChar> LipiSharedRecognizer::s_unicodeMap; +QString LipiSharedRecognizer::s_activeModel; +stringStringMap LipiSharedRecognizer::s_lipiEngineConfigEntries; +int LipiSharedRecognizer::s_recognitionCount = 0; + +/*! + \class QtVirtualKeyboard::LipiSharedRecognizer + \internal +*/ + +LipiSharedRecognizer::LipiSharedRecognizer() +{ + loadLipiInterface(); +} + +LipiSharedRecognizer::~LipiSharedRecognizer() +{ + unloadLipiInterface(); +} + +QString LipiSharedRecognizer::model() const +{ + return s_activeModel; +} + +bool LipiSharedRecognizer::setModel(const QString &modelName) +{ + qCDebug(lcLipi) << "LipiSharedRecognizer::setModel():" << modelName; + + if (!s_lipiEngine) { + qCWarning(lcLipi) << "Engine not initialized"; + return false; + } + + if (modelName.isEmpty()) + return false; + + if (modelName == s_activeModel) + return true; + + unloadModelData(); + + return loadModelData(modelName) == SUCCESS; +} + +void LipiSharedRecognizer::subsetOfClasses(const QString &charset, vector<int> &outSubsetOfClasses) const +{ + outSubsetOfClasses.clear(); + outSubsetOfClasses.reserve(charset.length()); + QString notFound; + for (int i = 0; i < charset.length(); i++) { + int classId = s_unicodeMap.key(charset.at(i), -1); + if (classId != -1) + outSubsetOfClasses.push_back(classId); + else if (lcLipi().isDebugEnabled()) + notFound.append(charset.at(i)); + } + if (!notFound.isEmpty()) + qCDebug(lcLipi) << "LipiSharedRecognizer::subsetOfClasses(): unrecognized characters" << notFound; +} + +QSharedPointer<LipiRecognitionTask> LipiSharedRecognizer::newRecognition(const LTKCaptureDevice& deviceInfo, + const LTKScreenContext& screenContext, + const vector<int>& inSubsetOfClasses, + float confThreshold, + int numChoices) +{ + if (!s_lipiEngine || !s_shapeRecognizer || !s_lipiWorker) + return QSharedPointer<LipiRecognitionTask>(); + + QSharedPointer<LipiRecognitionTask> task(new LipiRecognitionTask(deviceInfo, + screenContext, + inSubsetOfClasses, + confThreshold, + numChoices, + s_recognitionCount)); + + ++s_recognitionCount; + + return task; +} + +QSharedPointer<LipiRecognitionResultsTask> LipiSharedRecognizer::startRecognition(QSharedPointer<LipiRecognitionTask> &recognitionTask) +{ + if (!s_lipiEngine || !s_shapeRecognizer || !s_lipiWorker) + return QSharedPointer<LipiRecognitionResultsTask>(); + + QSharedPointer<LipiRecognitionResultsTask> resultsTask(new LipiRecognitionResultsTask(recognitionTask->resultVector, + s_unicodeMap, + recognitionTask->resultId())); + + s_lipiWorker->addTask(recognitionTask); + s_lipiWorker->addTask(resultsTask); + + return resultsTask; +} + +bool LipiSharedRecognizer::cancelRecognition() +{ + if (!s_lipiEngine || !s_shapeRecognizer || !s_lipiWorker) + return false; + + return s_lipiWorker->removeAllTasks() > 0; +} + +bool LipiSharedRecognizer::cancelRecognitionTask(QSharedPointer<LipiRecognitionTask> &recognitionTask) +{ + if (!s_lipiEngine || !s_shapeRecognizer || !s_lipiWorker || !recognitionTask) + return false; + + return recognitionTask->cancelRecognition() || s_lipiWorker->removeTask(recognitionTask) > 0; +} + +int LipiSharedRecognizer::loadLipiInterface() +{ + qCDebug(lcLipi) << "LipiSharedRecognizer::loadLipiInterface():" << s_lipiEngineRefCount; + + if (++s_lipiEngineRefCount == 1) { + if (s_lipiRoot.isEmpty()) { + /* LIPI_ROOT defines the root directory for lipi-toolkit project. + LIPI_LIB is an extension implemented for QtVirtualKeyboard and + allows using different location for lipi-toolkit plugins. + + LIPI_LIB defaults to LIPI_ROOT + "/lib". + */ + bool lipiRootVarIsEmpty = qEnvironmentVariableIsEmpty("LIPI_ROOT"); + s_lipiRoot = lipiRootVarIsEmpty ? + QDir(QLibraryInfo::location(QLibraryInfo::DataPath) + QLatin1String("/qtvirtualkeyboard/lipi_toolkit")).absolutePath() : + qEnvironmentVariable("LIPI_ROOT"); + + bool lipiLibVarIsEmpty = qEnvironmentVariableIsEmpty("LIPI_LIB"); + if (!lipiLibVarIsEmpty) + s_lipiLib = qEnvironmentVariable("LIPI_LIB"); + else if (!lipiRootVarIsEmpty) + s_lipiLib = s_lipiRoot + QLatin1String("/lib"); + else + s_lipiLib = QDir(QLibraryInfo::location(QLibraryInfo::PluginsPath) + QLatin1String("/lipi_toolkit")).absolutePath(); + } + + QScopedPointer<LTKOSUtil> osUtil(LTKOSUtilFactory::getInstance()); + const string lipiRootPath(QDir::toNativeSeparators(s_lipiRoot).toStdString()); + const string lipiLibPath(QDir::toNativeSeparators(s_lipiLib).toStdString()); + + int result = osUtil->loadSharedLib(lipiLibPath, LIPIENGINE_MODULE_STR, &s_lipiEngineHandle); + if (result != SUCCESS) { + qCWarning(lcLipi) << QStringLiteral("Error %1: Could not open shared library for module '%2'").arg(result).arg(QLatin1String(LIPIENGINE_MODULE_STR)); + return result; + } + + result = loadLipiEngineConfig(); + if (result != SUCCESS) + return result; + + result = osUtil->getFunctionAddress(s_lipiEngineHandle, "createLTKLipiEngine", (void **)&s_createLTKLipiEngine); + if (result != SUCCESS) { + qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str())); + return result; + } + + result = osUtil->getFunctionAddress(s_lipiEngineHandle, "deleteLTKLipiEngine", (void **)&s_deleteLTKLipiEngine); + if (result != SUCCESS) { + qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str())); + return result; + } + + s_lipiEngine = s_createLTKLipiEngine(); + s_lipiEngine->setLipiRootPath(lipiRootPath); + s_lipiEngine->setLipiLibPath(lipiLibPath); +#if 0 + s_lipiEngine->setLipiLogFileName(QDir::toNativeSeparators(QString("%1/lipi.log").arg(s_lipiRoot)).toStdString()); + s_lipiEngine->setLipiLogLevel("DEBUG"); +#endif + + result = s_lipiEngine->initializeLipiEngine(); + if (result != SUCCESS) { + qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str())); + return result; + } + } + + return SUCCESS; +} + +void LipiSharedRecognizer::unloadLipiInterface() +{ + qCDebug(lcLipi) << "LipiSharedRecognizer::unloadLipiInterface():" << s_lipiEngineRefCount; + + Q_ASSERT(s_lipiEngineRefCount > 0); + if (--s_lipiEngineRefCount == 0) { + unloadModelData(); + if (s_lipiEngine) { + s_deleteLTKLipiEngine(); + s_lipiEngine = nullptr; + } + s_createLTKLipiEngine = nullptr; + s_deleteLTKLipiEngine = nullptr; + QScopedPointer<LTKOSUtil> osUtil(LTKOSUtilFactory::getInstance()); + osUtil->unloadSharedLib(s_lipiEngineHandle); + s_lipiEngineHandle = nullptr; + } +} + +int LipiSharedRecognizer::loadLipiEngineConfig() +{ + s_lipiEngineConfigEntries.clear(); + + const QString &lipiEngineConfigFile(QDir::toNativeSeparators(QStringLiteral("%1/projects/lipiengine.cfg").arg(s_lipiRoot))); + if (!QFileInfo::exists(lipiEngineConfigFile)) { + qCWarning(lcLipi) << "File not found" << lipiEngineConfigFile; + return FAILURE; + } + + try { + LTKConfigFileReader configReader(lipiEngineConfigFile.toStdString()); + s_lipiEngineConfigEntries = configReader.getCfgFileMap(); + } catch (LTKException e) { + return e.getErrorCode(); + } + + return SUCCESS; +} + +int LipiSharedRecognizer::resolveLogicalNameToProjectProfile(const QString &logicalName, QString &outProjectName, QString &outProfileName) +{ + outProjectName.clear(); + outProfileName.clear(); + + stringStringMap::const_iterator configEntry = s_lipiEngineConfigEntries.find(logicalName.toStdString()); + if (configEntry == s_lipiEngineConfigEntries.end()) + return FAILURE; + + QStringList parts = QString::fromLatin1(configEntry->second.c_str()).split(QLatin1Char('('), QString::SkipEmptyParts); + if (parts.length() != 2) + return FAILURE; + + parts[1].replace(QLatin1Char(')'), QString()); + + outProjectName = parts[0].trimmed(); + outProfileName = parts[1].trimmed(); + + return SUCCESS; +} + +int LipiSharedRecognizer::loadModelData(const QString &logicalName) +{ + qCDebug(lcLipi) << "LipiSharedRecognizer::loadModelData():" << logicalName; + + Q_ASSERT(s_shapeRecognizer == nullptr); + Q_ASSERT(s_lipiWorker == nullptr); + + QTime perf; + perf.start(); + + s_activeModel = logicalName; + + QString project; + QString profile; + int result = resolveLogicalNameToProjectProfile(logicalName, project, profile); + if (result == SUCCESS) { + string strProject = project.toStdString(); + string strProfile = profile.toStdString(); + int result = s_lipiEngine->createShapeRecognizer(strProject, strProfile, &s_shapeRecognizer); + if (result == SUCCESS) { + result = loadMapping(QDir::toNativeSeparators(QStringLiteral("%1/projects/%2/config/unicodeMapfile_%2.ini").arg(s_lipiRoot).arg(project))); + if (result == SUCCESS) { + s_lipiWorker = new LipiWorker(s_shapeRecognizer); + QSharedPointer<LipiLoadModelDataTask> loadModelDataTask(new LipiLoadModelDataTask()); + s_lipiWorker->addTask(loadModelDataTask); + s_lipiWorker->start(); + } + } + } + + if (result == SUCCESS) + qCDebug(lcLipi) << "LipiSharedRecognizer::loadModelData(): time:" << perf.elapsed() << "ms"; + + if (result != SUCCESS) { + qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str())); + unloadModelData(); + } + + return result; +} + +void LipiSharedRecognizer::unloadModelData() +{ + if (!s_shapeRecognizer) + return; + + qCDebug(lcLipi) << "LipiSharedRecognizer::unloadModelData():" << s_activeModel; + + QTime perf; + perf.start(); + + if (s_lipiWorker) { + delete s_lipiWorker; + s_lipiWorker = nullptr; + } + + s_lipiEngine->deleteShapeRecognizer(s_shapeRecognizer); + s_shapeRecognizer = nullptr; + s_unicodeMap.clear(); + s_activeModel.clear(); + + qCDebug(lcLipi) << "LipiSharedRecognizer::unloadModelData(): time:" << perf.elapsed() << "ms"; +} + +int LipiSharedRecognizer::loadMapping(const QString &mapFile) +{ + if (!QFileInfo(mapFile).exists()) { + qCWarning(lcLipi) << "File not found" << mapFile; + return FAILURE; + } + + try { + LTKConfigFileReader configfilereader(mapFile.toStdString()); + const stringStringMap &cfgFileMap = configfilereader.getCfgFileMap(); + + for (stringStringMap::const_iterator i = cfgFileMap.begin(); i != cfgFileMap.end(); i++) { + if (i->first.empty()) + continue; + if (!QChar::fromLatin1(i->first.at(0)).isDigit()) + continue; + + bool ok; + int id = QString::fromLatin1(i->first.c_str()).toInt(&ok, 10); + if (!ok) + continue; + + QChar ch = QChar(QString::fromLatin1(i->second.c_str()).toInt(&ok, 16)); + if (!ok) + continue; + + s_unicodeMap[id] = ch; + } + } catch (LTKException) { + return FAILURE; + } + + qCDebug(lcLipi) << s_unicodeMap; + + return SUCCESS; +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE diff --git a/src/plugins/lipi-toolkit/plugin/lipisharedrecognizer_p.h b/src/plugins/lipi-toolkit/plugin/lipisharedrecognizer_p.h new file mode 100644 index 00000000..9d7cfbea --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipisharedrecognizer_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 LIPISHAREDRECOGNIZER_P_H +#define LIPISHAREDRECOGNIZER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QString> +#include <QMap> + +#include "lipiworker_p.h" + +class LTKLipiEngineInterface; + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +class LipiSharedRecognizer +{ + Q_DISABLE_COPY(LipiSharedRecognizer) +public: + LipiSharedRecognizer(); + ~LipiSharedRecognizer(); + + QString model() const; + bool setModel(const QString &modelName); + + void subsetOfClasses(const QString &charset, vector<int> &outSubsetOfClasses) const; + + QSharedPointer<LipiRecognitionTask> newRecognition(const LTKCaptureDevice& deviceInfo, + const LTKScreenContext& screenContext, + const vector<int>& inSubsetOfClasses, + float confThreshold, + int numChoices); + QSharedPointer<LipiRecognitionResultsTask> startRecognition(QSharedPointer<LipiRecognitionTask> &recognitionTask); + bool cancelRecognition(); + bool cancelRecognitionTask(QSharedPointer<LipiRecognitionTask> &recognitionTask); + +private: + static int loadLipiInterface(); + static void unloadLipiInterface(); + static int loadLipiEngineConfig(); + static int resolveLogicalNameToProjectProfile(const QString &logicalName, QString &outProjectName, QString &outProfileName); + static int loadModelData(const QString &logicalName); + static void unloadModelData(); + static int loadMapping(const QString &mapFile); + + typedef LTKLipiEngineInterface* (*FN_PTR_CREATELTKLIPIENGINE)(void); + typedef void (*FN_PTR_DELETELTKLIPIENGINE)(void); + + static int s_lipiEngineRefCount; + static QString s_lipiRoot; + static QString s_lipiLib; + static void *s_lipiEngineHandle; + static FN_PTR_CREATELTKLIPIENGINE s_createLTKLipiEngine; + static FN_PTR_DELETELTKLIPIENGINE s_deleteLTKLipiEngine; + static LTKLipiEngineInterface *s_lipiEngine; + static LTKShapeRecognizer *s_shapeRecognizer; + static LipiWorker *s_lipiWorker; + static QMap<int, QChar> s_unicodeMap; + static QString s_activeModel; + static stringStringMap s_lipiEngineConfigEntries; + static int s_recognitionCount; +}; + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE + +#endif // LIPISHAREDRECOGNIZER_P_H diff --git a/src/plugins/lipi-toolkit/plugin/lipiworker.cpp b/src/plugins/lipi-toolkit/plugin/lipiworker.cpp new file mode 100644 index 00000000..ffe5c3b7 --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipiworker.cpp @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "lipiworker_p.h" +#include <QLoggingCategory> + +#include <QTime> + +#include "LTKShapeRecognizer.h" +#include "LTKErrors.h" + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +Q_DECLARE_LOGGING_CATEGORY(lcLipi) + +/*! + \class QtVirtualKeyboard::LipiTask + \internal +*/ + +/*! + \class QtVirtualKeyboard::LipiLoadModelDataTask + \internal +*/ + +void LipiLoadModelDataTask::run() +{ + qCDebug(lcLipi) << "LipiLoadModelDataTask::run()"; + QTime perf; + perf.start(); + int result = shapeRecognizer->loadModelData(); + qCDebug(lcLipi) << "LipiLoadModelDataTask::run(): time:" << perf.elapsed() << "ms"; + if (result != SUCCESS) + qCWarning(lcLipi) << QStringLiteral("Error %1: %2").arg(result).arg(QLatin1String(getErrorMessage(result).c_str())); +} + +/*! + \class QtVirtualKeyboard::LipiRecognitionTask + \internal +*/ + +LipiRecognitionTask::LipiRecognitionTask(const LTKCaptureDevice& deviceInfo, + const LTKScreenContext& screenContext, + const vector<int>& inSubsetOfClasses, + float confThreshold, + int numChoices, + int resultId) : + LipiTask(), + deviceInfo(deviceInfo), + screenContext(screenContext), + inSubsetOfClasses(inSubsetOfClasses), + confThreshold(confThreshold), + numChoices(numChoices), + resultVector(new vector<LTKShapeRecoResult>()), + _resultId(resultId), + stateRunning(false), + stateCancelled(false) +{ +} + +void LipiRecognitionTask::run() +{ + qCDebug(lcLipi) << "LipiRecognitionTask::run()"; + + if (!shapeRecognizer || !resultVector) + return; + + { + QMutexLocker stateGuard(&stateLock); + stateRunning = true; + } + + resultVector->clear(); + resultVector->reserve(numChoices); + + shapeRecognizer->setDeviceContext(deviceInfo); + + QTime perf; + perf.start(); + shapeRecognizer->recognize(traceGroup, screenContext, + inSubsetOfClasses, confThreshold, + numChoices, *resultVector); + + int perfElapsed = perf.elapsed(); + + { + QMutexLocker stateGuard(&stateLock); + stateRunning = false; + if (stateCancelled) + resultVector->clear(); + qCDebug(lcLipi) << "LipiRecognitionTask::run(): time:" << perfElapsed << "ms" << (stateCancelled ? "(cancelled)" : ""); + } +} + +bool LipiRecognitionTask::cancelRecognition() +{ + QMutexLocker stateGuard(&stateLock); + stateCancelled = true; + bool result = (stateRunning && shapeRecognizer); + if (result) + shapeRecognizer->requestCancelRecognition(); + return result; +} + +int LipiRecognitionTask::resultId() const +{ + return _resultId; +} + +/*! + \class QtVirtualKeyboard::LipiRecognitionResultsTask + \internal +*/ + +LipiRecognitionResultsTask::LipiRecognitionResultsTask(QSharedPointer<vector<LTKShapeRecoResult> > resultVector, + const QMap<int, QChar> &unicodeMap, + int resultId) : + LipiTask(), + resultVector(resultVector), + unicodeMap(unicodeMap), + _resultId(resultId) +{ +} + +void LipiRecognitionResultsTask::run() +{ + if (!resultVector || unicodeMap.isEmpty()) + return; + + QVariantList resultList; + for (vector<LTKShapeRecoResult>::const_iterator i = resultVector->begin(); + i != resultVector->end(); i++) { + QVariantMap result; + int shapeId = i->getShapeId(); + result[QLatin1String("resultId")] = _resultId; + result[QLatin1String("shapeId")] = shapeId; + result[QLatin1String("unicode")] = unicodeMap.value(shapeId); + result[QLatin1String("confidence")] = i->getConfidence(); + resultList.append(result); + } + + if (resultList.isEmpty()) + return; + + emit resultsAvailable(resultList); +} + +/*! + \class QtVirtualKeyboard::LipiWorker + \internal +*/ + +LipiWorker::LipiWorker(LTKShapeRecognizer *shapeRecognizer, QObject *parent) : + QThread(parent), + taskSema(), + taskLock(), + shapeRecognizer(shapeRecognizer) +{ + abort = false; +} + +LipiWorker::~LipiWorker() +{ + abort = true; + taskSema.release(); + wait(); + if (shapeRecognizer) + shapeRecognizer->unloadModelData(); +} + +void LipiWorker::addTask(QSharedPointer<LipiTask> task) +{ + if (task) { + QMutexLocker guard(&taskLock); + taskList.append(task); + taskSema.release(); + } +} + +int LipiWorker::removeTask(QSharedPointer<LipiTask> task) +{ + int count = 0; + if (task) { + QMutexLocker guard(&taskLock); + count = taskList.removeAll(task); + taskSema.acquire(qMin(count, taskSema.available())); + } + return count; +} + +int LipiWorker::removeAllTasks() +{ + QMutexLocker guard(&taskLock); + int count = taskList.count(); + taskList.clear(); + if (taskSema.available()) + taskSema.acquire(taskSema.available()); + return count; +} + +void LipiWorker::run() +{ + while (!abort) { + taskSema.acquire(); + if (abort) + break; + QSharedPointer<LipiTask> currentTask; + { + QMutexLocker guard(&taskLock); + if (!taskList.isEmpty()) { + currentTask = taskList.front(); + taskList.pop_front(); + } + } + if (currentTask) { + currentTask->shapeRecognizer = shapeRecognizer; + currentTask->run(); + } + } +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE diff --git a/src/plugins/lipi-toolkit/plugin/lipiworker_p.h b/src/plugins/lipi-toolkit/plugin/lipiworker_p.h new file mode 100644 index 00000000..098e198b --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/lipiworker_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 LIPIWORKER_P_H +#define LIPIWORKER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QThread> +#include <QSemaphore> +#include <QMutex> +#include <QStringList> +#include <QSharedPointer> +#include <QMap> + +#include "LTKTypes.h" +#include "LTKCaptureDevice.h" +#include "LTKScreenContext.h" +#include "LTKTraceGroup.h" +#include "LTKChannel.h" +#include "LTKTraceFormat.h" +#include "LTKTrace.h" +#include "LTKShapeRecognizer.h" +#include "LTKShapeRecoResult.h" + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +class LipiTask : public QObject +{ + Q_OBJECT +public: + explicit LipiTask(QObject *parent = nullptr) : + QObject(parent), + shapeRecognizer(nullptr) + {} + + virtual void run() = 0; + + LTKShapeRecognizer *shapeRecognizer; +}; + +class LipiLoadModelDataTask : public LipiTask +{ + Q_OBJECT +public: + void run(); +}; + +class LipiRecognitionTask : public LipiTask +{ + Q_OBJECT +public: + explicit LipiRecognitionTask(const LTKCaptureDevice& deviceInfo, + const LTKScreenContext& screenContext, + const vector<int>& inSubsetOfClasses, + float confThreshold, + int numChoices, + int resultId); + + void run(); + bool cancelRecognition(); + int resultId() const; + + LTKTraceGroup traceGroup; + +private: + friend class LipiSharedRecognizer; + const QMap<int, QChar> unicodeMap; + const LTKCaptureDevice deviceInfo; + const LTKScreenContext screenContext; + const vector<int> inSubsetOfClasses; + const float confThreshold; + const int numChoices; + QSharedPointer<vector<LTKShapeRecoResult> > resultVector; + const int _resultId; + QMutex stateLock; + bool stateRunning; + bool stateCancelled; +}; + +class LipiRecognitionResultsTask : public LipiTask +{ + Q_OBJECT +public: + explicit LipiRecognitionResultsTask(QSharedPointer<vector<LTKShapeRecoResult> > resultVector, + const QMap<int, QChar> &unicodeMap, + int resultId); + + void run(); + +signals: + void resultsAvailable(const QVariantList &resultList); + +private: + QSharedPointer<vector<LTKShapeRecoResult> > resultVector; + const QMap<int, QChar> &unicodeMap; + const int _resultId; +}; + +class LipiWorker : public QThread +{ + Q_OBJECT +public: + explicit LipiWorker(LTKShapeRecognizer *shapeRecognizer, QObject *parent = nullptr); + ~LipiWorker(); + + void addTask(QSharedPointer<LipiTask> task); + int removeTask(QSharedPointer<LipiTask> task); + int removeAllTasks(); + +protected: + void run(); + +private: + QList<QSharedPointer<LipiTask> > taskList; + QSemaphore taskSema; + QMutex taskLock; + LTKShapeRecognizer *shapeRecognizer; + QBasicAtomicInt abort; +}; + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE + +#endif // LIPIWORKER_P_H diff --git a/src/plugins/lipi-toolkit/plugin/plugin.pro b/src/plugins/lipi-toolkit/plugin/plugin.pro new file mode 100644 index 00000000..c3a12440 --- /dev/null +++ b/src/plugins/lipi-toolkit/plugin/plugin.pro @@ -0,0 +1,68 @@ +TARGET = qtvirtualkeyboard_lipi +CONFIG += exceptions +QT += qml virtualkeyboard-private + +HEADERS += \ + lipiinputmethod_p.h \ + lipiplugin.h \ + lipisharedrecognizer_p.h \ + lipiworker_p.h +SOURCES += \ + lipiinputmethod.cpp \ + lipiplugin.cpp \ + lipisharedrecognizer.cpp \ + lipiworker.cpp +OTHER_FILES += \ + lipi.json + +DEFINES += \ + QT_NO_CAST_TO_ASCII \ + QT_ASCII_CAST_WARNINGS \ + QT_NO_CAST_FROM_ASCII \ + QT_NO_CAST_FROM_BYTEARRAY + +include(../../../config.pri) + +INCLUDEPATH += \ + ../3rdparty/lipi-toolkit/src/include \ + ../3rdparty/lipi-toolkit/src/util/lib +LIBS += -L$$OUT_PWD/../../lib \ + -lshaperecommon$$qtPlatformTargetSuffix() \ + -lltkcommon$$qtPlatformTargetSuffix() \ + -lltkutil$$qtPlatformTargetSuffix() +win32: LIBS += Advapi32.lib +else: QMAKE_USE += libdl +ltk_projects.files = $$PWD/../3rdparty/lipi-toolkit/projects +ltk_projects.path = $$VIRTUALKEYBOARD_INSTALL_DATA/lipi_toolkit +INSTALLS += ltk_projects +!prefix_build: COPIES += ltk_projects + +!disable-hunspell { + QT += hunspellinputmethod-private +} + +LAYOUT_FILES += $$LAYOUTS_BASE/content/layouts/fallback/handwriting.qml +contains(CONFIG, lang-en(_GB)?): LAYOUT_FILES += $$LAYOUTS_BASE/content/layouts/en_GB/handwriting.fallback +contains(CONFIG, lang-en(_US)?): LAYOUT_FILES += $$LAYOUTS_BASE/content/layouts/en_US/handwriting.fallback +contains(CONFIG, lang-id.*): LAYOUT_FILES += $$LAYOUTS_BASE/content/layouts/id_ID/handwriting.fallback +contains(CONFIG, lang-ms.*): LAYOUT_FILES += $$LAYOUTS_BASE/content/layouts/ms_MY/handwriting.fallback +contains(CONFIG, lang-nl.*): LAYOUT_FILES += $$LAYOUTS_BASE/content/layouts/nl_NL/handwriting.fallback + +OTHER_FILES += \ + $$LAYOUT_FILES + +!isEmpty(LAYOUT_FILES) { + virtualkeyboard_ltk_layouts.files = $$LAYOUT_FILES + virtualkeyboard_ltk_layouts.base = $$LAYOUTS_BASE + virtualkeyboard_ltk_layouts.prefix = $$LAYOUTS_PREFIX + RESOURCES += virtualkeyboard_ltk_layouts +} + +win32 { + QMAKE_TARGET_PRODUCT = "Qt Virtual Keyboard Lipi-Toolkit (Qt $$QT_VERSION)" + QMAKE_TARGET_DESCRIPTION = "Virtual Keyboard Extension for Qt." +} + +PLUGIN_TYPE = virtualkeyboard +PLUGIN_CLASS_NAME = QtVirtualKeyboardLipiPlugin +load(qt_plugin) |