From 8a5c261954836e2aeac53f5e6581135ffaa9a2e0 Mon Sep 17 00:00:00 2001 From: Roger Maclean Date: Tue, 12 Nov 2013 11:58:42 -0500 Subject: [PATCH] QNX: Add support for BB10 text highlighting and spell checking Adds the QPA side of the support for text highlighting during text entry and for spell checking. The changes are compatible with existing Qt text controls though require more advanced ones to have any effect. QQnxInputContext has three colors it can now use for highlighting text during composition to represent the active region, auto corrected text and reverted text. If any of these colors is invalid, that form of highlighting is not used. By default, only the active region color has a valid default which corresponds to the highlighting capabilities of classes such as QQuickTextInput. The QNX QPA native interface has been augmented with the ability to get a function pointer that can be used to set any or all of the three colors. The set of colors is reset to the default at any time that focus changes to ensure appropriate behavior if there is a mix of controls. It's worth noting that while the colors can be changed even when used with one of the standard Qt text controls, the auto-correct color will not show up since it is applied immediately before committing the text. Appropriate display of this highlighting requires that the control maintain the highlighting for a period after committing the text. Spell checking is provided via another function accessible through the QNX QPA native interface. This takes a string and a callback function to be called once spell checking is complete. As a slightly unrelated change, toSpannableString now uses toWCharArray to convert the QString to UTF-32 as required by IMF, the previous code was invalid in the case of strings containing UTF-16 surrogate pairs. Removed some extraneous includes. Change-Id: Ifdf3744d1990e0560d1923bca5db30953dea0192 Reviewed-by: Roger Maclean Reviewed-by: Fabian Bumberger Reviewed-by: Rafael Roquetto Reviewed-by: Kevin Krammer --- .../platforms/qnx/qqnxinputcontext_imf.cpp | 194 +++++++++++++----- .../platforms/qnx/qqnxinputcontext_imf.h | 18 +- .../platforms/qnx/qqnxnativeinterface.cpp | 14 ++ .../platforms/qnx/qqnxnativeinterface.h | 2 + 4 files changed, 175 insertions(+), 53 deletions(-) diff --git a/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp b/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp index dd47d37af30..619883e843d 100644 --- a/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp +++ b/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp @@ -54,6 +54,8 @@ #include #include #include +#include +#include #include #include "imf/imf_client.h" @@ -73,14 +75,10 @@ #define qInputContextDebug QT_NO_QDEBUG_MACRO #endif -#include -#include - static QQnxInputContext *sInputContextInstance; static QColor sSelectedColor(0,0xb8,0,85); -static QColor sAutoCorrectedColor(0,0xb8,0,85); -static QColor sRevertedColor(0,0x8d,0xaf,51); +static const input_session_t *sSpellCheckSession = 0; static const input_session_t *sInputSession = 0; static bool isSessionOkay(input_session_t *ic) { @@ -96,13 +94,20 @@ enum ImfEventType ImfGetTextAfterCursor, ImfGetTextBeforeCursor, ImfSendEvent, - ImfSendAsyncEvent, ImfSetComposingRegion, ImfSetComposingText, ImfIsTextSelected, ImfIsAllTextSelected, }; +struct SpellCheckInfo { + SpellCheckInfo(void *_context, void (*_spellCheckDone)(void *, const QString &, const QList &)) + : context(_context), spellCheckDone(_spellCheckDone) {} + void *context; + void (*spellCheckDone)(void *, const QString &, const QList &); +}; +Q_GLOBAL_STATIC(QQueue, sSpellCheckQueue) + // IMF requests all arrive on IMF's own thread and have to be posted to the main thread to be processed. class QQnxImfRequest { @@ -305,7 +310,8 @@ static int32_t ic_send_async_event(input_session_t *ic, event_t *event) { qInputContextIMFRequestDebug() << Q_FUNC_INFO; - QQnxImfRequest imfEvent(ic, ImfSendAsyncEvent); + // There's no difference from our point of view between ic_send_event & ic_send_async_event + QQnxImfRequest imfEvent(ic, ImfSendEvent); imfEvent.sae.event = event; imfEvent.sae.result = -1; executeIMFRequest(&imfEvent); @@ -372,6 +378,7 @@ static int32_t ic_is_all_text_selected(input_session_t* ic, int32_t* pIsSelected return event.its.result; } +// LCOV_EXCL_START - exclude from code coverage analysis // The following functions are defined in the IMF headers but are not currently called. // Not currently used @@ -446,6 +453,8 @@ static int32_t ic_set_selection(input_session_t *ic, int32_t start, int32_t end) return 0; } +// End of un-hittable code +// LCOV_EXCL_STOP static connection_interface_t ic_funcs = { @@ -497,21 +506,12 @@ static spannable_string_t *toSpannableString(const QString &text) { qInputContextDebug() << Q_FUNC_INFO << text; - spannable_string_t *pString = reinterpret_cast(malloc(sizeof(spannable_string_t))); - pString->str = (wchar_t *)malloc(sizeof(wchar_t) * text.length() + 1); - pString->length = text.length(); + spannable_string_t *pString = static_cast(malloc(sizeof(spannable_string_t))); + pString->str = static_cast(malloc(sizeof(wchar_t) * text.length() + 1)); + pString->length = text.toWCharArray(pString->str); pString->spans = 0; pString->spans_count = 0; - - const QChar *pData = text.constData(); - wchar_t *pDst = pString->str; - - while (!pData->isNull()) { - *pDst = pData->unicode(); - pDst++; - pData++; - } - *pDst = 0; + pString->str[pString->length] = 0; return pString; } @@ -575,6 +575,7 @@ QQnxInputContext::QQnxInputContext(QQnxIntegration *integration, QQnxAbstractVir m_isUpdatingText(false), m_inputPanelVisible(false), m_inputPanelLocale(QLocale::c()), + m_focusObject(0), m_integration(integration), m_virtualKeyboard(keyboard) { @@ -620,9 +621,12 @@ bool QQnxInputContext::isValid() const void QQnxInputContext::processImfEvent(QQnxImfRequest *imfEvent) { // If input session is no longer current, just bail, imfEvent should already be set with the appropriate - // return value. - if (!isSessionOkay(imfEvent->session)) - return; + // return value. The only exception is spell check events since they're not associated with the + // object with focus. + if (imfEvent->type != ImfSendEvent || imfEvent->sae.event->event_type != EVENT_SPELL_CHECK) { + if (!isSessionOkay(imfEvent->session)) + return; + } switch (imfEvent->type) { case ImfCommitText: @@ -649,9 +653,7 @@ void QQnxInputContext::processImfEvent(QQnxImfRequest *imfEvent) imfEvent->gtac.result = onGetTextBeforeCursor(imfEvent->gtac.n, imfEvent->gtac.flags); break; - // Don't need to distinguish these two cases case ImfSendEvent: - case ImfSendAsyncEvent: imfEvent->sae.result = onSendEvent(imfEvent->sae.event); break; @@ -983,29 +985,31 @@ void QQnxInputContext::updateComposition(spannable_string_t *text, int32_t new_c QVariant())); for (unsigned int i = 0; i < text->spans_count; ++i) { - const uint64_t knownMask = ACTIVE_REGION_ATTRIB | COMPOSED_TEXT_ATTRIB | - AUTO_CORRECTION_ATTRIB | REVERT_CORRECTION_ATTRIB; - bool selected = (text->spans[i].attributes_mask & ACTIVE_REGION_ATTRIB) != 0; - bool converted = (text->spans[i].attributes_mask & COMPOSED_TEXT_ATTRIB) != 0; - bool autoCorrected = (text->spans[i].attributes_mask & AUTO_CORRECTION_ATTRIB) != 0; - bool reverted = (text->spans[i].attributes_mask & REVERT_CORRECTION_ATTRIB) != 0; + QColor highlightColor; + bool underline = false; - if (text->spans[i].attributes_mask & knownMask) { + if ((text->spans[i].attributes_mask & COMPOSED_TEXT_ATTRIB) != 0) + underline = true; + + if ((text->spans[i].attributes_mask & ACTIVE_REGION_ATTRIB) != 0) { + underline = true; + highlightColor = m_highlightColor[ActiveRegion]; + } else if ((text->spans[i].attributes_mask & AUTO_CORRECTION_ATTRIB) != 0) { + highlightColor = m_highlightColor[AutoCorrected]; + } else if ((text->spans[i].attributes_mask & REVERT_CORRECTION_ATTRIB) != 0) { + highlightColor = m_highlightColor[Reverted]; + } + + if (underline || highlightColor.isValid()) { QTextCharFormat format; - - if (converted || selected) { + if (underline) format.setFontUnderline(true); - } - if (selected) { - format.setBackground(QBrush(sSelectedColor)); - } else if (autoCorrected) { - format.setBackground(QBrush(sAutoCorrectedColor)); - } else if (reverted) { - format.setBackground(QBrush(sRevertedColor)); - } - qInputContextDebug() << "attrib:" << selected << converted << text->spans[i].start << text->spans[i].end; + if (highlightColor.isValid()) + format.setBackground(QBrush(highlightColor)); + qInputContextDebug() << " attrib: " << underline << highlightColor << text->spans[i].start << text->spans[i].end; attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, text->spans[i].start, text->spans[i].end - text->spans[i].start + 1, QVariant(format))); + } } QInputMethodEvent event(m_composingText, attributes); @@ -1035,13 +1039,59 @@ void QQnxInputContext::finishComposingText() updateCursorPosition(); } +// Return the index relative to a UTF-16 sequence of characters for a index that is relative to the +// corresponding UTF-32 character string given a starting index in the UTF-16 string and a count +// of the number of lead surrogates prior to that index. Updates the highSurrogateCount to reflect the +// new surrogate characters encountered. +static int adjustIndex(const QChar *text, int utf32Index, int utf16StartIndex, int *highSurrogateCount) +{ + int utf16Index = utf32Index + *highSurrogateCount; + while (utf16StartIndex < utf16Index) { + if (text[utf16StartIndex].isHighSurrogate()) { + ++utf16Index; + ++*highSurrogateCount; + } + ++utf16StartIndex; + } + return utf16StartIndex; +} + +int QQnxInputContext::handleSpellCheck(spell_check_event_t *event) +{ + // These should never happen. + if (sSpellCheckQueue->isEmpty() || event->event.event_id != NOTIFY_SP_CHECK_MISSPELLINGS) + return -1; + + SpellCheckInfo callerInfo = sSpellCheckQueue->dequeue(); + spannable_string_t* spellCheckData = *event->data; + QString text = QString::fromWCharArray(spellCheckData->str, spellCheckData->length); + // Generate the list of indices indicating misspelled words in the text. We use end + 1 + // since it's more conventional to have the end index point just past the string. We also + // can't use the indices directly since they are relative to UTF-32 encoded data and the + // conversion to Qt's UTF-16 internal format might cause lengthening. + QList indices; + int adjustment = 0; + int index = 0; + for (unsigned int i = 0; i < spellCheckData->spans_count; ++i) { + if (spellCheckData->spans[i].attributes_mask & MISSPELLED_WORD_ATTRIB) { + index = adjustIndex(text.data(), spellCheckData->spans[i].start, index, &adjustment); + indices.push_back(index); + index = adjustIndex(text.data(), spellCheckData->spans[i].end + 1, index, &adjustment); + indices.push_back(index); + } + } + callerInfo.spellCheckDone(callerInfo.context, text, indices); + + return 0; +} + int32_t QQnxInputContext::processEvent(event_t *event) { int32_t result = -1; switch (event->event_type) { case EVENT_SPELL_CHECK: { qInputContextDebug() << Q_FUNC_INFO << "EVENT_SPELL_CHECK"; - result = 0; + result = handleSpellCheck(reinterpret_cast(event)); break; } @@ -1101,7 +1151,7 @@ int32_t QQnxInputContext::onCommitText(spannable_string_t *text, int32_t new_cur { Q_UNUSED(new_cursor_position); - m_composingText = QString::fromWCharArray(text->str, text->length); + updateComposition(text, new_cursor_position); finishComposingText(); return 0; @@ -1195,13 +1245,6 @@ int32_t QQnxInputContext::onSendEvent(event_t *event) return processEvent(event); } -int32_t QQnxInputContext::onSendAsyncEvent(event_t *event) -{ - qInputContextDebug() << Q_FUNC_INFO; - - return processEvent(event); -} - int32_t QQnxInputContext::onSetComposingRegion(int32_t start, int32_t end) { QObject *input = qGuiApp->focusObject(); @@ -1317,10 +1360,32 @@ void QQnxInputContext::keyboardLocaleChanged(const QLocale &locale) } } +void QQnxInputContext::setHighlightColor(int index, const QColor &color) +{ + qInputContextDebug() << Q_FUNC_INFO << "setHighlightColor" << index << color << qGuiApp->focusObject(); + + if (!sInputContextInstance) + return; + + // If the focus has changed, revert all colors to the default. + if (sInputContextInstance->m_focusObject != qGuiApp->focusObject()) { + QColor invalidColor; + sInputContextInstance->m_highlightColor[ActiveRegion] = sSelectedColor; + sInputContextInstance->m_highlightColor[AutoCorrected] = invalidColor; + sInputContextInstance->m_highlightColor[Reverted] = invalidColor; + sInputContextInstance->m_focusObject = qGuiApp->focusObject(); + } + if (index >= 0 && index <= Reverted) + sInputContextInstance->m_highlightColor[index] = color; +} + void QQnxInputContext::setFocusObject(QObject *object) { qInputContextDebug() << Q_FUNC_INFO << "input item=" << object; + // Ensure the colors are reset if we've a change in focus object + setHighlightColor(-1, QColor()); + if (!inputMethodAccepted()) { if (m_inputPanelVisible) hideInputPanel(); @@ -1340,4 +1405,29 @@ void QQnxInputContext::setFocusObject(QObject *object) } } +bool QQnxInputContext::checkSpelling(const QString &text, void *context, void (*spellCheckDone)(void *context, const QString &text, const QList &indices)) +{ + qInputContextDebug() << Q_FUNC_INFO << "text" << text; + + if (!imfAvailable()) + return false; + + if (!sSpellCheckSession) + sSpellCheckSession = p_ictrl_open_session(&ic_funcs); + + action_event_t spellEvent; + initEvent(&spellEvent.event, sSpellCheckSession, EVENT_ACTION, ACTION_CHECK_MISSPELLINGS, sizeof(spellEvent)); + int len = text.length(); + spellEvent.event_data = alloca(sizeof(wchar_t) * (len + 1)); + spellEvent.length_data = text.toWCharArray(static_cast(spellEvent.event_data)) * sizeof(wchar_t); + + int rc = p_ictrl_dispatch_event(reinterpret_cast(&spellEvent)); + + if (rc == 0) { + sSpellCheckQueue->enqueue(SpellCheckInfo(context, spellCheckDone)); + return true; + } + return false; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/qnx/qqnxinputcontext_imf.h b/src/plugins/platforms/qnx/qqnxinputcontext_imf.h index cda4f2e88e3..5028215bbec 100644 --- a/src/plugins/platforms/qnx/qqnxinputcontext_imf.h +++ b/src/plugins/platforms/qnx/qqnxinputcontext_imf.h @@ -47,6 +47,7 @@ #include #include +#include #include #include "imf/imf_client.h" @@ -65,6 +66,13 @@ public: explicit QQnxInputContext(QQnxIntegration *integration, QQnxAbstractVirtualKeyboard &keyboard); ~QQnxInputContext(); + // Indices for selecting and setting highlight colors. + enum HighlightIndex { + ActiveRegion, + AutoCorrected, + Reverted, + }; + bool isValid() const; bool filterEvent(const QEvent *event); @@ -82,6 +90,10 @@ public: QLocale locale() const; void setFocusObject(QObject *object); + static void setHighlightColor(int index, const QColor &color); + + static bool checkSpelling(const QString &text, void *context, void (*spellCheckDone)(void *context, const QString &text, const QList &indices)); + private Q_SLOTS: void keyboardVisibilityChanged(bool visible); void keyboardLocaleChanged(const QLocale &locale); @@ -93,6 +105,7 @@ private: void dispatchFocusLossEvent(); bool dispatchRequestSoftwareInputPanel(); bool dispatchCloseSoftwareInputPanel(); + int handleSpellCheck(spell_check_event_t *event); int32_t processEvent(event_t *event); void closeSession(); @@ -113,7 +126,6 @@ private: spannable_string_t *onGetTextAfterCursor(int32_t n, int32_t flags); spannable_string_t *onGetTextBeforeCursor(int32_t n, int32_t flags); int32_t onSendEvent(event_t *event); - int32_t onSendAsyncEvent(event_t *event); int32_t onSetComposingRegion(int32_t start, int32_t end); int32_t onSetComposingText(spannable_string_t *text, int32_t new_cursor_position); int32_t onIsTextSelected(int32_t* pIsSelected); @@ -126,6 +138,10 @@ private: bool m_isUpdatingText; bool m_inputPanelVisible; QLocale m_inputPanelLocale; + // The object that had focus when the last highlight color was set. + QObject *m_focusObject; + // Indexed by HighlightIndex + QColor m_highlightColor[3]; QQnxIntegration *m_integration; QQnxAbstractVirtualKeyboard &m_virtualKeyboard; }; diff --git a/src/plugins/platforms/qnx/qqnxnativeinterface.cpp b/src/plugins/platforms/qnx/qqnxnativeinterface.cpp index e468b051cd9..d1ceebfebc5 100644 --- a/src/plugins/platforms/qnx/qqnxnativeinterface.cpp +++ b/src/plugins/platforms/qnx/qqnxnativeinterface.cpp @@ -44,6 +44,9 @@ #include "qqnxglcontext.h" #include "qqnxscreen.h" #include "qqnxwindow.h" +#if defined(QQNX_IMF) +#include "qqnxinputcontext_imf.h" +#endif #include #include @@ -91,4 +94,15 @@ void QQnxNativeInterface::setWindowProperty(QPlatformWindow *window, const QStri } } +QPlatformNativeInterface::NativeResourceForIntegrationFunction QQnxNativeInterface::nativeResourceFunctionForIntegration(const QByteArray &resource) +{ +#if defined(QQNX_IMF) + if (resource == "blackberryIMFSetHighlightColor") + return reinterpret_cast(QQnxInputContext::setHighlightColor); + if (resource == "blackberryIMFCheckSpelling") + return reinterpret_cast(QQnxInputContext::checkSpelling); +#endif + return 0; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/qnx/qqnxnativeinterface.h b/src/plugins/platforms/qnx/qqnxnativeinterface.h index dfd386214e2..e2fdd32689b 100644 --- a/src/plugins/platforms/qnx/qqnxnativeinterface.h +++ b/src/plugins/platforms/qnx/qqnxnativeinterface.h @@ -51,8 +51,10 @@ class QQnxNativeInterface : public QPlatformNativeInterface public: void *nativeResourceForWindow(const QByteArray &resource, QWindow *window); void *nativeResourceForScreen(const QByteArray &resource, QScreen *screen); + void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context); void setWindowProperty(QPlatformWindow *window, const QString &name, const QVariant &value); + NativeResourceForIntegrationFunction nativeResourceFunctionForIntegration(const QByteArray &resource); }; QT_END_NAMESPACE