| /*************************************************************************** |
| ** |
| ** Copyright (C) 2013 BlackBerry Limited. All rights reserved. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** 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 Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or 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.GPL2 and 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-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qqnxinputcontext_imf.h" |
| #include "qqnxabstractvirtualkeyboard.h" |
| #include "qqnxintegration.h" |
| #include "qqnxscreen.h" |
| #include "qqnxscreeneventhandler.h" |
| |
| #include <QtGui/QGuiApplication> |
| #include <QtGui/QInputMethodEvent> |
| #include <QtGui/QTextCharFormat> |
| |
| #include <QtCore/QDebug> |
| #include <QtCore/QMutex> |
| #include <QtCore/QVariant> |
| #include <QtCore/QVariantHash> |
| #include <QtCore/QWaitCondition> |
| #include <QtCore/QQueue> |
| #include <QtCore/QGlobalStatic> |
| |
| #include <dlfcn.h> |
| #include "imf/imf_client.h" |
| #include "imf/input_control.h" |
| #include <process.h> |
| #include <sys/keycodes.h> |
| |
| #if defined(QQNXINPUTCONTEXT_IMF_EVENT_DEBUG) |
| #define qInputContextIMFRequestDebug qDebug |
| #else |
| #define qInputContextIMFRequestDebug QT_NO_QDEBUG_MACRO |
| #endif |
| |
| #if defined(QQNXINPUTCONTEXT_DEBUG) |
| #define qInputContextDebug qDebug |
| #else |
| #define qInputContextDebug QT_NO_QDEBUG_MACRO |
| #endif |
| |
| static QQnxInputContext *sInputContextInstance; |
| static QColor sSelectedColor(0,0xb8,0,85); |
| |
| static const input_session_t *sSpellCheckSession = 0; |
| static const input_session_t *sInputSession = 0; |
| static bool isSessionOkay(input_session_t *ic) |
| { |
| return ic !=0 && sInputSession != 0 && ic->component_id == sInputSession->component_id; |
| } |
| |
| enum ImfEventType |
| { |
| ImfCommitText, |
| ImfDeleteSurroundingText, |
| ImfFinishComposingText, |
| ImfGetCursorPosition, |
| ImfGetTextAfterCursor, |
| ImfGetTextBeforeCursor, |
| ImfSendEvent, |
| 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 |
| { |
| public: |
| QQnxImfRequest(input_session_t *_session, ImfEventType _type) |
| : session(_session), type(_type) |
| { } |
| ~QQnxImfRequest() { } |
| |
| input_session_t *session; |
| ImfEventType type; |
| union { |
| struct { |
| int32_t n; |
| int32_t flags; |
| bool before; |
| spannable_string_t *result; |
| } gtac; // ic_get_text_before_cursor/ic_get_text_after_cursor |
| struct { |
| int32_t result; |
| } gcp; // ic_get_cursor_position |
| struct { |
| int32_t start; |
| int32_t end; |
| int32_t result; |
| } scr; // ic_set_composing_region |
| struct { |
| spannable_string_t* text; |
| int32_t new_cursor_position; |
| int32_t result; |
| } sct; // ic_set_composing_text |
| struct { |
| spannable_string_t* text; |
| int32_t new_cursor_position; |
| int32_t result; |
| } ct; // ic_commit_text |
| struct { |
| int32_t result; |
| } fct; // ic_finish_composing_text |
| struct { |
| int32_t left_length; |
| int32_t right_length; |
| int32_t result; |
| } dst; // ic_delete_surrounding_text |
| struct { |
| event_t *event; |
| int32_t result; |
| } sae; // ic_send_async_event/ic_send_event |
| struct { |
| int32_t *pIsSelected; |
| int32_t result; |
| } its; // ic_is_text_selected/ic_is_all_text_selected |
| }; |
| }; |
| |
| // Invoke an IMF initiated request synchronously on Qt's main thread. As describe below all |
| // IMF requests are made from another thread but need to be executed on the main thread. |
| static void executeIMFRequest(QQnxImfRequest *event) |
| { |
| QMetaObject::invokeMethod(sInputContextInstance, |
| "processImfEvent", |
| Qt::BlockingQueuedConnection, |
| Q_ARG(QQnxImfRequest*, event)); |
| } |
| |
| // The following functions (ic_*) are callback functions called by the input system to query information |
| // about the text object that currently has focus or to make changes to it. All calls are made from the |
| // input system's own thread. The pattern for each callback function is to copy its parameters into |
| // a QQnxImfRequest structure and call executeIMFRequest to have it passed synchronously to Qt's main thread. |
| // Any return values should be pre-initialised with suitable default values as in some cases |
| // (e.g. a stale session) the call will return without having executed any request specific code. |
| // |
| // To make the correspondence more obvious, the names of these functions match those defined in the headers. |
| // They're in an anonymous namespace to avoid compiler conflicts with external functions defined with the |
| // same names. |
| namespace |
| { |
| |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_begin_batch_edit(input_session_t *ic) |
| { |
| Q_UNUSED(ic); |
| |
| // Ignore silently. |
| return 0; |
| } |
| |
| // End composition, committing the supplied text. |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_commit_text(input_session_t *ic, spannable_string_t *text, int32_t new_cursor_position) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfCommitText); |
| event.ct.text = text; |
| event.ct.new_cursor_position = new_cursor_position; |
| event.ct.result = -1; |
| executeIMFRequest(&event); |
| |
| return event.ct.result; |
| } |
| |
| // Delete left_length characters before and right_length characters after the cursor. |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_delete_surrounding_text(input_session_t *ic, int32_t left_length, int32_t right_length) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfDeleteSurroundingText); |
| event.dst.left_length = left_length; |
| event.dst.right_length = right_length; |
| event.dst.result = -1; |
| executeIMFRequest(&event); |
| |
| return event.dst.result; |
| } |
| |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_end_batch_edit(input_session_t *ic) |
| { |
| Q_UNUSED(ic); |
| |
| // Ignore silently. |
| return 0; |
| } |
| |
| // End composition, committing what's there. |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_finish_composing_text(input_session_t *ic) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfFinishComposingText); |
| event.fct.result = -1; |
| executeIMFRequest(&event); |
| |
| return event.fct.result; |
| } |
| |
| // Return the position of the cursor. |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_get_cursor_position(input_session_t *ic) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfGetCursorPosition); |
| event.gcp.result = -1; |
| executeIMFRequest(&event); |
| |
| return event.gcp.result; |
| } |
| |
| // Return the n characters after the cursor. |
| // See comment at beginning of namespace declaration for general information |
| static spannable_string_t *ic_get_text_after_cursor(input_session_t *ic, int32_t n, int32_t flags) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfGetTextAfterCursor); |
| event.gtac.n = n; |
| event.gtac.flags = flags; |
| event.gtac.result = 0; |
| executeIMFRequest(&event); |
| |
| return event.gtac.result; |
| } |
| |
| // Return the n characters before the cursor. |
| // See comment at beginning of namespace declaration for general information |
| static spannable_string_t *ic_get_text_before_cursor(input_session_t *ic, int32_t n, int32_t flags) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfGetTextBeforeCursor); |
| event.gtac.n = n; |
| event.gtac.flags = flags; |
| event.gtac.result = 0; |
| executeIMFRequest(&event); |
| |
| return event.gtac.result; |
| } |
| |
| // Process an event from IMF. Primarily used for reflecting back keyboard events. |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_send_event(input_session_t *ic, event_t *event) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest imfEvent(ic, ImfSendEvent); |
| imfEvent.sae.event = event; |
| imfEvent.sae.result = -1; |
| executeIMFRequest(&imfEvent); |
| |
| return imfEvent.sae.result; |
| } |
| |
| // Same as ic_send_event. |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_send_async_event(input_session_t *ic, event_t *event) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| // 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); |
| |
| return imfEvent.sae.result; |
| } |
| |
| // Set the range of text between start and end as the composition range. |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_set_composing_region(input_session_t *ic, int32_t start, int32_t end) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfSetComposingRegion); |
| event.scr.start = start; |
| event.scr.end = end; |
| event.scr.result = -1; |
| executeIMFRequest(&event); |
| |
| return event.scr.result; |
| } |
| |
| // Update the composition range with the supplied text. This can be called when no composition |
| // range is in effect in which case one is started at the current cursor position. |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_set_composing_text(input_session_t *ic, spannable_string_t *text, int32_t new_cursor_position) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfSetComposingText); |
| event.sct.text = text; |
| event.sct.new_cursor_position = new_cursor_position; |
| event.sct.result = -1; |
| executeIMFRequest(&event); |
| |
| return event.sct.result; |
| } |
| |
| // Indicate if any text is selected |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_is_text_selected(input_session_t* ic, int32_t* pIsSelected) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfIsTextSelected); |
| event.its.pIsSelected = pIsSelected; |
| event.its.result = -1; |
| executeIMFRequest(&event); |
| |
| return event.its.result; |
| } |
| |
| // Indicate if all text is selected |
| // See comment at beginning of namespace declaration for general information |
| static int32_t ic_is_all_text_selected(input_session_t* ic, int32_t* pIsSelected) |
| { |
| qInputContextIMFRequestDebug(); |
| |
| QQnxImfRequest event(ic, ImfIsAllTextSelected); |
| event.its.pIsSelected = pIsSelected; |
| event.its.result = -1; |
| executeIMFRequest(&event); |
| |
| 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 |
| static int32_t ic_perform_editor_action(input_session_t *ic, int32_t editor_action) |
| { |
| Q_UNUSED(ic); |
| Q_UNUSED(editor_action); |
| |
| qCritical("ic_perform_editor_action not implemented"); |
| return 0; |
| } |
| |
| // Not currently used |
| static int32_t ic_report_fullscreen_mode(input_session_t *ic, int32_t enabled) |
| { |
| Q_UNUSED(ic); |
| Q_UNUSED(enabled); |
| |
| qCritical("ic_report_fullscreen_mode not implemented"); |
| return 0; |
| } |
| |
| // Not currently used |
| static extracted_text_t *ic_get_extracted_text(input_session_t *ic, extracted_text_request_t *request, int32_t flags) |
| { |
| Q_UNUSED(ic); |
| Q_UNUSED(request); |
| Q_UNUSED(flags); |
| |
| qCritical("ic_get_extracted_text not implemented"); |
| return 0; |
| } |
| |
| // Not currently used |
| static spannable_string_t *ic_get_selected_text(input_session_t *ic, int32_t flags) |
| { |
| Q_UNUSED(ic); |
| Q_UNUSED(flags); |
| |
| qCritical("ic_get_selected_text not implemented"); |
| return 0; |
| } |
| |
| // Not currently used |
| static int32_t ic_get_cursor_caps_mode(input_session_t *ic, int32_t req_modes) |
| { |
| Q_UNUSED(ic); |
| Q_UNUSED(req_modes); |
| |
| qCritical("ic_get_cursor_caps_mode not implemented"); |
| return 0; |
| } |
| |
| // Not currently used |
| static int32_t ic_clear_meta_key_states(input_session_t *ic, int32_t states) |
| { |
| Q_UNUSED(ic); |
| Q_UNUSED(states); |
| |
| qCritical("ic_clear_meta_key_states not implemented"); |
| return 0; |
| } |
| |
| // Not currently used |
| static int32_t ic_set_selection(input_session_t *ic, int32_t start, int32_t end) |
| { |
| Q_UNUSED(ic); |
| Q_UNUSED(start); |
| Q_UNUSED(end); |
| |
| qCritical("ic_set_selection not implemented"); |
| return 0; |
| } |
| |
| // End of un-hittable code |
| // LCOV_EXCL_STOP |
| |
| |
| static connection_interface_t ic_funcs = { |
| ic_begin_batch_edit, |
| ic_clear_meta_key_states, |
| ic_commit_text, |
| ic_delete_surrounding_text, |
| ic_end_batch_edit, |
| ic_finish_composing_text, |
| ic_get_cursor_caps_mode, |
| ic_get_cursor_position, |
| ic_get_extracted_text, |
| ic_get_selected_text, |
| ic_get_text_after_cursor, |
| ic_get_text_before_cursor, |
| ic_perform_editor_action, |
| ic_report_fullscreen_mode, |
| 0, //ic_send_key_event |
| ic_send_event, |
| ic_send_async_event, |
| ic_set_composing_region, |
| ic_set_composing_text, |
| ic_set_selection, |
| 0, //ic_set_candidates, |
| 0, //ic_get_cursor_offset, |
| 0, //ic_get_selection, |
| ic_is_text_selected, |
| ic_is_all_text_selected, |
| 0, //ic_get_max_cursor_offset_t |
| }; |
| |
| } // namespace |
| |
| static void |
| initEvent(event_t *pEvent, const input_session_t *pSession, EventType eventType, int eventId, int eventSize) |
| { |
| static int s_transactionId; |
| |
| // Make sure structure is squeaky clean since it's not clear just what is significant. |
| memset(pEvent, 0, eventSize); |
| pEvent->event_type = eventType; |
| pEvent->event_id = eventId; |
| pEvent->pid = getpid(); |
| pEvent->component_id = pSession->component_id; |
| pEvent->transaction_id = ++s_transactionId; |
| } |
| |
| static spannable_string_t *toSpannableString(const QString &text) |
| { |
| qInputContextDebug() << text; |
| |
| 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; |
| pString->str[pString->length] = 0; |
| |
| return pString; |
| } |
| |
| |
| static const input_session_t *(*p_ictrl_open_session)(connection_interface_t *) = 0; |
| static void (*p_ictrl_close_session)(input_session_t *) = 0; |
| static int32_t (*p_ictrl_dispatch_event)(event_t*) = 0; |
| static int32_t (*p_imf_client_init)() = 0; |
| static void (*p_imf_client_disconnect)() = 0; |
| static int32_t (*p_vkb_init_selection_service)() = 0; |
| static int32_t (*p_ictrl_get_num_active_sessions)() = 0; |
| static bool s_imfInitFailed = false; |
| |
| static bool imfAvailable() |
| { |
| static bool s_imfDisabled = getenv("DISABLE_IMF") != 0; |
| static bool s_imfReady = false; |
| |
| if ( s_imfInitFailed || s_imfDisabled) |
| return false; |
| else if ( s_imfReady ) |
| return true; |
| |
| if ( p_imf_client_init == 0 ) { |
| void *handle = dlopen("libinput_client.so.1", 0); |
| if (Q_UNLIKELY(!handle)) { |
| qCritical("libinput_client.so.1 is not present - IMF services are disabled."); |
| s_imfDisabled = true; |
| return false; |
| } |
| p_imf_client_init = (int32_t (*)()) dlsym(handle, "imf_client_init"); |
| p_imf_client_disconnect = (void (*)()) dlsym(handle, "imf_client_disconnect"); |
| p_ictrl_open_session = (const input_session_t *(*)(connection_interface_t *))dlsym(handle, "ictrl_open_session"); |
| p_ictrl_close_session = (void (*)(input_session_t *))dlsym(handle, "ictrl_close_session"); |
| p_ictrl_dispatch_event = (int32_t (*)(event_t *))dlsym(handle, "ictrl_dispatch_event"); |
| p_vkb_init_selection_service = (int32_t (*)())dlsym(handle, "vkb_init_selection_service"); |
| p_ictrl_get_num_active_sessions = (int32_t (*)())dlsym(handle, "ictrl_get_num_active_sessions"); |
| |
| if (Q_UNLIKELY(!p_imf_client_init || !p_ictrl_open_session || !p_ictrl_dispatch_event)) { |
| p_ictrl_open_session = 0; |
| p_ictrl_dispatch_event = 0; |
| s_imfDisabled = true; |
| qCritical("libinput_client.so.1 did not contain the correct symbols, library mismatch? IMF services are disabled."); |
| return false; |
| } |
| |
| s_imfReady = true; |
| } |
| |
| return s_imfReady; |
| } |
| |
| QT_BEGIN_NAMESPACE |
| |
| QQnxInputContext::QQnxInputContext(QQnxIntegration *integration, QQnxAbstractVirtualKeyboard &keyboard) : |
| QPlatformInputContext(), |
| m_caretPosition(0), |
| m_isComposing(false), |
| m_isUpdatingText(false), |
| m_inputPanelVisible(false), |
| m_inputPanelLocale(QLocale::c()), |
| m_focusObject(0), |
| m_integration(integration), |
| m_virtualKeyboard(keyboard) |
| { |
| qInputContextDebug(); |
| |
| if (!imfAvailable()) |
| return; |
| |
| // Save a pointer to ourselves so we can execute calls from IMF through executeIMFRequest |
| // In practice there will only ever be a single instance. |
| Q_ASSERT(sInputContextInstance == 0); |
| sInputContextInstance = this; |
| |
| if (Q_UNLIKELY(p_imf_client_init() != 0)) { |
| s_imfInitFailed = true; |
| qCritical("imf_client_init failed - IMF services will be unavailable"); |
| } |
| |
| connect(&keyboard, SIGNAL(visibilityChanged(bool)), this, SLOT(keyboardVisibilityChanged(bool))); |
| connect(&keyboard, SIGNAL(localeChanged(QLocale)), this, SLOT(keyboardLocaleChanged(QLocale))); |
| keyboardVisibilityChanged(keyboard.isVisible()); |
| keyboardLocaleChanged(keyboard.locale()); |
| } |
| |
| QQnxInputContext::~QQnxInputContext() |
| { |
| qInputContextDebug(); |
| |
| Q_ASSERT(sInputContextInstance == this); |
| sInputContextInstance = 0; |
| |
| if (!imfAvailable()) |
| return; |
| |
| p_imf_client_disconnect(); |
| } |
| |
| bool QQnxInputContext::isValid() const |
| { |
| return imfAvailable(); |
| } |
| |
| void QQnxInputContext::processImfEvent(QQnxImfRequest *imfEvent) |
| { |
| // If input session is no longer current, just bail, imfEvent should already be set with the appropriate |
| // 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: |
| imfEvent->ct.result = onCommitText(imfEvent->ct.text, imfEvent->ct.new_cursor_position); |
| break; |
| |
| case ImfDeleteSurroundingText: |
| imfEvent->dst.result = onDeleteSurroundingText(imfEvent->dst.left_length, imfEvent->dst.right_length); |
| break; |
| |
| case ImfFinishComposingText: |
| imfEvent->fct.result = onFinishComposingText(); |
| break; |
| |
| case ImfGetCursorPosition: |
| imfEvent->gcp.result = onGetCursorPosition(); |
| break; |
| |
| case ImfGetTextAfterCursor: |
| imfEvent->gtac.result = onGetTextAfterCursor(imfEvent->gtac.n, imfEvent->gtac.flags); |
| break; |
| |
| case ImfGetTextBeforeCursor: |
| imfEvent->gtac.result = onGetTextBeforeCursor(imfEvent->gtac.n, imfEvent->gtac.flags); |
| break; |
| |
| case ImfSendEvent: |
| imfEvent->sae.result = onSendEvent(imfEvent->sae.event); |
| break; |
| |
| case ImfSetComposingRegion: |
| imfEvent->scr.result = onSetComposingRegion(imfEvent->scr.start, imfEvent->scr.end); |
| break; |
| |
| case ImfSetComposingText: |
| imfEvent->sct.result = onSetComposingText(imfEvent->sct.text, imfEvent->sct.new_cursor_position); |
| break; |
| |
| case ImfIsTextSelected: |
| imfEvent->its.result = onIsTextSelected(imfEvent->its.pIsSelected); |
| break; |
| |
| case ImfIsAllTextSelected: |
| imfEvent->its.result = onIsAllTextSelected(imfEvent->its.pIsSelected); |
| break; |
| }; //switch |
| } |
| |
| bool QQnxInputContext::filterEvent( const QEvent *event ) |
| { |
| qInputContextDebug() << event; |
| |
| switch (event->type()) { |
| case QEvent::CloseSoftwareInputPanel: |
| return dispatchCloseSoftwareInputPanel(); |
| |
| case QEvent::RequestSoftwareInputPanel: |
| return dispatchRequestSoftwareInputPanel(); |
| |
| default: |
| return false; |
| } |
| } |
| |
| QRectF QQnxInputContext::keyboardRect() const |
| { |
| QRect screenGeometry = m_integration->primaryDisplay()->geometry(); |
| return QRectF(screenGeometry.x(), screenGeometry.height() - m_virtualKeyboard.height(), |
| screenGeometry.width(), m_virtualKeyboard.height()); |
| } |
| |
| void QQnxInputContext::reset() |
| { |
| qInputContextDebug(); |
| endComposition(); |
| } |
| |
| void QQnxInputContext::commit() |
| { |
| qInputContextDebug(); |
| endComposition(); |
| } |
| |
| void QQnxInputContext::update(Qt::InputMethodQueries queries) |
| { |
| qInputContextDebug() << queries; |
| |
| if (queries & Qt::ImCursorPosition) { |
| int lastCaret = m_caretPosition; |
| updateCursorPosition(); |
| // If caret position has changed we need to inform IMF unless this is just due to our own action |
| // such as committing text. |
| if (hasSession() && !m_isUpdatingText && lastCaret != m_caretPosition) { |
| caret_event_t caretEvent; |
| initEvent(&caretEvent.event, sInputSession, EVENT_CARET, CARET_POS_CHANGED, sizeof(caretEvent)); |
| caretEvent.old_pos = lastCaret; |
| caretEvent.new_pos = m_caretPosition; |
| qInputContextDebug("ictrl_dispatch_event caret changed %d %d", lastCaret, m_caretPosition); |
| p_ictrl_dispatch_event(&caretEvent.event); |
| } |
| } |
| } |
| |
| void QQnxInputContext::closeSession() |
| { |
| qInputContextDebug(); |
| if (!imfAvailable()) |
| return; |
| |
| if (sInputSession) { |
| p_ictrl_close_session((input_session_t *)sInputSession); |
| sInputSession = 0; |
| } |
| // These are likely already in the right state but this depends on the text control |
| // having called reset or commit. So, just in case, set them to proper values. |
| m_isComposing = false; |
| m_composingText.clear(); |
| } |
| |
| bool QQnxInputContext::openSession() |
| { |
| if (!imfAvailable()) |
| return false; |
| |
| closeSession(); |
| sInputSession = p_ictrl_open_session(&ic_funcs); |
| |
| qInputContextDebug(); |
| |
| return sInputSession != 0; |
| } |
| |
| bool QQnxInputContext::hasSession() |
| { |
| return sInputSession != 0; |
| } |
| |
| bool QQnxInputContext::hasSelectedText() |
| { |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return false; |
| |
| QInputMethodQueryEvent query(Qt::ImCurrentSelection); |
| QCoreApplication::sendEvent(input, &query); |
| |
| return !query.value(Qt::ImCurrentSelection).toString().isEmpty(); |
| } |
| |
| bool QQnxInputContext::dispatchRequestSoftwareInputPanel() |
| { |
| qInputContextDebug() << "requesting keyboard" << m_inputPanelVisible; |
| m_virtualKeyboard.showKeyboard(); |
| |
| return true; |
| } |
| |
| bool QQnxInputContext::dispatchCloseSoftwareInputPanel() |
| { |
| qInputContextDebug() << "hiding keyboard" << m_inputPanelVisible; |
| m_virtualKeyboard.hideKeyboard(); |
| |
| return true; |
| } |
| |
| /** |
| * IMF Event Dispatchers. |
| */ |
| bool QQnxInputContext::dispatchFocusGainEvent(int inputHints) |
| { |
| if (hasSession()) |
| dispatchFocusLossEvent(); |
| |
| QObject *input = qGuiApp->focusObject(); |
| |
| if (!input || !openSession()) |
| return false; |
| |
| // Set the last caret position to 0 since we don't really have one and we don't |
| // want to have the old one. |
| m_caretPosition = 0; |
| |
| QInputMethodQueryEvent query(Qt::ImHints); |
| QCoreApplication::sendEvent(input, &query); |
| |
| focus_event_t focusEvent; |
| initEvent(&focusEvent.event, sInputSession, EVENT_FOCUS, FOCUS_GAINED, sizeof(focusEvent)); |
| focusEvent.style = DEFAULT_STYLE; |
| |
| if (inputHints & Qt::ImhNoPredictiveText) |
| focusEvent.style |= NO_PREDICTION | NO_AUTO_CORRECTION; |
| if (inputHints & Qt::ImhNoAutoUppercase) |
| focusEvent.style |= NO_AUTO_TEXT; |
| |
| // Following styles are mutually exclusive |
| if (inputHints & Qt::ImhHiddenText) { |
| focusEvent.style |= IMF_PASSWORD_TYPE; |
| } else if (inputHints & Qt::ImhDialableCharactersOnly) { |
| focusEvent.style |= IMF_PHONE_TYPE; |
| } else if (inputHints & Qt::ImhUrlCharactersOnly) { |
| focusEvent.style |= IMF_URL_TYPE; |
| } else if (inputHints & Qt::ImhEmailCharactersOnly) { |
| focusEvent.style |= IMF_EMAIL_TYPE; |
| } |
| |
| qInputContextDebug() << "ictrl_dispatch_event focus gain style:" << focusEvent.style; |
| |
| p_ictrl_dispatch_event((event_t *)&focusEvent); |
| |
| return true; |
| } |
| |
| void QQnxInputContext::dispatchFocusLossEvent() |
| { |
| if (hasSession()) { |
| qInputContextDebug("ictrl_dispatch_event focus lost"); |
| |
| focus_event_t focusEvent; |
| initEvent(&focusEvent.event, sInputSession, EVENT_FOCUS, FOCUS_LOST, sizeof(focusEvent)); |
| p_ictrl_dispatch_event((event_t *)&focusEvent); |
| closeSession(); |
| } |
| } |
| |
| bool QQnxInputContext::handleKeyboardEvent(int flags, int sym, int mod, int scan, int cap, int sequenceId) |
| { |
| Q_UNUSED(scan); |
| |
| if (!hasSession()) |
| return false; |
| |
| int key = (flags & KEY_SYM_VALID) ? sym : cap; |
| bool navigationKey = false; |
| switch (key) { |
| case KEYCODE_RETURN: |
| /* In a single line edit we should end composition because enter might be used by something. |
| endComposition(); |
| return false;*/ |
| break; |
| |
| case KEYCODE_BACKSPACE: |
| case KEYCODE_DELETE: |
| // If there is a selection range, then we want a delete key to operate on that (by |
| // deleting the contents of the select range) rather than operating on the composition |
| // range. |
| if (hasSelectedText()) |
| return false; |
| break; |
| case KEYCODE_LEFT: |
| key = NAVIGATE_LEFT; |
| navigationKey = true; |
| break; |
| case KEYCODE_RIGHT: |
| key = NAVIGATE_RIGHT; |
| navigationKey = true; |
| break; |
| case KEYCODE_UP: |
| key = NAVIGATE_UP; |
| navigationKey = true; |
| break; |
| case KEYCODE_DOWN: |
| key = NAVIGATE_DOWN; |
| navigationKey = true; |
| break; |
| case KEYCODE_LEFT_CTRL: |
| case KEYCODE_RIGHT_CTRL: |
| case KEYCODE_MENU: |
| case KEYCODE_LEFT_HYPER: |
| case KEYCODE_RIGHT_HYPER: |
| case KEYCODE_INSERT: |
| case KEYCODE_HOME: |
| case KEYCODE_PG_UP: |
| case KEYCODE_END: |
| case KEYCODE_PG_DOWN: |
| // Don't send these |
| key = 0; |
| break; |
| } |
| |
| // Pass the keys we don't know about on through |
| if ( key == 0 ) |
| return false; |
| |
| if (navigationKey) { |
| // Even if we're forwarding up events, we can't do this for |
| // navigation keys. |
| if ( flags & KEY_DOWN ) { |
| navigation_event_t navEvent; |
| initEvent(&navEvent.event, sInputSession, EVENT_NAVIGATION, key, sizeof(navEvent)); |
| navEvent.magnitude = 1; |
| qInputContextDebug("ictrl_dispatch_even navigation %d", key); |
| p_ictrl_dispatch_event(&navEvent.event); |
| } |
| } else { |
| key_event_t keyEvent; |
| initEvent(&keyEvent.event, sInputSession, EVENT_KEY, flags & KEY_DOWN ? IMF_KEY_DOWN : IMF_KEY_UP, |
| sizeof(keyEvent)); |
| keyEvent.key_code = cap; |
| keyEvent.character = sym; |
| keyEvent.meta_key_state = mod; |
| keyEvent.sequence_id = sequenceId; |
| |
| p_ictrl_dispatch_event(&keyEvent.event); |
| qInputContextDebug("ictrl_dispatch_even key %d", key); |
| } |
| |
| return true; |
| } |
| |
| void QQnxInputContext::updateCursorPosition() |
| { |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return; |
| |
| QInputMethodQueryEvent query(Qt::ImCursorPosition); |
| QCoreApplication::sendEvent(input, &query); |
| m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); |
| |
| qInputContextDebug("%d", m_caretPosition); |
| } |
| |
| void QQnxInputContext::endComposition() |
| { |
| if (!m_isComposing) |
| return; |
| |
| finishComposingText(); |
| |
| if (hasSession()) { |
| action_event_t actionEvent; |
| initEvent(&actionEvent.event, sInputSession, EVENT_ACTION, ACTION_END_COMPOSITION, sizeof(actionEvent)); |
| qInputContextDebug("ictrl_dispatch_even end composition"); |
| p_ictrl_dispatch_event(&actionEvent.event); |
| } |
| } |
| |
| void QQnxInputContext::updateComposition(spannable_string_t *text, int32_t new_cursor_position) |
| { |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return; |
| |
| if (new_cursor_position > 0) |
| new_cursor_position += text->length - 1; |
| |
| m_composingText = QString::fromWCharArray(text->str, text->length); |
| m_isComposing = true; |
| |
| qInputContextDebug() << m_composingText << new_cursor_position; |
| |
| QList<QInputMethodEvent::Attribute> attributes; |
| attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, |
| new_cursor_position, |
| 1, |
| QVariant())); |
| |
| for (unsigned int i = 0; i < text->spans_count; ++i) { |
| 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 (underline || highlightColor.isValid()) { |
| QTextCharFormat format; |
| if (underline) |
| format.setFontUnderline(true); |
| 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); |
| m_isUpdatingText = true; |
| QCoreApplication::sendEvent(input, &event); |
| m_isUpdatingText = false; |
| |
| updateCursorPosition(); |
| } |
| |
| void QQnxInputContext::finishComposingText() |
| { |
| QObject *input = qGuiApp->focusObject(); |
| |
| if (input) { |
| qInputContextDebug() << m_composingText; |
| |
| QInputMethodEvent event; |
| event.setCommitString(m_composingText); |
| m_isUpdatingText = true; |
| QCoreApplication::sendEvent(input, &event); |
| m_isUpdatingText = false; |
| } |
| m_composingText = QString(); |
| m_isComposing = false; |
| |
| 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("EVENT_SPELL_CHECK"); |
| result = handleSpellCheck(reinterpret_cast<spell_check_event_t *>(event)); |
| break; |
| } |
| |
| case EVENT_NAVIGATION: { |
| qInputContextDebug("EVENT_NAVIGATION"); |
| |
| int key = event->event_id == NAVIGATE_UP ? KEYCODE_UP : |
| event->event_id == NAVIGATE_DOWN ? KEYCODE_DOWN : |
| event->event_id == NAVIGATE_LEFT ? KEYCODE_LEFT : |
| event->event_id == NAVIGATE_RIGHT ? KEYCODE_RIGHT : 0; |
| |
| QQnxScreenEventHandler::injectKeyboardEvent(KEY_DOWN | KEY_CAP_VALID, key, 0, 0, 0); |
| QQnxScreenEventHandler::injectKeyboardEvent(KEY_CAP_VALID, key, 0, 0, 0); |
| result = 0; |
| break; |
| } |
| |
| case EVENT_KEY: { |
| key_event_t *kevent = reinterpret_cast<key_event_t *>(event); |
| int keySym = kevent->character != 0 ? kevent->character : kevent->key_code; |
| int keyCap = kevent->key_code; |
| int modifiers = 0; |
| if (kevent->meta_key_state & META_SHIFT_ON) |
| modifiers |= KEYMOD_SHIFT; |
| int flags = KEY_SYM_VALID | KEY_CAP_VALID; |
| if (event->event_id == IMF_KEY_DOWN) |
| flags |= KEY_DOWN; |
| qInputContextDebug("EVENT_KEY %d %d", flags, keySym); |
| QQnxScreenEventHandler::injectKeyboardEvent(flags, keySym, modifiers, 0, keyCap); |
| result = 0; |
| break; |
| } |
| |
| case EVENT_ACTION: |
| // Don't care, indicates that IMF is done. |
| break; |
| |
| case EVENT_CARET: |
| case EVENT_NOTHING: |
| case EVENT_FOCUS: |
| case EVENT_USER_ACTION: |
| case EVENT_STROKE: |
| case EVENT_INVOKE_LATER: |
| qCritical() << "Unsupported event type: " << event->event_type; |
| break; |
| default: |
| qCritical() << "Unknown event type: " << event->event_type; |
| } |
| return result; |
| } |
| |
| /** |
| * IMF Event Handlers |
| */ |
| |
| int32_t QQnxInputContext::onCommitText(spannable_string_t *text, int32_t new_cursor_position) |
| { |
| Q_UNUSED(new_cursor_position); |
| |
| updateComposition(text, new_cursor_position); |
| finishComposingText(); |
| |
| return 0; |
| } |
| |
| int32_t QQnxInputContext::onDeleteSurroundingText(int32_t left_length, int32_t right_length) |
| { |
| qInputContextDebug("L: %d R: %d", int(left_length), int(right_length)); |
| |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return 0; |
| |
| int replacementLength = left_length + right_length; |
| int replacementStart = -left_length; |
| |
| finishComposingText(); |
| |
| QInputMethodEvent event; |
| event.setCommitString(QString(), replacementStart, replacementLength); |
| m_isUpdatingText = true; |
| QCoreApplication::sendEvent(input, &event); |
| m_isUpdatingText = false; |
| |
| updateCursorPosition(); |
| |
| return 0; |
| } |
| |
| int32_t QQnxInputContext::onFinishComposingText() |
| { |
| finishComposingText(); |
| |
| return 0; |
| } |
| |
| int32_t QQnxInputContext::onGetCursorPosition() |
| { |
| qInputContextDebug(); |
| |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return 0; |
| |
| updateCursorPosition(); |
| |
| return m_caretPosition; |
| } |
| |
| spannable_string_t *QQnxInputContext::onGetTextAfterCursor(int32_t n, int32_t flags) |
| { |
| Q_UNUSED(flags); |
| qInputContextDebug(); |
| |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return toSpannableString(""); |
| |
| QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText); |
| QCoreApplication::sendEvent(input, &query); |
| QString text = query.value(Qt::ImSurroundingText).toString(); |
| m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); |
| |
| return toSpannableString(text.mid(m_caretPosition, n)); |
| } |
| |
| spannable_string_t *QQnxInputContext::onGetTextBeforeCursor(int32_t n, int32_t flags) |
| { |
| Q_UNUSED(flags); |
| qInputContextDebug(); |
| |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return toSpannableString(""); |
| |
| QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText); |
| QCoreApplication::sendEvent(input, &query); |
| QString text = query.value(Qt::ImSurroundingText).toString(); |
| m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); |
| |
| if (n < m_caretPosition) |
| return toSpannableString(text.mid(m_caretPosition - n, n)); |
| else |
| return toSpannableString(text.mid(0, m_caretPosition)); |
| } |
| |
| int32_t QQnxInputContext::onSendEvent(event_t *event) |
| { |
| qInputContextDebug(); |
| |
| return processEvent(event); |
| } |
| |
| int32_t QQnxInputContext::onSetComposingRegion(int32_t start, int32_t end) |
| { |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return 0; |
| |
| QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText); |
| QCoreApplication::sendEvent(input, &query); |
| QString text = query.value(Qt::ImSurroundingText).toString(); |
| m_caretPosition = query.value(Qt::ImCursorPosition).toInt(); |
| |
| qInputContextDebug() << text; |
| |
| m_isUpdatingText = true; |
| |
| // Delete the current text. |
| QInputMethodEvent deleteEvent; |
| deleteEvent.setCommitString(QString(), start - m_caretPosition, end - start); |
| QCoreApplication::sendEvent(input, &deleteEvent); |
| |
| m_composingText = text.mid(start, end - start); |
| m_isComposing = true; |
| |
| QList<QInputMethodEvent::Attribute> attributes; |
| QTextCharFormat format; |
| format.setFontUnderline(true); |
| attributes.push_back(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, m_composingText.length(), format)); |
| |
| QInputMethodEvent setTextEvent(m_composingText, attributes); |
| QCoreApplication::sendEvent(input, &setTextEvent); |
| |
| m_isUpdatingText = false; |
| |
| return 0; |
| } |
| |
| int32_t QQnxInputContext::onSetComposingText(spannable_string_t *text, int32_t new_cursor_position) |
| { |
| if (text->length > 0) { |
| updateComposition(text, new_cursor_position); |
| } else { |
| // If the composing text is empty we can simply end composition, the visual effect is the same. |
| // However, sometimes one wants to display hint text in an empty text field and for this to work |
| // QQuickTextEdit.inputMethodComposing has to be false if the composition string is empty. |
| m_composingText.clear(); |
| finishComposingText(); |
| } |
| return 0; |
| } |
| |
| int32_t QQnxInputContext::onIsTextSelected(int32_t* pIsSelected) |
| { |
| *pIsSelected = hasSelectedText(); |
| |
| qInputContextDebug() << *pIsSelected; |
| |
| return 0; |
| } |
| |
| int32_t QQnxInputContext::onIsAllTextSelected(int32_t* pIsSelected) |
| { |
| QObject *input = qGuiApp->focusObject(); |
| if (!input) |
| return -1; |
| |
| QInputMethodQueryEvent query(Qt::ImCurrentSelection | Qt::ImSurroundingText); |
| QCoreApplication::sendEvent(input, &query); |
| |
| *pIsSelected = query.value(Qt::ImSurroundingText).toString().length() == query.value(Qt::ImCurrentSelection).toString().length(); |
| |
| qInputContextDebug() << *pIsSelected; |
| |
| return 0; |
| } |
| |
| void QQnxInputContext::showInputPanel() |
| { |
| qInputContextDebug(); |
| dispatchRequestSoftwareInputPanel(); |
| } |
| |
| void QQnxInputContext::hideInputPanel() |
| { |
| qInputContextDebug(); |
| dispatchCloseSoftwareInputPanel(); |
| } |
| |
| bool QQnxInputContext::isInputPanelVisible() const |
| { |
| return m_inputPanelVisible; |
| } |
| |
| QLocale QQnxInputContext::locale() const |
| { |
| return m_inputPanelLocale; |
| } |
| |
| void QQnxInputContext::keyboardVisibilityChanged(bool visible) |
| { |
| qInputContextDebug() << "visible=" << visible; |
| if (m_inputPanelVisible != visible) { |
| m_inputPanelVisible = visible; |
| emitInputPanelVisibleChanged(); |
| } |
| } |
| |
| void QQnxInputContext::keyboardLocaleChanged(const QLocale &locale) |
| { |
| qInputContextDebug() << "locale=" << locale; |
| if (m_inputPanelLocale != locale) { |
| m_inputPanelLocale = locale; |
| emitLocaleChanged(); |
| } |
| } |
| |
| void QQnxInputContext::setHighlightColor(int index, const QColor &color) |
| { |
| qInputContextDebug() << "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() << "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(); |
| if (hasSession()) |
| dispatchFocusLossEvent(); |
| } else { |
| QInputMethodQueryEvent query(Qt::ImHints | Qt::ImEnterKeyType); |
| QCoreApplication::sendEvent(object, &query); |
| int inputHints = query.value(Qt::ImHints).toInt(); |
| Qt::EnterKeyType qtEnterKeyType = Qt::EnterKeyType(query.value(Qt::ImEnterKeyType).toInt()); |
| |
| dispatchFocusGainEvent(inputHints); |
| |
| m_virtualKeyboard.setInputHints(inputHints); |
| m_virtualKeyboard.setEnterKeyType( |
| QQnxAbstractVirtualKeyboard::qtEnterKeyTypeToQnx(qtEnterKeyType) |
| ); |
| |
| if (!m_inputPanelVisible) |
| showInputPanel(); |
| } |
| } |
| |
| bool QQnxInputContext::checkSpelling(const QString &text, void *context, void (*spellCheckDone)(void *context, const QString &text, const QList<int> &indices)) |
| { |
| qInputContextDebug() << "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 |