summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp
diff options
context:
space:
mode:
authorRoger Maclean <rmaclean@qnx.com>2013-11-12 11:58:42 -0500
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-12-24 19:17:36 +0100
commit8a5c261954836e2aeac53f5e6581135ffaa9a2e0 (patch)
treeedc7eb5c203f571ddce7026e177a65d337d29632 /src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp
parent432674d787124ecfd6f3e1cca74f13f0f0096e53 (diff)
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 <rmaclean@qnx.com> Reviewed-by: Fabian Bumberger <fbumberger@rim.com> Reviewed-by: Rafael Roquetto <rafael.roquetto@kdab.com> Reviewed-by: Kevin Krammer <kevin.krammer@kdab.com>
Diffstat (limited to 'src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp')
-rw-r--r--src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp196
1 files changed, 143 insertions, 53 deletions
diff --git a/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp b/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp
index dd47d37af3..619883e843 100644
--- a/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp
+++ b/src/plugins/platforms/qnx/qqnxinputcontext_imf.cpp
@@ -54,6 +54,8 @@
#include <QtCore/QVariant>
#include <QtCore/QVariantHash>
#include <QtCore/QWaitCondition>
+#include <QtCore/QQueue>
+#include <QtCore/QGlobalStatic>
#include <dlfcn.h>
#include "imf/imf_client.h"
@@ -73,14 +75,10 @@
#define qInputContextDebug QT_NO_QDEBUG_MACRO
#endif
-#include <QtGui/QAccessible>
-#include <QtGui/private/qaccessible2_p.h>
-
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<int> &))
+ : context(_context), spellCheckDone(_spellCheckDone) {}
+ void *context;
+ void (*spellCheckDone)(void *, const QString &, const QList<int> &);
+};
+Q_GLOBAL_STATIC(QQueue<SpellCheckInfo>, 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<spannable_string_t *>(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<spannable_string_t *>(malloc(sizeof(spannable_string_t)));
+ pString->str = static_cast<wchar_t *>(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;
-
- if (text->spans[i].attributes_mask & knownMask) {
- QTextCharFormat format;
+ QColor highlightColor;
+ bool underline = false;
+
+ 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 (converted || selected) {
+ if (underline || highlightColor.isValid()) {
+ QTextCharFormat format;
+ 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<int> 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<spell_check_event_t *>(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<int> &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<wchar_t*>(spellEvent.event_data)) * sizeof(wchar_t);
+
+ int rc = p_ictrl_dispatch_event(reinterpret_cast<event_t*>(&spellEvent));
+
+ if (rc == 0) {
+ sSpellCheckQueue->enqueue(SpellCheckInfo(context, spellCheckDone));
+ return true;
+ }
+ return false;
+}
+
QT_END_NAMESPACE