// Copyright (C) MyScript. Contact: https://www.myscript.com/about/contact-us/sales-inquiry/ // Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). Contact: https://www.qt.io/licensing/ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "myscriptinputmethod_p.h" #include "myscriptinputmethod_p_p.h" #include #include #include #include #include MYSCRIPT_CERTIFICATE #include #include #include #include #include #ifndef PATH_MAX #define PATH_MAX 512 #endif #include #include #include #include #include #include #define VERIFY(arg) if (!(arg)) abort(); #define VERIFY2(arg, msg, engine) if (!(arg)) { \ qCCritical(qlcVKMyScript) << msg << strMyScriptError(voGetError(engine)); \ abort(); \ } #define GESTURE_STRING_RIGHT_TO_LEFT "\xF3\xB0\x80\x82" // equivalent Unicode is "\U000F0002" #define GESTURE_STRING_LEFT_TO_RIGHT "\xF3\xB0\x80\x83" // equivalent Unicode is "\U000F0003" #define GESTURE_STRING_DOWN_THEN_LEFT "\xF3\xB0\x80\x84" // equivalent Unicode is "\U000F0004" #define GESTURE_STRING_DOWN_THEN_RIGHT "\xF3\xB0\x80\x88" // equivalent Unicode is "\U000F0008" QT_BEGIN_NAMESPACE namespace QtVirtualKeyboard { Q_LOGGING_CATEGORY(qlcVKMyScript, "qt.virtualkeyboard.myscript") typedef enum GESTURE_TYPE { GESTURE_TYPE_NONE = 0, GESTURE_TYPE_RIGHT_TO_LEFT = 0x000F0002, GESTURE_TYPE_LEFT_TO_RIGHT = 0x000F0003, GESTURE_TYPE_DOWN_THEN_LEFT = 0x000F0004, GESTURE_TYPE_DOWN_THEN_RIGHT = 0x000F0008 } GestureType; typedef struct CANDIDATE_ITEM { int candidateIndex; QStringList candidates; } CandidateItem; static inline QString getVoimErrorMessage(const voimErrorCode code) { QString message; switch (code) { case VOIM_EC_NO_ERROR: message = QString::fromLatin1("no error occurred"); break; case VOIM_EC_INVALID_VALUE: message = QString::fromLatin1("a value passed to an API function was not valid"); break; case VOIM_EC_INVALID_INDEX: message = QString::fromLatin1("an index passed to an API function was out of range"); break; case VOIM_EC_INVALID_OPERATION: message = QString::fromLatin1("the requested operation is not valid regarding the current state of the target object"); break; case VOIM_EC_OUT_OF_MEMORY: message = QString::fromLatin1("a memory allocation failure"); break; case VOIM_EC_IO_FAILURE: message = QString::fromLatin1("an I/O operation failure"); break; case VOIM_EC_INTERNAL_ERROR: message = QString::fromLatin1("an internal error"); break; default: message = QString::fromLatin1("unknown error code - ") + QString::number(code); break; } return message; } class MyScriptInputMethodPrivate { Q_DECLARE_PUBLIC(MyScriptInputMethod) public: MyScriptInputMethodPrivate(MyScriptInputMethod *q_ptr) : q_ptr(q_ptr), m_engine(nullptr), m_languageManager(nullptr), m_recognizer(nullptr), m_onManagingResult(false), m_isProcessing(false), m_commitTimer(0), textCase(QVirtualKeyboardInputEngine::TextCase::Lower), wordIndex(-1), m_itemIndex(-1), m_preeditCursorPosition(0) { initHwrEngine(); } ~MyScriptInputMethodPrivate() { destroyHwrEngine(); } void setContext(QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) { Q_UNUSED(patternRecognitionMode); Q_UNUSED(traceCaptureDeviceInfo); Q_UNUSED(traceScreenInfo); qCDebug(qlcVKMyScript) << Q_FUNC_INFO; } QVirtualKeyboardTrace *traceBegin( int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) { Q_UNUSED(patternRecognitionMode); Q_UNUSED(traceCaptureDeviceInfo); Q_UNUSED(traceScreenInfo); qCDebug(qlcVKMyScript) << Q_FUNC_INFO << traceId; if (!m_isProcessing) { Q_Q(MyScriptInputMethod); if (!q->inputContext()->preeditText().isEmpty()) q->inputContext()->commit(); } stopCommitTimer(); for (int i = 0; i < traceList.size(); i++) { traceList[i]->setOpacity(qMax(0.0, 1 - 0.25 * (traceList.size() - i))); } Q_Q(MyScriptInputMethod); QVirtualKeyboardTrace *trace = new QVirtualKeyboardTrace(q); traceList.append(trace); return trace; } void traceEnd(QVirtualKeyboardTrace *trace) { if (trace->isCanceled()) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO << "discarded" << trace; traceList.removeOne(trace); delete trace; } else { addPointsToTraceGroup(trace); } if (!traceList.isEmpty() && countActiveTraces() == 0) { resetCommitTimer(); } } int countActiveTraces() const { int count = 0; for (QVirtualKeyboardTrace *trace : qAsConst(traceList)) { if (!trace->isFinal()) count++; } return count; } void clearTraces() { qDeleteAll(traceList); traceList.clear(); } void handleBackspace() { Q_Q(MyScriptInputMethod); cancelRecognition(); clearCandidates(); clearItems(); q->inputContext()->commit(); } void addPointsToTraceGroup(QVirtualKeyboardTrace *trace) { const QVariantList sourcePoints = trace->points(); struct voPoint { float x; float y; }; std::vector points; points.reserve(sourcePoints.size()); for (const QVariant &p : sourcePoints) { const QPointF pt(p.toPointF()); points.push_back({ (float)pt.x(), (float)pt.y() }); } if (!voim_addStroke(m_engine, m_recognizer, &points.data()->x, sizeof(voPoint), &points.data()->y, sizeof(voPoint), (int)(points.size())) && voim_getError(m_engine) != VOIM_EC_NO_ERROR) { qCCritical(qlcVKMyScript) << "voim_addStroke() failed -" << getVoimErrorMessage(voim_getError(m_engine)); return; } } void clearCandidates(void) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; wordCandidates.clear(); word = QString(); wordIndex = -1; updateCandidateView(); } void clearItems(void) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; m_itemIndex = -1; for (int i = 0; i < m_items.count(); i++) { CandidateItem *candidateItem = m_items.at(i).second; delete candidateItem; } m_items.clear(); } bool cancelRecognition() { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; Q_ASSERT(m_engine != nullptr && m_recognizer != nullptr); if (m_isProcessing) stopCommitTimer(); //commit(); clearTraces(); if (voim_cancel(m_engine, m_recognizer) && voim_getError(m_engine) != VOIM_EC_NO_ERROR) { qCCritical(qlcVKMyScript) << "voim_cancel() failed -" << getVoimErrorMessage(voim_getError(m_engine)); return false; } m_isProcessing = false; return !traceList.isEmpty(); } void resetCommitTimer(void) { Q_Q(MyScriptInputMethod); stopCommitTimer(); m_commitTimer = q->startTimer(1500); } void stopCommitTimer(void) { if (m_commitTimer) { Q_Q(MyScriptInputMethod); q->killTimer(m_commitTimer); m_commitTimer = 0; } } QString getLibraryPath(QString libName) { QString libPath = QLibraryInfo::path(QLibraryInfo::LibrariesPath) + "/" + libName; if (QFile::exists(libPath)) { return libPath; } else { return QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/" + libName; } } void initHwrEngine(void) { if (!createEngine()) return; if (!createLanguageManager()) { voim_destroyEngine(m_engine); m_engine = nullptr; return; } if (!createRecognizer()) { voim_destroyLanguageManager(m_engine, m_languageManager); voim_destroyEngine(m_engine); m_languageManager = nullptr; m_engine = nullptr; return; } if (!voim_setNotificationCallback(m_engine, m_recognizer, notificationCallback, this)) { qCCritical(qlcVKMyScript) << "voim_setNotificationCallback() failed -" << getVoimErrorMessage(voim_getError(m_engine)); return; } m_threadController.reset(new MyScriptRecognizeController(this, m_engine, m_recognizer)); } bool createEngine(void) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; /* * NOTE: you must use the certificate provided by MyScript to use a MyScript product. * It is described in MyCertificate.c and MyCertificate.h */ const voCertificate *certificate = &myCertificate; voimProperty *properties = nullptr; QString imLibrary = getLibraryPath(MYSCRIPT_VOIM_NAME); properties = Properties_put(properties, "com.myscript.im.library", imLibrary.toStdString().c_str()); if (!properties) { qCCritical(qlcVKMyScript) << "failed to define property " << "com.myscript.im.library" << " with value " << imLibrary; return false; } QString engineLibrary = getLibraryPath(MYSCRIPT_ENGINE_NAME); properties = Properties_put(properties, "com.myscript.engine.library", engineLibrary.toStdString().c_str()); if (!properties) { qCCritical(qlcVKMyScript) << "failed to define property " << "com.myscript.engine.library" << " with value " << engineLibrary; return false; } QString propertyFile = QLatin1String("/Engine.properties"); propertyFile = MYSCRIPT_VOIM_PROPERTY_PATH + propertyFile; if (!checkFile(propertyFile)) { qCCritical(qlcVKMyScript) << "failed to open Engine Property file " << propertyFile; return false; } m_engine = voim_createEngine(certificate, propertyFile.toStdString().c_str(), properties); if (!m_engine) { qCCritical(qlcVKMyScript) << "voim_createEngine() failed"; return false; } return true; } bool createLanguageManager(void) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; voimProperty *properties = nullptr; QString languageConf = MYSCRIPT_LANGUAGE_CONF_PATH; properties = Properties_put(properties, "com.myscript.im.languageSearchPath", languageConf.toStdString().c_str()); if (!properties) { qCCritical(qlcVKMyScript) << "failed to define property " << "com.myscript.im.languageSearchPath" << " with value " << languageConf; return false; } properties = Properties_put(properties, "com.myscript.im.languageManifestSuffix", ".conf"); if (!properties) { qCCritical(qlcVKMyScript) << "failed to define property " << "com.myscript.im.languageManifestSuffix" << " with value" << " \".conf\""; return false; } QString propertyFile = QLatin1String("/LanguageManager.properties"); propertyFile = MYSCRIPT_VOIM_PROPERTY_PATH + propertyFile; if (!checkFile(propertyFile)) { qCCritical(qlcVKMyScript) << "failed to open LanguageManager Property file " << propertyFile; return false; } m_languageManager = voim_createLanguageManager(m_engine, propertyFile.toStdString().c_str(), properties); if (!m_languageManager) { qCCritical(qlcVKMyScript) << "voim_createLanguageManager() failed -" << getVoimErrorMessage(voim_getError(m_engine)); return false; } voim_refreshLanguageList(m_engine, m_languageManager); if (voim_getError(m_engine) != VOIM_EC_NO_ERROR) { qCCritical(qlcVKMyScript) << "voim_refreshLanguageList failed -" << getVoimErrorMessage(voim_getError(m_engine)); } Properties_destroy(properties); return true; } bool createRecognizer(void) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; QString propertyFile = QLatin1String("/Recognizer.properties"); propertyFile = MYSCRIPT_VOIM_PROPERTY_PATH + propertyFile; if (!checkFile(propertyFile)) { qCCritical(qlcVKMyScript) << "failed to open Recognizer Property file " << propertyFile; return false; } m_recognizer = voim_createRecognizer(m_engine, m_languageManager, propertyFile.toStdString().c_str(), NULL); if (!m_recognizer) { qCCritical(qlcVKMyScript) << "voim_createRecognizer() failed -" << getVoimErrorMessage(voim_getError(m_engine)); return false; } return true; } QString getLanguageName(const QString &locale) { if (locale.startsWith(QLatin1String("ar"))) // (language == "ar_EG" || language == "ar_AR") return QLatin1String("ar"); else if (locale.startsWith(QLatin1String("fa"))) // (language == "fa_FA") return QLatin1String("fa_IR"); else if (locale.startsWith(QLatin1String("nb"))) return QLatin1String("no_NO"); else if (locale.startsWith(QLatin1String("sr"))) return QLatin1String("sr_Cyrl_RS"); else return locale; } QString getModeName(Qt::InputMethodHints inputMethodHints) { /* * Qt::InputMethodHints flags description * * - Qt::ImhNone :0x00000000 * - Qt::ImhHiddenText :0x00000001 * - Qt::ImhSensitiveData :0x00000002 * - Qt::ImhNoAutoUppercase :0x00000004 * - Qt::ImhPreferNumbers :0x00000008 * - Qt::ImhPreferUppercase :0x00000010 * - Qt::ImhPreferLowercase :0x00000020 * - Qt::ImhNoPredictiveText :0x00000040 * - Qt::ImhDate :0x00000080 * - Qt::ImhTime :0x00000100 * - Qt::ImhPreferLatin :0x00000200 * - Qt::ImhMultiLine :0x00000400 * - Qt::ImhExclusiveInputMask :0xffff0000 * - Qt::ImhDigitsOnly :0x00010000 * - Qt::ImhFormattedNumbersOnly :0x00020000 * - Qt::ImhUppercaseOnly :0x00040000 * - Qt::ImhLowercaseOnly :0x00080000 * - Qt::ImhDialableCharactersOnly :0x00100000 * - Qt::ImhEmailCharactersOnly :0x00200000 * - Qt::ImhUrlCharactersOnly :0x00400000 * - Qt::ImhLatinOnly :0x00800000 */ if (inputMethodHints & Qt::ImhDigitsOnly) return QLatin1String("number-superimposed"); if (inputMethodHints & Qt::ImhFormattedNumbersOnly) return QLatin1String("number-superimposed"); // "number-superimposed" is not correctly matched with Qt::ImhFormattedNumbersOnly // temporary linked to "number-superimposed", need to improve it later on if (inputMethodHints & Qt::ImhDialableCharactersOnly) return QLatin1String("phone_number-superimposed"); if (inputMethodHints & Qt::ImhEmailCharactersOnly) return QLatin1String("email-superimposed"); if (inputMethodHints & Qt::ImhUrlCharactersOnly) return QLatin1String("uri-superimposed"); return QLatin1String("text-superimposed"); } bool setMode(const QString &locale, Qt::InputMethodHints inputMethodHints) { if (locale == m_locale && inputMethodHints == m_inputMethodHints) return false; m_locale = locale; m_inputMethodHints = inputMethodHints; qCDebug(qlcVKMyScript) << Q_FUNC_INFO << locale; Q_ASSERT(m_engine != nullptr && m_recognizer != nullptr); QString language = getLanguageName(m_locale); QString mode = getModeName(m_inputMethodHints); if (!voim_setMode(m_engine, m_recognizer, language.toStdString().c_str(), mode.toStdString().c_str()) && voim_getError(m_engine) != VOIM_EC_NO_ERROR) { qCCritical(qlcVKMyScript) << "voim_setMode() failed -" << getVoimErrorMessage(voim_getError(m_engine)); return false; } return true; } bool commit(void) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; Q_ASSERT(m_engine != nullptr && m_recognizer != nullptr); if (!voim_commit(m_engine, m_recognizer) && voim_getError(m_engine) != VOIM_EC_NO_ERROR) { qCCritical(qlcVKMyScript) << "voim_commit() failed -" << getVoimErrorMessage(voim_getError(m_engine)); return false; } stopCommitTimer(); return true; } void destroyHwrEngine(void) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; cancelRecognition(); if (m_engine) { if (m_recognizer) voim_destroyRecognizer(m_engine, m_recognizer); if (m_languageManager) voim_destroyLanguageManager(m_engine, m_languageManager); voim_destroyEngine(m_engine); } m_threadController.reset(); } bool checkFile(QString filename) { if (QFile::exists(filename)) { return true; } else { return false; } } /****************************************************************************** * The notificationCallback handles events raised by * the worker thread of a recognizer * * Parameters: * - engine : the VOIM engine * - recognizer : the VOIM recognizer that sends the event. * - eventType : the type of the event. * - eventParameters : the parameters associated with the event, if any. * - userParam : the userParameter that passed to * voim_setNotificationCallback *****************************************************************************/ static void notificationCallback(voimEngine *engine, voimRecognizer *recognizer, voimEventType eventType, const void *eventParameters, void *userParam) { if (userParam) { MyScriptInputMethodPrivate *pParent = static_cast(userParam); pParent->onNotify(engine, recognizer, eventType, eventParameters); } } /****************************************************************************** * Event types of VOIM handwriting notification callback * * - VOIM_EVENT_ON_SET_MODE * :Occurs when a mode change request has been taken into account. * - VOIM_EVENT_ON_SET_POSITION_AND_SCALE_INDICATOR * :Occurs when a guide line change request has been taken into account. * - VOIM_EVENT_ON_SET_USER_DICTIONARY * :Occurs when a user dictionary has been set to the recognizer. * - VOIM_EVENT_ON_ADD_STROKE * :Occurs when a new digital ink stroke has been added to * the recognizer and received by the worker thread. * - VOIM_EVENT_ON_RECOGNITION_START * :Occurs when a recognition process starts. * - VOIM_EVENT_ON_RECOGNITION_PROGRESS * :Occurs during a recognition process. * - VOIM_EVENT_ON_RECOGNITION_END * :Occurs when a recognition process ends. * - VOIM_EVENT_ON_COMMIT * :Occurs when a commit request has been taken into account. * - VOIM_EVENT_ON_CANCEL * :Occurs when a cancel request has been taken into account. * - VOIM_EVENT_ON_FLOW_SYNC * :Occurs when a flow sync command is received by * the recognizer worker thread. * - VOIM_EVENT_ON_NEW_INPUT_ITEM * :Occurs when a new input item command is received by * the recognizer worker thread. * - VOIM_EVENT_ON_ADD_STRING * :Occurs when a new digital text string has been added * to the recognizer and received by the worker thread. *****************************************************************************/ void onNotify(voimEngine *engine, voimRecognizer *recognizer, voimEventType eventType, const void *eventParameters) { Q_UNUSED(recognizer); switch (eventType) { case VOIM_EVENT_ON_SET_MODE: { /* * voimSetModeParameters * * voimLanguage *language - The language that contains the handwriting mode * int modeIndex - The index of the mode in the language * bool successful - True if the operation was successful */ const voimSetModeParameters *p = (const voimSetModeParameters *)eventParameters; if (p->successful) { QString language = QString::fromUtf8(voim_getLanguageName(engine, p->language)); QString mode = QString::fromUtf8(voim_getLanguageModeNameAt(engine, p->language, p->modeIndex)); qCDebug(qlcVKMyScript) << "a mode" << language << "/" << mode << "has been set, ready to receive strokes"; } else { qCCritical(qlcVKMyScript) << "failed to set mode"; } } break; case VOIM_EVENT_ON_SET_POSITION_AND_SCALE_INDICATOR: { /* * voimSetPositionAndScaleIndicatorParameters * * float baselinePosition - The baseline position * float xHeight - The height of small letter x * float lineSpacing - The line spacing * bool successful - True if the operation was successful */ const voimSetPositionAndScaleIndicatorParameters *p = (const voimSetPositionAndScaleIndicatorParameters *)eventParameters; if (p->successful) { qCDebug(qlcVKMyScript) << "baselinePosition \"" << p->baselinePosition << "\", " << "xHeight \"" << p->xHeight << "\", " << "lineSpacing \"" << p->lineSpacing << "\" have been set"; } else { qCCritical(qlcVKMyScript) << "failed to set position and scale indicator"; } } break; case VOIM_EVENT_ON_SET_USER_DICTIONARY: { /* * voimSetUserDictionaryParameters * * voimDictionary *dictionary - The dictionary * bool successful - True if the operation was successful */ const voimSetUserDictionaryParameters *p = (const voimSetUserDictionaryParameters *)eventParameters; if (p->successful) qCDebug(qlcVKMyScript) << "user dictionary has been set"; else qCCritical(qlcVKMyScript) << "failed to set user dictionary"; } break; case VOIM_EVENT_ON_ADD_STROKE: { /* * voimAddStrokeParameters * * int sessionIndex - The index of the recognition session * int strokeIndex - The index of the stroke in the recognition session * bool successful - True if the operation was successful */ const voimAddStrokeParameters *p = (const voimAddStrokeParameters *)eventParameters; if (p->successful) { qCDebug(qlcVKMyScript) << "a stroke with sessionIndex \"" << p->sessionIndex << "\", " << "strokeIndex \"" << p->strokeIndex << "\" has been added"; } else { qCCritical(qlcVKMyScript) << "failed to add stroke"; } } break; case VOIM_EVENT_ON_RECOGNITION_START: { /* * voimRecognitionStartParameters * * int firstStrokeIndex - The index of the first new stroke to be recognized * int sessionIndex - The index of the recognition session * int strokeCount - The number of new strokes to be recognized */ const voimRecognitionStartParameters *p = (const voimRecognitionStartParameters *)eventParameters; qCDebug(qlcVKMyScript) << "recognition started at sessionIndex \"" << p->sessionIndex << "\", " << "firstStrokeIndex \"" << p->firstStrokeIndex << "\", " << "strokeCount \"" << p->strokeCount << "\""; m_isProcessing = true; } break; case VOIM_EVENT_ON_RECOGNITION_PROGRESS: { /* * voimRecognitionProgressParameters * * int amountDone - The current amount of work done * int amountToDo - The current amount of work to do */ const voimRecognitionProgressParameters *p = (const voimRecognitionProgressParameters *)eventParameters; if (p->amountDone == p->amountToDo) qCDebug(qlcVKMyScript) << "progress recognition, " << p->amountDone << "/" << p->amountToDo; } break; case VOIM_EVENT_ON_RECOGNITION_END: { /* * voimRecognitionEndParameters * * bool successful - True if the operation was successful */ const voimRecognitionEndParameters *p = (const voimRecognitionEndParameters *)eventParameters; if (p->successful) { qCDebug(qlcVKMyScript) << "recognition has been ended"; if (!m_onManagingResult) { m_onManagingResult = true; if (m_threadController) { m_threadController->emitRecognitionEnded(); } } } else { qCCritical(qlcVKMyScript) << "failed to finish recognition"; } } break; case VOIM_EVENT_ON_COMMIT: { /* * voimCommitParameters * * bool successful - True if the operation was successful */ const voimCommitParameters *p = (const voimCommitParameters *)eventParameters; if (p->successful) { qCDebug(qlcVKMyScript) << "recognition has been committed"; if (m_threadController) { m_threadController->emitRecognitionCommitted(); } } else { qCCritical(qlcVKMyScript) << "failed to commit recognition"; } } break; case VOIM_EVENT_ON_CANCEL: { /* * voimCancelParameters * * bool successful - True if the operation was successful */ const voimCancelParameters *p = (const voimCancelParameters *)eventParameters; if (p->successful) qCDebug(qlcVKMyScript) << "recognition has been canceled"; else qCCritical(qlcVKMyScript) << "failed to cancel recognition"; } break; case VOIM_EVENT_ON_FLOW_SYNC: { /* * voimFlowSyncParameters * * int intValue - The integer value that was passed to the voim_flowSync() function */ const voimFlowSyncParameters *p = (const voimFlowSyncParameters *)eventParameters; qCDebug(qlcVKMyScript) << "voim_flowSync() has been called with value \"" << p->intValue << "\""; } break; case VOIM_EVENT_ON_NEW_INPUT_ITEM: { /* * voimNewInputItemParameters * * bool successful - True if the operation was successful */ const voimNewInputItemParameters *p = (const voimNewInputItemParameters *)eventParameters; if (p->successful) qCDebug(qlcVKMyScript) << "new recognition session being created while remaining in the current session"; else qCCritical(qlcVKMyScript) << "failed to create new recogniiton session"; } break; case VOIM_EVENT_ON_ADD_STRING: { /* * [Note that the parameters are same as that of the VOIM_EVENT_ON_ADD_STROKE event * because references to it in the result work the same (using stroke index * for string and point index for its characters)] * * voimAddStrokeParameters * * int sessionIndex - The index of the recognition session * int strokeIndex - The index of the stroke in the recognition session * bool successful - True if the operation was successful */ const voimAddStrokeParameters *p = (const voimAddStrokeParameters *)eventParameters; if (p->successful) { qCDebug(qlcVKMyScript) << "a stroke with sessionIndex \"" << p->sessionIndex << "\", " << "strokeIndex \"" << p->strokeIndex << "\" has been added"; } else { qCCritical(qlcVKMyScript) << "failed to add string"; } } break; default: break; } } void updateCandidateView(void) { Q_Q(MyScriptInputMethod); emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, wordIndex); } void updatePreeditTextCursor(int cursorPosition) { Q_Q(MyScriptInputMethod); QVirtualKeyboardInputContext *ic = q->inputContext(); if (!ic) return; qCDebug(qlcVKMyScript) << Q_FUNC_INFO; #ifdef SENSITIVE_DEBUG qCDebug(qlcVKMyScript) << "preeditText:" << ic->preeditText(); #endif bool isItemChanged = false; int lastPosition = 0; QList>::const_iterator iter; for (iter = m_items.cbegin(); iter != m_items.cend(); iter++) { int itemIndex = iter->first; CandidateItem *candidateItem = iter->second; int candidateIndex = candidateItem->candidateIndex; QString candidate = candidateItem->candidates.at(candidateIndex); lastPosition += candidate.length(); if (candidate != " " && cursorPosition <= lastPosition) { m_itemStartPosition = lastPosition - candidate.length(); m_itemLength = candidate.length(); if (m_itemIndex != itemIndex) { clearCandidates(); word = candidate; wordIndex = candidateIndex; wordCandidates = candidateItem->candidates; m_itemIndex = itemIndex; isItemChanged = true; } break; } } if (isItemChanged) updateCandidateView(); } MyScriptInputMethod *q_ptr; QScopedPointer m_threadController; voimEngine *m_engine; voimLanguageManager *m_languageManager; voimRecognizer *m_recognizer; bool m_onManagingResult; bool m_isProcessing; int m_commitTimer; QList traceList; QVirtualKeyboardInputEngine::TextCase textCase; QStringList wordCandidates; QString word; int wordIndex; int m_itemIndex; int m_itemStartPosition; int m_itemLength; QList> m_items; QString m_locale; Qt::InputMethodHints m_inputMethodHints; int m_preeditCursorPosition; }; /*! \class QtVirtualKeyboard::MyScriptInputMethod \internal */ MyScriptInputMethod::MyScriptInputMethod(QObject *parent) : QVirtualKeyboardAbstractInputMethod(parent), d_ptr(new MyScriptInputMethodPrivate(this)) { connect(this, SIGNAL(preeditTextChanged(QString, bool, int, int, int)), this, SLOT(setPreeditText(QString, bool, int, int, int))); connect(this, SIGNAL(gestureDetected(int, int)), this, SLOT(doGestureAction(int, int))); } MyScriptInputMethod::~MyScriptInputMethod() { } QList MyScriptInputMethod::inputModes(const QString &locale) { Q_UNUSED(locale); return QList() << QVirtualKeyboardInputEngine::InputMode::Latin; } bool MyScriptInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) { Q_UNUSED(inputMode); Q_D(MyScriptInputMethod); QVirtualKeyboardInputContext *ic = inputContext(); if (d->setMode(locale, ic->inputMethodHints())) { d->m_locale = locale; return true; } return false; } bool MyScriptInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase) { Q_D(MyScriptInputMethod); d->textCase = textCase; return true; } bool MyScriptInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) { Q_UNUSED(text); Q_UNUSED(modifiers); Q_D(MyScriptInputMethod); switch (key) { case Qt::Key_Backspace: d->handleBackspace(); break; default: d->cancelRecognition(); if (inputContext()) inputContext()->commit(); break; } return false; } void MyScriptInputMethod::reset() { QVirtualKeyboardInputContext *ic = inputContext(); if (ic) { Q_D(MyScriptInputMethod); d->clearCandidates(); ic->commit(); } } void MyScriptInputMethod::update() { Q_D(MyScriptInputMethod); if (d->m_isProcessing) d->cancelRecognition(); reset(); } QList MyScriptInputMethod::selectionLists() { return QList() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList; } int MyScriptInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type) { Q_D(MyScriptInputMethod); if (type != QVirtualKeyboardSelectionListModel::Type::WordCandidateList) return 0; return d->wordCandidates.count(); } QVariant MyScriptInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role) { Q_D(MyScriptInputMethod); if (type != QVirtualKeyboardSelectionListModel::Type::WordCandidateList) return QVariant(); switch (role) { case QVirtualKeyboardSelectionListModel::Role::Display: return QVariant(d->wordCandidates.at(index)); case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength: { const QString wordCandidate(d->wordCandidates.at(index)); int wordCompletionLength = wordCandidate.length() - d->word.length(); return QVariant((wordCompletionLength > 0 && wordCandidate.startsWith(d->word)) ? wordCompletionLength : 0); } default: return QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role); } } void MyScriptInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index) { Q_D(MyScriptInputMethod); if (d->m_isProcessing) return; QVirtualKeyboardInputContext *ic = inputContext(); if (!ic) return; qCDebug(qlcVKMyScript) << Q_FUNC_INFO; QVirtualKeyboardAbstractInputMethod::selectionListItemSelected(type, index); int itemIndex = d->m_itemIndex; CandidateItem *candidateItem = d->m_items.at(itemIndex).second; QString candidate = candidateItem->candidates.at(index); candidateItem->candidateIndex = index; QString label = ic->preeditText(); label.replace(d->m_itemStartPosition, d->m_itemLength, candidate); setPreeditText(label, true, d->m_preeditCursorPosition, d->m_itemStartPosition, candidate.length()); d->updatePreeditTextCursor(d->m_preeditCursorPosition); } QList MyScriptInputMethod::patternRecognitionModes() const { return QList() << QVirtualKeyboardInputEngine::PatternRecognitionMode::Handwriting; } QVirtualKeyboardTrace *MyScriptInputMethod::traceBegin( int traceId, QVirtualKeyboardInputEngine::PatternRecognitionMode patternRecognitionMode, const QVariantMap &traceCaptureDeviceInfo, const QVariantMap &traceScreenInfo) { Q_D(MyScriptInputMethod); return d->traceBegin(traceId, patternRecognitionMode, traceCaptureDeviceInfo, traceScreenInfo); } bool MyScriptInputMethod::traceEnd(QVirtualKeyboardTrace *trace) { Q_D(MyScriptInputMethod); d->traceEnd(trace); return true; } bool MyScriptInputMethod::clickPreeditText(int cursorPosition) { Q_D(MyScriptInputMethod); if (d->m_isProcessing) return true; QVirtualKeyboardInputContext *ic = inputContext(); if (ic) { setPreeditText(ic->preeditText(), true, cursorPosition); } return true; } void MyScriptInputMethod::timerEvent(QTimerEvent *timerEvent) { Q_D(MyScriptInputMethod); if (timerEvent->timerId() == d->m_commitTimer) { d->commit(); } } void MyScriptInputMethod::setPreeditText(QString label, bool isCommitted, int cursorPosition, int highlightStart, int highlightLength) { Q_D(MyScriptInputMethod); qCDebug(qlcVKMyScript) << Q_FUNC_INFO; QList attributes; QBrush foreground = isCommitted ? QBrush(Qt::black) : QBrush(Qt::blue); QBrush backgroundNormal = QBrush(Qt::white); QBrush backgroundHighlight = QBrush(QColor(0x66, 0xCD, 0xAA)); QTextCharFormat textFormat; textFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, label.length(), textFormat)); int highlightEnd = highlightStart + highlightLength; if (highlightLength > 0 && highlightStart <= label.length() && highlightEnd <= label.length()) { if (highlightStart > 0) { QTextCharFormat normal; normal.setBackground(backgroundNormal); normal.setForeground(foreground); attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, highlightStart, normal)); } QTextCharFormat highlight; highlight.setBackground(backgroundHighlight); highlight.setForeground(foreground); attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, highlightStart, highlightLength, highlight)); if (highlightEnd < label.length()) { QTextCharFormat normal; int highlightLength = label.length() - highlightEnd; normal.setBackground(backgroundNormal); normal.setForeground(foreground); attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, highlightEnd, highlightLength, normal)); } } else { QTextCharFormat normal; normal.setBackground(backgroundNormal); normal.setForeground(foreground); attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, label.length(), normal)); } d->m_preeditCursorPosition = (cursorPosition != -1) ? cursorPosition : label.length(); attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, d->m_preeditCursorPosition, 1, QVariant())); inputContext()->setPreeditText(label, attributes); if (isCommitted) { Q_D(MyScriptInputMethod); d->updatePreeditTextCursor(d->m_preeditCursorPosition); } } void MyScriptInputMethod::doGestureAction(const int gestureType, const int gestureCount) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; Q_D(MyScriptInputMethod); d->cancelRecognition(); QVirtualKeyboardInputContext *ic = inputContext(); if (ic) { switch (gestureType) { case GESTURE_TYPE_LEFT_TO_RIGHT: if (d->m_locale.contains("ar")) { ic->inputEngine()->virtualKeyClick(Qt::Key_Backspace, QString(), Qt::NoModifier); } else { for (int i = 0; i < gestureCount; i++) ic->inputEngine()->virtualKeyClick(Qt::Key_Space, QLatin1String(" "), Qt::NoModifier); } break; case GESTURE_TYPE_RIGHT_TO_LEFT: if (d->m_locale.contains("ar")) { for (int i = 0; i < gestureCount; i++) ic->inputEngine()->virtualKeyClick(Qt::Key_Space, QLatin1String(" "), Qt::NoModifier); } else { ic->inputEngine()->virtualKeyClick(Qt::Key_Backspace, QString(), Qt::NoModifier); } break; case GESTURE_TYPE_DOWN_THEN_LEFT: if (!d->m_locale.contains("ar")) ic->inputEngine()->virtualKeyClick(Qt::Key_Return, QString(), Qt::NoModifier); break; case GESTURE_TYPE_DOWN_THEN_RIGHT: if (d->m_locale.contains("ar")) ic->inputEngine()->virtualKeyClick(Qt::Key_Return, QString(), Qt::NoModifier); break; } } } MyScriptRecognizeWorker::MyScriptRecognizeWorker(voimEngine *engine, voimRecognizer *recognizer) : QObject() , m_engine(engine) , m_recognizer(recognizer) , m_resultLabel(QString()) { } void MyScriptRecognizeWorker::manageRecognitionEnded() { qCDebug(qlcVKMyScript) << "recognition has been ended (thread)"; manageRecognitionResult(m_engine, m_recognizer, false); } void MyScriptRecognizeWorker::manageRecognitionCommitted() { qCDebug(qlcVKMyScript) << "recognition has been committed (thread)"; manageRecognitionResult(m_engine, m_recognizer, true); } void MyScriptRecognizeWorker::manageRecognitionResult(voimEngine *engine, voimRecognizer *recognizer, const bool isCommitted) { Q_ASSERT(engine != nullptr && recognizer != nullptr); voimResult *result = voim_getResult(engine, recognizer, false, true); if (result != NULL) { QString resultLabel = ""; qCDebug(qlcVKMyScript) << ">>> recognition result"; QStringList wordCandidates; QString word; int wordIndex = -1; int gestureType = GESTURE_TYPE_NONE; int gestureCount = 1; emit clearItem(); int itemCount = voim_getItemCount(engine, result); for (int itemIndex = 0; itemIndex < itemCount; itemIndex++) { wordCandidates.clear(); word = QString(); wordIndex = -1; qCDebug(qlcVKMyScript) << " * item #" << itemIndex << " of " << itemCount; int candidateCount = voim_getItemCandidateCount(engine, result, itemIndex); for (int candidateIndex = 0; candidateIndex < candidateCount; candidateIndex++) { int length = voim_getItemCandidateLabel(engine, result, itemIndex, candidateIndex, nullptr, 0, "UTF-8"); if (length > 0) { std::vector bytes(length + 1); char *temp = bytes.data(); voim_getItemCandidateLabel(engine, result, itemIndex, candidateIndex, temp, length, "UTF-8"); #ifdef SENSITIVE_DEBUG float score = voim_getItemCandidateScore(engine, result, itemIndex, candidateIndex); qCDebug(qlcVKMyScript) << " - candidate #" << candidateIndex << " of " << candidateCount << " :" << temp << "(" << score << ")"; #endif QString label = QString::fromUtf8(temp); if (candidateIndex == 0) { gestureType = isGesture(label); if (gestureType == GESTURE_TYPE_NONE) { resultLabel += label; } else { gestureCount = label.length() / QString::fromStdString(GESTURE_STRING_LEFT_TO_RIGHT).length(); break; } } if (isGesture(label) == GESTURE_TYPE_NONE) { if (wordIndex == -1) { word = label; wordIndex = candidateIndex; } wordCandidates << label; } } } if (gestureType != GESTURE_TYPE_NONE) break; emit newItem(itemIndex, wordIndex, wordCandidates); } voim_destroyResult(m_engine, result); if (gestureType != GESTURE_TYPE_NONE) { emit clearTraces(); emit gestureDetected(gestureType, gestureCount); } else { m_resultLabel = resultLabel; emit preeditChanged(m_resultLabel, false); emit newCandidates(wordCandidates, word, wordIndex); } } else if (result == NULL && voim_getError(m_engine) != VOIM_EC_NO_ERROR) { qCCritical(qlcVKMyScript) << "voim_getResult() failed -" << getVoimErrorMessage(voim_getError(m_engine)); } emit recognitionEnded(); if (isCommitted) { emit clearTraces(); emit preeditChanged(m_resultLabel, true); emit recognitionCommitted(); } } int MyScriptRecognizeWorker::isGesture(const QString label) { if (label.length() < 2) return GESTURE_TYPE_NONE; if (label.contains(QString::fromUtf8(GESTURE_STRING_RIGHT_TO_LEFT))) return GESTURE_TYPE_RIGHT_TO_LEFT; if (label.contains(QString::fromUtf8(GESTURE_STRING_LEFT_TO_RIGHT))) return GESTURE_TYPE_LEFT_TO_RIGHT; if (label.contains(QString::fromUtf8(GESTURE_STRING_DOWN_THEN_LEFT))) return GESTURE_TYPE_DOWN_THEN_LEFT; if (label.contains(QString::fromUtf8(GESTURE_STRING_DOWN_THEN_RIGHT))) return GESTURE_TYPE_DOWN_THEN_RIGHT; return GESTURE_TYPE_NONE; } MyScriptRecognizeController::MyScriptRecognizeController(MyScriptInputMethodPrivate *d_, voimEngine *engine, voimRecognizer *recognizer) : QObject() , d(d_) { MyScriptRecognizeWorker *worker = new MyScriptRecognizeWorker(engine, recognizer); worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &MyScriptRecognizeController::recognitionEnded, worker, &MyScriptRecognizeWorker::manageRecognitionEnded); connect(this, &MyScriptRecognizeController::recognitionCommitted, worker, &MyScriptRecognizeWorker::manageRecognitionCommitted); connect(worker, &MyScriptRecognizeWorker::recognitionEnded, this, &MyScriptRecognizeController::handleRecognitionEnded); connect(worker, &MyScriptRecognizeWorker::recognitionCommitted, this, &MyScriptRecognizeController::handleRecognitionCommitted); connect(worker, &MyScriptRecognizeWorker::gestureDetected, this, &MyScriptRecognizeController::handelGestureDetected); connect(worker, &MyScriptRecognizeWorker::preeditChanged, this, &MyScriptRecognizeController::handlePreeditChanged); connect(worker, &MyScriptRecognizeWorker::clearItem, this, &MyScriptRecognizeController::handleClearItem); connect(worker, &MyScriptRecognizeWorker::newItem, this, &MyScriptRecognizeController::handleNewItem); connect(worker, &MyScriptRecognizeWorker::newCandidates, this, &MyScriptRecognizeController::handleNewCandidates); connect(worker, &MyScriptRecognizeWorker::clearTraces, this, &MyScriptRecognizeController::handleClearTraces); workerThread.start(); } MyScriptRecognizeController::~MyScriptRecognizeController() { workerThread.quit(); workerThread.wait(); } void MyScriptRecognizeController::emitRecognitionEnded() { emit recognitionEnded(); } void MyScriptRecognizeController::emitRecognitionCommitted() { emit recognitionCommitted(); } void MyScriptRecognizeController::handleRecognitionEnded() { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; d->m_onManagingResult = false; } void MyScriptRecognizeController::handleRecognitionCommitted() { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; d->m_isProcessing = false; } void MyScriptRecognizeController::handelGestureDetected(const int gestureType, const int gestureCount) { qCDebug(qlcVKMyScript) << Q_FUNC_INFO; emit d->q_ptr->gestureDetected(gestureType, gestureCount); } void MyScriptRecognizeController::handlePreeditChanged(const QString &preedit, const bool isCommitted) { emit d->q_ptr->preeditTextChanged(preedit, isCommitted, -1, 0, 0); } void MyScriptRecognizeController::handleClearItem() { d->clearItems(); } void MyScriptRecognizeController::handleNewItem(const int itemIndex, const int candidateIndex, const QStringList &candidates) { CandidateItem *candidateItem = new CandidateItem; candidateItem->candidateIndex = candidateIndex; candidateItem->candidates = candidates; d->m_items.push_back(std::make_pair(itemIndex, candidateItem)); d->m_itemIndex = itemIndex; } void MyScriptRecognizeController::handleNewCandidates(const QStringList &candidates, const QString &word, int wordIndex) { d->wordCandidates = candidates; d->word = word; d->wordIndex = wordIndex; if (!word.isEmpty() && word != " " && word != "\u00A0") { d->updateCandidateView(); } } void MyScriptRecognizeController::handleClearTraces() { d->clearTraces(); } } // namespace QtVirtualKeyboard QT_END_NAMESPACE