1 /***************************************************************************
2 **
3 ** Copyright (C) 2013 BlackBerry Limited. All rights reserved.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qqnxinputcontext_imf.h"
41 #include "qqnxabstractvirtualkeyboard.h"
42 #include "qqnxintegration.h"
43 #include "qqnxscreen.h"
44 #include "qqnxscreeneventhandler.h"
45
46 #include <QtGui/QGuiApplication>
47 #include <QtGui/QInputMethodEvent>
48 #include <QtGui/QTextCharFormat>
49
50 #include <QtCore/QDebug>
51 #include <QtCore/QMutex>
52 #include <QtCore/QVariant>
53 #include <QtCore/QVariantHash>
54 #include <QtCore/QWaitCondition>
55 #include <QtCore/QQueue>
56 #include <QtCore/QGlobalStatic>
57
58 #include <dlfcn.h>
59 #include "imf/imf_client.h"
60 #include "imf/input_control.h"
61 #include <process.h>
62 #include <sys/keycodes.h>
63
64 #if defined(QQNXINPUTCONTEXT_IMF_EVENT_DEBUG)
65 #define qInputContextIMFRequestDebug qDebug
66 #else
67 #define qInputContextIMFRequestDebug QT_NO_QDEBUG_MACRO
68 #endif
69
70 #if defined(QQNXINPUTCONTEXT_DEBUG)
71 #define qInputContextDebug qDebug
72 #else
73 #define qInputContextDebug QT_NO_QDEBUG_MACRO
74 #endif
75
76 static QQnxInputContext *sInputContextInstance;
77 static QColor sSelectedColor(0,0xb8,0,85);
78
79 static const input_session_t *sSpellCheckSession = 0;
80 static const input_session_t *sInputSession = 0;
isSessionOkay(input_session_t * ic)81 static bool isSessionOkay(input_session_t *ic)
82 {
83 return ic !=0 && sInputSession != 0 && ic->component_id == sInputSession->component_id;
84 }
85
86 enum ImfEventType
87 {
88 ImfCommitText,
89 ImfDeleteSurroundingText,
90 ImfFinishComposingText,
91 ImfGetCursorPosition,
92 ImfGetTextAfterCursor,
93 ImfGetTextBeforeCursor,
94 ImfSendEvent,
95 ImfSetComposingRegion,
96 ImfSetComposingText,
97 ImfIsTextSelected,
98 ImfIsAllTextSelected,
99 };
100
101 struct SpellCheckInfo {
SpellCheckInfoSpellCheckInfo102 SpellCheckInfo(void *_context, void (*_spellCheckDone)(void *, const QString &, const QList<int> &))
103 : context(_context), spellCheckDone(_spellCheckDone) {}
104 void *context;
105 void (*spellCheckDone)(void *, const QString &, const QList<int> &);
106 };
107 Q_GLOBAL_STATIC(QQueue<SpellCheckInfo>, sSpellCheckQueue)
108
109 // IMF requests all arrive on IMF's own thread and have to be posted to the main thread to be processed.
110 class QQnxImfRequest
111 {
112 public:
QQnxImfRequest(input_session_t * _session,ImfEventType _type)113 QQnxImfRequest(input_session_t *_session, ImfEventType _type)
114 : session(_session), type(_type)
115 { }
~QQnxImfRequest()116 ~QQnxImfRequest() { }
117
118 input_session_t *session;
119 ImfEventType type;
120 union {
121 struct {
122 int32_t n;
123 int32_t flags;
124 bool before;
125 spannable_string_t *result;
126 } gtac; // ic_get_text_before_cursor/ic_get_text_after_cursor
127 struct {
128 int32_t result;
129 } gcp; // ic_get_cursor_position
130 struct {
131 int32_t start;
132 int32_t end;
133 int32_t result;
134 } scr; // ic_set_composing_region
135 struct {
136 spannable_string_t* text;
137 int32_t new_cursor_position;
138 int32_t result;
139 } sct; // ic_set_composing_text
140 struct {
141 spannable_string_t* text;
142 int32_t new_cursor_position;
143 int32_t result;
144 } ct; // ic_commit_text
145 struct {
146 int32_t result;
147 } fct; // ic_finish_composing_text
148 struct {
149 int32_t left_length;
150 int32_t right_length;
151 int32_t result;
152 } dst; // ic_delete_surrounding_text
153 struct {
154 event_t *event;
155 int32_t result;
156 } sae; // ic_send_async_event/ic_send_event
157 struct {
158 int32_t *pIsSelected;
159 int32_t result;
160 } its; // ic_is_text_selected/ic_is_all_text_selected
161 };
162 };
163
164 // Invoke an IMF initiated request synchronously on Qt's main thread. As describe below all
165 // IMF requests are made from another thread but need to be executed on the main thread.
executeIMFRequest(QQnxImfRequest * event)166 static void executeIMFRequest(QQnxImfRequest *event)
167 {
168 QMetaObject::invokeMethod(sInputContextInstance,
169 "processImfEvent",
170 Qt::BlockingQueuedConnection,
171 Q_ARG(QQnxImfRequest*, event));
172 }
173
174 // The following functions (ic_*) are callback functions called by the input system to query information
175 // about the text object that currently has focus or to make changes to it. All calls are made from the
176 // input system's own thread. The pattern for each callback function is to copy its parameters into
177 // a QQnxImfRequest structure and call executeIMFRequest to have it passed synchronously to Qt's main thread.
178 // Any return values should be pre-initialised with suitable default values as in some cases
179 // (e.g. a stale session) the call will return without having executed any request specific code.
180 //
181 // To make the correspondence more obvious, the names of these functions match those defined in the headers.
182 // They're in an anonymous namespace to avoid compiler conflicts with external functions defined with the
183 // same names.
184 namespace
185 {
186
187 // See comment at beginning of namespace declaration for general information
ic_begin_batch_edit(input_session_t * ic)188 static int32_t ic_begin_batch_edit(input_session_t *ic)
189 {
190 Q_UNUSED(ic);
191
192 // Ignore silently.
193 return 0;
194 }
195
196 // End composition, committing the supplied text.
197 // See comment at beginning of namespace declaration for general information
ic_commit_text(input_session_t * ic,spannable_string_t * text,int32_t new_cursor_position)198 static int32_t ic_commit_text(input_session_t *ic, spannable_string_t *text, int32_t new_cursor_position)
199 {
200 qInputContextIMFRequestDebug();
201
202 QQnxImfRequest event(ic, ImfCommitText);
203 event.ct.text = text;
204 event.ct.new_cursor_position = new_cursor_position;
205 event.ct.result = -1;
206 executeIMFRequest(&event);
207
208 return event.ct.result;
209 }
210
211 // Delete left_length characters before and right_length characters after the cursor.
212 // See comment at beginning of namespace declaration for general information
ic_delete_surrounding_text(input_session_t * ic,int32_t left_length,int32_t right_length)213 static int32_t ic_delete_surrounding_text(input_session_t *ic, int32_t left_length, int32_t right_length)
214 {
215 qInputContextIMFRequestDebug();
216
217 QQnxImfRequest event(ic, ImfDeleteSurroundingText);
218 event.dst.left_length = left_length;
219 event.dst.right_length = right_length;
220 event.dst.result = -1;
221 executeIMFRequest(&event);
222
223 return event.dst.result;
224 }
225
226 // See comment at beginning of namespace declaration for general information
ic_end_batch_edit(input_session_t * ic)227 static int32_t ic_end_batch_edit(input_session_t *ic)
228 {
229 Q_UNUSED(ic);
230
231 // Ignore silently.
232 return 0;
233 }
234
235 // End composition, committing what's there.
236 // See comment at beginning of namespace declaration for general information
ic_finish_composing_text(input_session_t * ic)237 static int32_t ic_finish_composing_text(input_session_t *ic)
238 {
239 qInputContextIMFRequestDebug();
240
241 QQnxImfRequest event(ic, ImfFinishComposingText);
242 event.fct.result = -1;
243 executeIMFRequest(&event);
244
245 return event.fct.result;
246 }
247
248 // Return the position of the cursor.
249 // See comment at beginning of namespace declaration for general information
ic_get_cursor_position(input_session_t * ic)250 static int32_t ic_get_cursor_position(input_session_t *ic)
251 {
252 qInputContextIMFRequestDebug();
253
254 QQnxImfRequest event(ic, ImfGetCursorPosition);
255 event.gcp.result = -1;
256 executeIMFRequest(&event);
257
258 return event.gcp.result;
259 }
260
261 // Return the n characters after the cursor.
262 // See comment at beginning of namespace declaration for general information
ic_get_text_after_cursor(input_session_t * ic,int32_t n,int32_t flags)263 static spannable_string_t *ic_get_text_after_cursor(input_session_t *ic, int32_t n, int32_t flags)
264 {
265 qInputContextIMFRequestDebug();
266
267 QQnxImfRequest event(ic, ImfGetTextAfterCursor);
268 event.gtac.n = n;
269 event.gtac.flags = flags;
270 event.gtac.result = 0;
271 executeIMFRequest(&event);
272
273 return event.gtac.result;
274 }
275
276 // Return the n characters before the cursor.
277 // See comment at beginning of namespace declaration for general information
ic_get_text_before_cursor(input_session_t * ic,int32_t n,int32_t flags)278 static spannable_string_t *ic_get_text_before_cursor(input_session_t *ic, int32_t n, int32_t flags)
279 {
280 qInputContextIMFRequestDebug();
281
282 QQnxImfRequest event(ic, ImfGetTextBeforeCursor);
283 event.gtac.n = n;
284 event.gtac.flags = flags;
285 event.gtac.result = 0;
286 executeIMFRequest(&event);
287
288 return event.gtac.result;
289 }
290
291 // Process an event from IMF. Primarily used for reflecting back keyboard events.
292 // See comment at beginning of namespace declaration for general information
ic_send_event(input_session_t * ic,event_t * event)293 static int32_t ic_send_event(input_session_t *ic, event_t *event)
294 {
295 qInputContextIMFRequestDebug();
296
297 QQnxImfRequest imfEvent(ic, ImfSendEvent);
298 imfEvent.sae.event = event;
299 imfEvent.sae.result = -1;
300 executeIMFRequest(&imfEvent);
301
302 return imfEvent.sae.result;
303 }
304
305 // Same as ic_send_event.
306 // See comment at beginning of namespace declaration for general information
ic_send_async_event(input_session_t * ic,event_t * event)307 static int32_t ic_send_async_event(input_session_t *ic, event_t *event)
308 {
309 qInputContextIMFRequestDebug();
310
311 // There's no difference from our point of view between ic_send_event & ic_send_async_event
312 QQnxImfRequest imfEvent(ic, ImfSendEvent);
313 imfEvent.sae.event = event;
314 imfEvent.sae.result = -1;
315 executeIMFRequest(&imfEvent);
316
317 return imfEvent.sae.result;
318 }
319
320 // Set the range of text between start and end as the composition range.
321 // See comment at beginning of namespace declaration for general information
ic_set_composing_region(input_session_t * ic,int32_t start,int32_t end)322 static int32_t ic_set_composing_region(input_session_t *ic, int32_t start, int32_t end)
323 {
324 qInputContextIMFRequestDebug();
325
326 QQnxImfRequest event(ic, ImfSetComposingRegion);
327 event.scr.start = start;
328 event.scr.end = end;
329 event.scr.result = -1;
330 executeIMFRequest(&event);
331
332 return event.scr.result;
333 }
334
335 // Update the composition range with the supplied text. This can be called when no composition
336 // range is in effect in which case one is started at the current cursor position.
337 // See comment at beginning of namespace declaration for general information
ic_set_composing_text(input_session_t * ic,spannable_string_t * text,int32_t new_cursor_position)338 static int32_t ic_set_composing_text(input_session_t *ic, spannable_string_t *text, int32_t new_cursor_position)
339 {
340 qInputContextIMFRequestDebug();
341
342 QQnxImfRequest event(ic, ImfSetComposingText);
343 event.sct.text = text;
344 event.sct.new_cursor_position = new_cursor_position;
345 event.sct.result = -1;
346 executeIMFRequest(&event);
347
348 return event.sct.result;
349 }
350
351 // Indicate if any text is selected
352 // See comment at beginning of namespace declaration for general information
ic_is_text_selected(input_session_t * ic,int32_t * pIsSelected)353 static int32_t ic_is_text_selected(input_session_t* ic, int32_t* pIsSelected)
354 {
355 qInputContextIMFRequestDebug();
356
357 QQnxImfRequest event(ic, ImfIsTextSelected);
358 event.its.pIsSelected = pIsSelected;
359 event.its.result = -1;
360 executeIMFRequest(&event);
361
362 return event.its.result;
363 }
364
365 // Indicate if all text is selected
366 // See comment at beginning of namespace declaration for general information
ic_is_all_text_selected(input_session_t * ic,int32_t * pIsSelected)367 static int32_t ic_is_all_text_selected(input_session_t* ic, int32_t* pIsSelected)
368 {
369 qInputContextIMFRequestDebug();
370
371 QQnxImfRequest event(ic, ImfIsAllTextSelected);
372 event.its.pIsSelected = pIsSelected;
373 event.its.result = -1;
374 executeIMFRequest(&event);
375
376 return event.its.result;
377 }
378
379 // LCOV_EXCL_START - exclude from code coverage analysis
380 // The following functions are defined in the IMF headers but are not currently called.
381
382 // Not currently used
ic_perform_editor_action(input_session_t * ic,int32_t editor_action)383 static int32_t ic_perform_editor_action(input_session_t *ic, int32_t editor_action)
384 {
385 Q_UNUSED(ic);
386 Q_UNUSED(editor_action);
387
388 qCritical("ic_perform_editor_action not implemented");
389 return 0;
390 }
391
392 // Not currently used
ic_report_fullscreen_mode(input_session_t * ic,int32_t enabled)393 static int32_t ic_report_fullscreen_mode(input_session_t *ic, int32_t enabled)
394 {
395 Q_UNUSED(ic);
396 Q_UNUSED(enabled);
397
398 qCritical("ic_report_fullscreen_mode not implemented");
399 return 0;
400 }
401
402 // Not currently used
ic_get_extracted_text(input_session_t * ic,extracted_text_request_t * request,int32_t flags)403 static extracted_text_t *ic_get_extracted_text(input_session_t *ic, extracted_text_request_t *request, int32_t flags)
404 {
405 Q_UNUSED(ic);
406 Q_UNUSED(request);
407 Q_UNUSED(flags);
408
409 qCritical("ic_get_extracted_text not implemented");
410 return 0;
411 }
412
413 // Not currently used
ic_get_selected_text(input_session_t * ic,int32_t flags)414 static spannable_string_t *ic_get_selected_text(input_session_t *ic, int32_t flags)
415 {
416 Q_UNUSED(ic);
417 Q_UNUSED(flags);
418
419 qCritical("ic_get_selected_text not implemented");
420 return 0;
421 }
422
423 // Not currently used
ic_get_cursor_caps_mode(input_session_t * ic,int32_t req_modes)424 static int32_t ic_get_cursor_caps_mode(input_session_t *ic, int32_t req_modes)
425 {
426 Q_UNUSED(ic);
427 Q_UNUSED(req_modes);
428
429 qCritical("ic_get_cursor_caps_mode not implemented");
430 return 0;
431 }
432
433 // Not currently used
ic_clear_meta_key_states(input_session_t * ic,int32_t states)434 static int32_t ic_clear_meta_key_states(input_session_t *ic, int32_t states)
435 {
436 Q_UNUSED(ic);
437 Q_UNUSED(states);
438
439 qCritical("ic_clear_meta_key_states not implemented");
440 return 0;
441 }
442
443 // Not currently used
ic_set_selection(input_session_t * ic,int32_t start,int32_t end)444 static int32_t ic_set_selection(input_session_t *ic, int32_t start, int32_t end)
445 {
446 Q_UNUSED(ic);
447 Q_UNUSED(start);
448 Q_UNUSED(end);
449
450 qCritical("ic_set_selection not implemented");
451 return 0;
452 }
453
454 // End of un-hittable code
455 // LCOV_EXCL_STOP
456
457
458 static connection_interface_t ic_funcs = {
459 ic_begin_batch_edit,
460 ic_clear_meta_key_states,
461 ic_commit_text,
462 ic_delete_surrounding_text,
463 ic_end_batch_edit,
464 ic_finish_composing_text,
465 ic_get_cursor_caps_mode,
466 ic_get_cursor_position,
467 ic_get_extracted_text,
468 ic_get_selected_text,
469 ic_get_text_after_cursor,
470 ic_get_text_before_cursor,
471 ic_perform_editor_action,
472 ic_report_fullscreen_mode,
473 0, //ic_send_key_event
474 ic_send_event,
475 ic_send_async_event,
476 ic_set_composing_region,
477 ic_set_composing_text,
478 ic_set_selection,
479 0, //ic_set_candidates,
480 0, //ic_get_cursor_offset,
481 0, //ic_get_selection,
482 ic_is_text_selected,
483 ic_is_all_text_selected,
484 0, //ic_get_max_cursor_offset_t
485 };
486
487 } // namespace
488
489 static void
initEvent(event_t * pEvent,const input_session_t * pSession,EventType eventType,int eventId,int eventSize)490 initEvent(event_t *pEvent, const input_session_t *pSession, EventType eventType, int eventId, int eventSize)
491 {
492 static int s_transactionId;
493
494 // Make sure structure is squeaky clean since it's not clear just what is significant.
495 memset(pEvent, 0, eventSize);
496 pEvent->event_type = eventType;
497 pEvent->event_id = eventId;
498 pEvent->pid = getpid();
499 pEvent->component_id = pSession->component_id;
500 pEvent->transaction_id = ++s_transactionId;
501 }
502
toSpannableString(const QString & text)503 static spannable_string_t *toSpannableString(const QString &text)
504 {
505 qInputContextDebug() << text;
506
507 spannable_string_t *pString = static_cast<spannable_string_t *>(malloc(sizeof(spannable_string_t)));
508 pString->str = static_cast<wchar_t *>(malloc(sizeof(wchar_t) * text.length() + 1));
509 pString->length = text.toWCharArray(pString->str);
510 pString->spans = 0;
511 pString->spans_count = 0;
512 pString->str[pString->length] = 0;
513
514 return pString;
515 }
516
517
518 static const input_session_t *(*p_ictrl_open_session)(connection_interface_t *) = 0;
519 static void (*p_ictrl_close_session)(input_session_t *) = 0;
520 static int32_t (*p_ictrl_dispatch_event)(event_t*) = 0;
521 static int32_t (*p_imf_client_init)() = 0;
522 static void (*p_imf_client_disconnect)() = 0;
523 static int32_t (*p_vkb_init_selection_service)() = 0;
524 static int32_t (*p_ictrl_get_num_active_sessions)() = 0;
525 static bool s_imfInitFailed = false;
526
imfAvailable()527 static bool imfAvailable()
528 {
529 static bool s_imfDisabled = getenv("DISABLE_IMF") != 0;
530 static bool s_imfReady = false;
531
532 if ( s_imfInitFailed || s_imfDisabled)
533 return false;
534 else if ( s_imfReady )
535 return true;
536
537 if ( p_imf_client_init == 0 ) {
538 void *handle = dlopen("libinput_client.so.1", 0);
539 if (Q_UNLIKELY(!handle)) {
540 qCritical("libinput_client.so.1 is not present - IMF services are disabled.");
541 s_imfDisabled = true;
542 return false;
543 }
544 p_imf_client_init = (int32_t (*)()) dlsym(handle, "imf_client_init");
545 p_imf_client_disconnect = (void (*)()) dlsym(handle, "imf_client_disconnect");
546 p_ictrl_open_session = (const input_session_t *(*)(connection_interface_t *))dlsym(handle, "ictrl_open_session");
547 p_ictrl_close_session = (void (*)(input_session_t *))dlsym(handle, "ictrl_close_session");
548 p_ictrl_dispatch_event = (int32_t (*)(event_t *))dlsym(handle, "ictrl_dispatch_event");
549 p_vkb_init_selection_service = (int32_t (*)())dlsym(handle, "vkb_init_selection_service");
550 p_ictrl_get_num_active_sessions = (int32_t (*)())dlsym(handle, "ictrl_get_num_active_sessions");
551
552 if (Q_UNLIKELY(!p_imf_client_init || !p_ictrl_open_session || !p_ictrl_dispatch_event)) {
553 p_ictrl_open_session = 0;
554 p_ictrl_dispatch_event = 0;
555 s_imfDisabled = true;
556 qCritical("libinput_client.so.1 did not contain the correct symbols, library mismatch? IMF services are disabled.");
557 return false;
558 }
559
560 s_imfReady = true;
561 }
562
563 return s_imfReady;
564 }
565
566 QT_BEGIN_NAMESPACE
567
QQnxInputContext(QQnxIntegration * integration,QQnxAbstractVirtualKeyboard & keyboard)568 QQnxInputContext::QQnxInputContext(QQnxIntegration *integration, QQnxAbstractVirtualKeyboard &keyboard) :
569 QPlatformInputContext(),
570 m_caretPosition(0),
571 m_isComposing(false),
572 m_isUpdatingText(false),
573 m_inputPanelVisible(false),
574 m_inputPanelLocale(QLocale::c()),
575 m_focusObject(0),
576 m_integration(integration),
577 m_virtualKeyboard(keyboard)
578 {
579 qInputContextDebug();
580
581 if (!imfAvailable())
582 return;
583
584 // Save a pointer to ourselves so we can execute calls from IMF through executeIMFRequest
585 // In practice there will only ever be a single instance.
586 Q_ASSERT(sInputContextInstance == 0);
587 sInputContextInstance = this;
588
589 if (Q_UNLIKELY(p_imf_client_init() != 0)) {
590 s_imfInitFailed = true;
591 qCritical("imf_client_init failed - IMF services will be unavailable");
592 }
593
594 connect(&keyboard, SIGNAL(visibilityChanged(bool)), this, SLOT(keyboardVisibilityChanged(bool)));
595 connect(&keyboard, SIGNAL(localeChanged(QLocale)), this, SLOT(keyboardLocaleChanged(QLocale)));
596 keyboardVisibilityChanged(keyboard.isVisible());
597 keyboardLocaleChanged(keyboard.locale());
598 }
599
~QQnxInputContext()600 QQnxInputContext::~QQnxInputContext()
601 {
602 qInputContextDebug();
603
604 Q_ASSERT(sInputContextInstance == this);
605 sInputContextInstance = 0;
606
607 if (!imfAvailable())
608 return;
609
610 p_imf_client_disconnect();
611 }
612
isValid() const613 bool QQnxInputContext::isValid() const
614 {
615 return imfAvailable();
616 }
617
processImfEvent(QQnxImfRequest * imfEvent)618 void QQnxInputContext::processImfEvent(QQnxImfRequest *imfEvent)
619 {
620 // If input session is no longer current, just bail, imfEvent should already be set with the appropriate
621 // return value. The only exception is spell check events since they're not associated with the
622 // object with focus.
623 if (imfEvent->type != ImfSendEvent || imfEvent->sae.event->event_type != EVENT_SPELL_CHECK) {
624 if (!isSessionOkay(imfEvent->session))
625 return;
626 }
627
628 switch (imfEvent->type) {
629 case ImfCommitText:
630 imfEvent->ct.result = onCommitText(imfEvent->ct.text, imfEvent->ct.new_cursor_position);
631 break;
632
633 case ImfDeleteSurroundingText:
634 imfEvent->dst.result = onDeleteSurroundingText(imfEvent->dst.left_length, imfEvent->dst.right_length);
635 break;
636
637 case ImfFinishComposingText:
638 imfEvent->fct.result = onFinishComposingText();
639 break;
640
641 case ImfGetCursorPosition:
642 imfEvent->gcp.result = onGetCursorPosition();
643 break;
644
645 case ImfGetTextAfterCursor:
646 imfEvent->gtac.result = onGetTextAfterCursor(imfEvent->gtac.n, imfEvent->gtac.flags);
647 break;
648
649 case ImfGetTextBeforeCursor:
650 imfEvent->gtac.result = onGetTextBeforeCursor(imfEvent->gtac.n, imfEvent->gtac.flags);
651 break;
652
653 case ImfSendEvent:
654 imfEvent->sae.result = onSendEvent(imfEvent->sae.event);
655 break;
656
657 case ImfSetComposingRegion:
658 imfEvent->scr.result = onSetComposingRegion(imfEvent->scr.start, imfEvent->scr.end);
659 break;
660
661 case ImfSetComposingText:
662 imfEvent->sct.result = onSetComposingText(imfEvent->sct.text, imfEvent->sct.new_cursor_position);
663 break;
664
665 case ImfIsTextSelected:
666 imfEvent->its.result = onIsTextSelected(imfEvent->its.pIsSelected);
667 break;
668
669 case ImfIsAllTextSelected:
670 imfEvent->its.result = onIsAllTextSelected(imfEvent->its.pIsSelected);
671 break;
672 }; //switch
673 }
674
filterEvent(const QEvent * event)675 bool QQnxInputContext::filterEvent( const QEvent *event )
676 {
677 qInputContextDebug() << event;
678
679 switch (event->type()) {
680 case QEvent::CloseSoftwareInputPanel:
681 return dispatchCloseSoftwareInputPanel();
682
683 case QEvent::RequestSoftwareInputPanel:
684 return dispatchRequestSoftwareInputPanel();
685
686 default:
687 return false;
688 }
689 }
690
keyboardRect() const691 QRectF QQnxInputContext::keyboardRect() const
692 {
693 QRect screenGeometry = m_integration->primaryDisplay()->geometry();
694 return QRectF(screenGeometry.x(), screenGeometry.height() - m_virtualKeyboard.height(),
695 screenGeometry.width(), m_virtualKeyboard.height());
696 }
697
reset()698 void QQnxInputContext::reset()
699 {
700 qInputContextDebug();
701 endComposition();
702 }
703
commit()704 void QQnxInputContext::commit()
705 {
706 qInputContextDebug();
707 endComposition();
708 }
709
update(Qt::InputMethodQueries queries)710 void QQnxInputContext::update(Qt::InputMethodQueries queries)
711 {
712 qInputContextDebug() << queries;
713
714 if (queries & Qt::ImCursorPosition) {
715 int lastCaret = m_caretPosition;
716 updateCursorPosition();
717 // If caret position has changed we need to inform IMF unless this is just due to our own action
718 // such as committing text.
719 if (hasSession() && !m_isUpdatingText && lastCaret != m_caretPosition) {
720 caret_event_t caretEvent;
721 initEvent(&caretEvent.event, sInputSession, EVENT_CARET, CARET_POS_CHANGED, sizeof(caretEvent));
722 caretEvent.old_pos = lastCaret;
723 caretEvent.new_pos = m_caretPosition;
724 qInputContextDebug("ictrl_dispatch_event caret changed %d %d", lastCaret, m_caretPosition);
725 p_ictrl_dispatch_event(&caretEvent.event);
726 }
727 }
728 }
729
closeSession()730 void QQnxInputContext::closeSession()
731 {
732 qInputContextDebug();
733 if (!imfAvailable())
734 return;
735
736 if (sInputSession) {
737 p_ictrl_close_session((input_session_t *)sInputSession);
738 sInputSession = 0;
739 }
740 // These are likely already in the right state but this depends on the text control
741 // having called reset or commit. So, just in case, set them to proper values.
742 m_isComposing = false;
743 m_composingText.clear();
744 }
745
openSession()746 bool QQnxInputContext::openSession()
747 {
748 if (!imfAvailable())
749 return false;
750
751 closeSession();
752 sInputSession = p_ictrl_open_session(&ic_funcs);
753
754 qInputContextDebug();
755
756 return sInputSession != 0;
757 }
758
hasSession()759 bool QQnxInputContext::hasSession()
760 {
761 return sInputSession != 0;
762 }
763
hasSelectedText()764 bool QQnxInputContext::hasSelectedText()
765 {
766 QObject *input = qGuiApp->focusObject();
767 if (!input)
768 return false;
769
770 QInputMethodQueryEvent query(Qt::ImCurrentSelection);
771 QCoreApplication::sendEvent(input, &query);
772
773 return !query.value(Qt::ImCurrentSelection).toString().isEmpty();
774 }
775
dispatchRequestSoftwareInputPanel()776 bool QQnxInputContext::dispatchRequestSoftwareInputPanel()
777 {
778 qInputContextDebug() << "requesting keyboard" << m_inputPanelVisible;
779 m_virtualKeyboard.showKeyboard();
780
781 return true;
782 }
783
dispatchCloseSoftwareInputPanel()784 bool QQnxInputContext::dispatchCloseSoftwareInputPanel()
785 {
786 qInputContextDebug() << "hiding keyboard" << m_inputPanelVisible;
787 m_virtualKeyboard.hideKeyboard();
788
789 return true;
790 }
791
792 /**
793 * IMF Event Dispatchers.
794 */
dispatchFocusGainEvent(int inputHints)795 bool QQnxInputContext::dispatchFocusGainEvent(int inputHints)
796 {
797 if (hasSession())
798 dispatchFocusLossEvent();
799
800 QObject *input = qGuiApp->focusObject();
801
802 if (!input || !openSession())
803 return false;
804
805 // Set the last caret position to 0 since we don't really have one and we don't
806 // want to have the old one.
807 m_caretPosition = 0;
808
809 QInputMethodQueryEvent query(Qt::ImHints);
810 QCoreApplication::sendEvent(input, &query);
811
812 focus_event_t focusEvent;
813 initEvent(&focusEvent.event, sInputSession, EVENT_FOCUS, FOCUS_GAINED, sizeof(focusEvent));
814 focusEvent.style = DEFAULT_STYLE;
815
816 if (inputHints & Qt::ImhNoPredictiveText)
817 focusEvent.style |= NO_PREDICTION | NO_AUTO_CORRECTION;
818 if (inputHints & Qt::ImhNoAutoUppercase)
819 focusEvent.style |= NO_AUTO_TEXT;
820
821 // Following styles are mutually exclusive
822 if (inputHints & Qt::ImhHiddenText) {
823 focusEvent.style |= IMF_PASSWORD_TYPE;
824 } else if (inputHints & Qt::ImhDialableCharactersOnly) {
825 focusEvent.style |= IMF_PHONE_TYPE;
826 } else if (inputHints & Qt::ImhUrlCharactersOnly) {
827 focusEvent.style |= IMF_URL_TYPE;
828 } else if (inputHints & Qt::ImhEmailCharactersOnly) {
829 focusEvent.style |= IMF_EMAIL_TYPE;
830 }
831
832 qInputContextDebug() << "ictrl_dispatch_event focus gain style:" << focusEvent.style;
833
834 p_ictrl_dispatch_event((event_t *)&focusEvent);
835
836 return true;
837 }
838
dispatchFocusLossEvent()839 void QQnxInputContext::dispatchFocusLossEvent()
840 {
841 if (hasSession()) {
842 qInputContextDebug("ictrl_dispatch_event focus lost");
843
844 focus_event_t focusEvent;
845 initEvent(&focusEvent.event, sInputSession, EVENT_FOCUS, FOCUS_LOST, sizeof(focusEvent));
846 p_ictrl_dispatch_event((event_t *)&focusEvent);
847 closeSession();
848 }
849 }
850
handleKeyboardEvent(int flags,int sym,int mod,int scan,int cap,int sequenceId)851 bool QQnxInputContext::handleKeyboardEvent(int flags, int sym, int mod, int scan, int cap, int sequenceId)
852 {
853 Q_UNUSED(scan);
854
855 if (!hasSession())
856 return false;
857
858 int key = (flags & KEY_SYM_VALID) ? sym : cap;
859 bool navigationKey = false;
860 switch (key) {
861 case KEYCODE_RETURN:
862 /* In a single line edit we should end composition because enter might be used by something.
863 endComposition();
864 return false;*/
865 break;
866
867 case KEYCODE_BACKSPACE:
868 case KEYCODE_DELETE:
869 // If there is a selection range, then we want a delete key to operate on that (by
870 // deleting the contents of the select range) rather than operating on the composition
871 // range.
872 if (hasSelectedText())
873 return false;
874 break;
875 case KEYCODE_LEFT:
876 key = NAVIGATE_LEFT;
877 navigationKey = true;
878 break;
879 case KEYCODE_RIGHT:
880 key = NAVIGATE_RIGHT;
881 navigationKey = true;
882 break;
883 case KEYCODE_UP:
884 key = NAVIGATE_UP;
885 navigationKey = true;
886 break;
887 case KEYCODE_DOWN:
888 key = NAVIGATE_DOWN;
889 navigationKey = true;
890 break;
891 case KEYCODE_LEFT_CTRL:
892 case KEYCODE_RIGHT_CTRL:
893 case KEYCODE_MENU:
894 case KEYCODE_LEFT_HYPER:
895 case KEYCODE_RIGHT_HYPER:
896 case KEYCODE_INSERT:
897 case KEYCODE_HOME:
898 case KEYCODE_PG_UP:
899 case KEYCODE_END:
900 case KEYCODE_PG_DOWN:
901 // Don't send these
902 key = 0;
903 break;
904 }
905
906 // Pass the keys we don't know about on through
907 if ( key == 0 )
908 return false;
909
910 if (navigationKey) {
911 // Even if we're forwarding up events, we can't do this for
912 // navigation keys.
913 if ( flags & KEY_DOWN ) {
914 navigation_event_t navEvent;
915 initEvent(&navEvent.event, sInputSession, EVENT_NAVIGATION, key, sizeof(navEvent));
916 navEvent.magnitude = 1;
917 qInputContextDebug("ictrl_dispatch_even navigation %d", key);
918 p_ictrl_dispatch_event(&navEvent.event);
919 }
920 } else {
921 key_event_t keyEvent;
922 initEvent(&keyEvent.event, sInputSession, EVENT_KEY, flags & KEY_DOWN ? IMF_KEY_DOWN : IMF_KEY_UP,
923 sizeof(keyEvent));
924 keyEvent.key_code = cap;
925 keyEvent.character = sym;
926 keyEvent.meta_key_state = mod;
927 keyEvent.sequence_id = sequenceId;
928
929 p_ictrl_dispatch_event(&keyEvent.event);
930 qInputContextDebug("ictrl_dispatch_even key %d", key);
931 }
932
933 return true;
934 }
935
updateCursorPosition()936 void QQnxInputContext::updateCursorPosition()
937 {
938 QObject *input = qGuiApp->focusObject();
939 if (!input)
940 return;
941
942 QInputMethodQueryEvent query(Qt::ImCursorPosition);
943 QCoreApplication::sendEvent(input, &query);
944 m_caretPosition = query.value(Qt::ImCursorPosition).toInt();
945
946 qInputContextDebug("%d", m_caretPosition);
947 }
948
endComposition()949 void QQnxInputContext::endComposition()
950 {
951 if (!m_isComposing)
952 return;
953
954 finishComposingText();
955
956 if (hasSession()) {
957 action_event_t actionEvent;
958 initEvent(&actionEvent.event, sInputSession, EVENT_ACTION, ACTION_END_COMPOSITION, sizeof(actionEvent));
959 qInputContextDebug("ictrl_dispatch_even end composition");
960 p_ictrl_dispatch_event(&actionEvent.event);
961 }
962 }
963
updateComposition(spannable_string_t * text,int32_t new_cursor_position)964 void QQnxInputContext::updateComposition(spannable_string_t *text, int32_t new_cursor_position)
965 {
966 QObject *input = qGuiApp->focusObject();
967 if (!input)
968 return;
969
970 if (new_cursor_position > 0)
971 new_cursor_position += text->length - 1;
972
973 m_composingText = QString::fromWCharArray(text->str, text->length);
974 m_isComposing = true;
975
976 qInputContextDebug() << m_composingText << new_cursor_position;
977
978 QList<QInputMethodEvent::Attribute> attributes;
979 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
980 new_cursor_position,
981 1,
982 QVariant()));
983
984 for (unsigned int i = 0; i < text->spans_count; ++i) {
985 QColor highlightColor;
986 bool underline = false;
987
988 if ((text->spans[i].attributes_mask & COMPOSED_TEXT_ATTRIB) != 0)
989 underline = true;
990
991 if ((text->spans[i].attributes_mask & ACTIVE_REGION_ATTRIB) != 0) {
992 underline = true;
993 highlightColor = m_highlightColor[ActiveRegion];
994 } else if ((text->spans[i].attributes_mask & AUTO_CORRECTION_ATTRIB) != 0) {
995 highlightColor = m_highlightColor[AutoCorrected];
996 } else if ((text->spans[i].attributes_mask & REVERT_CORRECTION_ATTRIB) != 0) {
997 highlightColor = m_highlightColor[Reverted];
998 }
999
1000 if (underline || highlightColor.isValid()) {
1001 QTextCharFormat format;
1002 if (underline)
1003 format.setFontUnderline(true);
1004 if (highlightColor.isValid())
1005 format.setBackground(QBrush(highlightColor));
1006 qInputContextDebug() << " attrib: " << underline << highlightColor << text->spans[i].start << text->spans[i].end;
1007 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, text->spans[i].start,
1008 text->spans[i].end - text->spans[i].start + 1, QVariant(format)));
1009
1010 }
1011 }
1012 QInputMethodEvent event(m_composingText, attributes);
1013 m_isUpdatingText = true;
1014 QCoreApplication::sendEvent(input, &event);
1015 m_isUpdatingText = false;
1016
1017 updateCursorPosition();
1018 }
1019
finishComposingText()1020 void QQnxInputContext::finishComposingText()
1021 {
1022 QObject *input = qGuiApp->focusObject();
1023
1024 if (input) {
1025 qInputContextDebug() << m_composingText;
1026
1027 QInputMethodEvent event;
1028 event.setCommitString(m_composingText);
1029 m_isUpdatingText = true;
1030 QCoreApplication::sendEvent(input, &event);
1031 m_isUpdatingText = false;
1032 }
1033 m_composingText = QString();
1034 m_isComposing = false;
1035
1036 updateCursorPosition();
1037 }
1038
1039 // Return the index relative to a UTF-16 sequence of characters for a index that is relative to the
1040 // corresponding UTF-32 character string given a starting index in the UTF-16 string and a count
1041 // of the number of lead surrogates prior to that index. Updates the highSurrogateCount to reflect the
1042 // new surrogate characters encountered.
adjustIndex(const QChar * text,int utf32Index,int utf16StartIndex,int * highSurrogateCount)1043 static int adjustIndex(const QChar *text, int utf32Index, int utf16StartIndex, int *highSurrogateCount)
1044 {
1045 int utf16Index = utf32Index + *highSurrogateCount;
1046 while (utf16StartIndex < utf16Index) {
1047 if (text[utf16StartIndex].isHighSurrogate()) {
1048 ++utf16Index;
1049 ++*highSurrogateCount;
1050 }
1051 ++utf16StartIndex;
1052 }
1053 return utf16StartIndex;
1054 }
1055
handleSpellCheck(spell_check_event_t * event)1056 int QQnxInputContext::handleSpellCheck(spell_check_event_t *event)
1057 {
1058 // These should never happen.
1059 if (sSpellCheckQueue->isEmpty() || event->event.event_id != NOTIFY_SP_CHECK_MISSPELLINGS)
1060 return -1;
1061
1062 SpellCheckInfo callerInfo = sSpellCheckQueue->dequeue();
1063 spannable_string_t* spellCheckData = *event->data;
1064 QString text = QString::fromWCharArray(spellCheckData->str, spellCheckData->length);
1065 // Generate the list of indices indicating misspelled words in the text. We use end + 1
1066 // since it's more conventional to have the end index point just past the string. We also
1067 // can't use the indices directly since they are relative to UTF-32 encoded data and the
1068 // conversion to Qt's UTF-16 internal format might cause lengthening.
1069 QList<int> indices;
1070 int adjustment = 0;
1071 int index = 0;
1072 for (unsigned int i = 0; i < spellCheckData->spans_count; ++i) {
1073 if (spellCheckData->spans[i].attributes_mask & MISSPELLED_WORD_ATTRIB) {
1074 index = adjustIndex(text.data(), spellCheckData->spans[i].start, index, &adjustment);
1075 indices.push_back(index);
1076 index = adjustIndex(text.data(), spellCheckData->spans[i].end + 1, index, &adjustment);
1077 indices.push_back(index);
1078 }
1079 }
1080 callerInfo.spellCheckDone(callerInfo.context, text, indices);
1081
1082 return 0;
1083 }
1084
processEvent(event_t * event)1085 int32_t QQnxInputContext::processEvent(event_t *event)
1086 {
1087 int32_t result = -1;
1088 switch (event->event_type) {
1089 case EVENT_SPELL_CHECK: {
1090 qInputContextDebug("EVENT_SPELL_CHECK");
1091 result = handleSpellCheck(reinterpret_cast<spell_check_event_t *>(event));
1092 break;
1093 }
1094
1095 case EVENT_NAVIGATION: {
1096 qInputContextDebug("EVENT_NAVIGATION");
1097
1098 int key = event->event_id == NAVIGATE_UP ? KEYCODE_UP :
1099 event->event_id == NAVIGATE_DOWN ? KEYCODE_DOWN :
1100 event->event_id == NAVIGATE_LEFT ? KEYCODE_LEFT :
1101 event->event_id == NAVIGATE_RIGHT ? KEYCODE_RIGHT : 0;
1102
1103 QQnxScreenEventHandler::injectKeyboardEvent(KEY_DOWN | KEY_CAP_VALID, key, 0, 0, 0);
1104 QQnxScreenEventHandler::injectKeyboardEvent(KEY_CAP_VALID, key, 0, 0, 0);
1105 result = 0;
1106 break;
1107 }
1108
1109 case EVENT_KEY: {
1110 key_event_t *kevent = reinterpret_cast<key_event_t *>(event);
1111 int keySym = kevent->character != 0 ? kevent->character : kevent->key_code;
1112 int keyCap = kevent->key_code;
1113 int modifiers = 0;
1114 if (kevent->meta_key_state & META_SHIFT_ON)
1115 modifiers |= KEYMOD_SHIFT;
1116 int flags = KEY_SYM_VALID | KEY_CAP_VALID;
1117 if (event->event_id == IMF_KEY_DOWN)
1118 flags |= KEY_DOWN;
1119 qInputContextDebug("EVENT_KEY %d %d", flags, keySym);
1120 QQnxScreenEventHandler::injectKeyboardEvent(flags, keySym, modifiers, 0, keyCap);
1121 result = 0;
1122 break;
1123 }
1124
1125 case EVENT_ACTION:
1126 // Don't care, indicates that IMF is done.
1127 break;
1128
1129 case EVENT_CARET:
1130 case EVENT_NOTHING:
1131 case EVENT_FOCUS:
1132 case EVENT_USER_ACTION:
1133 case EVENT_STROKE:
1134 case EVENT_INVOKE_LATER:
1135 qCritical() << "Unsupported event type: " << event->event_type;
1136 break;
1137 default:
1138 qCritical() << "Unknown event type: " << event->event_type;
1139 }
1140 return result;
1141 }
1142
1143 /**
1144 * IMF Event Handlers
1145 */
1146
onCommitText(spannable_string_t * text,int32_t new_cursor_position)1147 int32_t QQnxInputContext::onCommitText(spannable_string_t *text, int32_t new_cursor_position)
1148 {
1149 Q_UNUSED(new_cursor_position);
1150
1151 updateComposition(text, new_cursor_position);
1152 finishComposingText();
1153
1154 return 0;
1155 }
1156
onDeleteSurroundingText(int32_t left_length,int32_t right_length)1157 int32_t QQnxInputContext::onDeleteSurroundingText(int32_t left_length, int32_t right_length)
1158 {
1159 qInputContextDebug("L: %d R: %d", int(left_length), int(right_length));
1160
1161 QObject *input = qGuiApp->focusObject();
1162 if (!input)
1163 return 0;
1164
1165 int replacementLength = left_length + right_length;
1166 int replacementStart = -left_length;
1167
1168 finishComposingText();
1169
1170 QInputMethodEvent event;
1171 event.setCommitString(QString(), replacementStart, replacementLength);
1172 m_isUpdatingText = true;
1173 QCoreApplication::sendEvent(input, &event);
1174 m_isUpdatingText = false;
1175
1176 updateCursorPosition();
1177
1178 return 0;
1179 }
1180
onFinishComposingText()1181 int32_t QQnxInputContext::onFinishComposingText()
1182 {
1183 finishComposingText();
1184
1185 return 0;
1186 }
1187
onGetCursorPosition()1188 int32_t QQnxInputContext::onGetCursorPosition()
1189 {
1190 qInputContextDebug();
1191
1192 QObject *input = qGuiApp->focusObject();
1193 if (!input)
1194 return 0;
1195
1196 updateCursorPosition();
1197
1198 return m_caretPosition;
1199 }
1200
onGetTextAfterCursor(int32_t n,int32_t flags)1201 spannable_string_t *QQnxInputContext::onGetTextAfterCursor(int32_t n, int32_t flags)
1202 {
1203 Q_UNUSED(flags);
1204 qInputContextDebug();
1205
1206 QObject *input = qGuiApp->focusObject();
1207 if (!input)
1208 return toSpannableString("");
1209
1210 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText);
1211 QCoreApplication::sendEvent(input, &query);
1212 QString text = query.value(Qt::ImSurroundingText).toString();
1213 m_caretPosition = query.value(Qt::ImCursorPosition).toInt();
1214
1215 return toSpannableString(text.mid(m_caretPosition, n));
1216 }
1217
onGetTextBeforeCursor(int32_t n,int32_t flags)1218 spannable_string_t *QQnxInputContext::onGetTextBeforeCursor(int32_t n, int32_t flags)
1219 {
1220 Q_UNUSED(flags);
1221 qInputContextDebug();
1222
1223 QObject *input = qGuiApp->focusObject();
1224 if (!input)
1225 return toSpannableString("");
1226
1227 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText);
1228 QCoreApplication::sendEvent(input, &query);
1229 QString text = query.value(Qt::ImSurroundingText).toString();
1230 m_caretPosition = query.value(Qt::ImCursorPosition).toInt();
1231
1232 if (n < m_caretPosition)
1233 return toSpannableString(text.mid(m_caretPosition - n, n));
1234 else
1235 return toSpannableString(text.mid(0, m_caretPosition));
1236 }
1237
onSendEvent(event_t * event)1238 int32_t QQnxInputContext::onSendEvent(event_t *event)
1239 {
1240 qInputContextDebug();
1241
1242 return processEvent(event);
1243 }
1244
onSetComposingRegion(int32_t start,int32_t end)1245 int32_t QQnxInputContext::onSetComposingRegion(int32_t start, int32_t end)
1246 {
1247 QObject *input = qGuiApp->focusObject();
1248 if (!input)
1249 return 0;
1250
1251 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImSurroundingText);
1252 QCoreApplication::sendEvent(input, &query);
1253 QString text = query.value(Qt::ImSurroundingText).toString();
1254 m_caretPosition = query.value(Qt::ImCursorPosition).toInt();
1255
1256 qInputContextDebug() << text;
1257
1258 m_isUpdatingText = true;
1259
1260 // Delete the current text.
1261 QInputMethodEvent deleteEvent;
1262 deleteEvent.setCommitString(QString(), start - m_caretPosition, end - start);
1263 QCoreApplication::sendEvent(input, &deleteEvent);
1264
1265 m_composingText = text.mid(start, end - start);
1266 m_isComposing = true;
1267
1268 QList<QInputMethodEvent::Attribute> attributes;
1269 QTextCharFormat format;
1270 format.setFontUnderline(true);
1271 attributes.push_back(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, m_composingText.length(), format));
1272
1273 QInputMethodEvent setTextEvent(m_composingText, attributes);
1274 QCoreApplication::sendEvent(input, &setTextEvent);
1275
1276 m_isUpdatingText = false;
1277
1278 return 0;
1279 }
1280
onSetComposingText(spannable_string_t * text,int32_t new_cursor_position)1281 int32_t QQnxInputContext::onSetComposingText(spannable_string_t *text, int32_t new_cursor_position)
1282 {
1283 if (text->length > 0) {
1284 updateComposition(text, new_cursor_position);
1285 } else {
1286 // If the composing text is empty we can simply end composition, the visual effect is the same.
1287 // However, sometimes one wants to display hint text in an empty text field and for this to work
1288 // QQuickTextEdit.inputMethodComposing has to be false if the composition string is empty.
1289 m_composingText.clear();
1290 finishComposingText();
1291 }
1292 return 0;
1293 }
1294
onIsTextSelected(int32_t * pIsSelected)1295 int32_t QQnxInputContext::onIsTextSelected(int32_t* pIsSelected)
1296 {
1297 *pIsSelected = hasSelectedText();
1298
1299 qInputContextDebug() << *pIsSelected;
1300
1301 return 0;
1302 }
1303
onIsAllTextSelected(int32_t * pIsSelected)1304 int32_t QQnxInputContext::onIsAllTextSelected(int32_t* pIsSelected)
1305 {
1306 QObject *input = qGuiApp->focusObject();
1307 if (!input)
1308 return -1;
1309
1310 QInputMethodQueryEvent query(Qt::ImCurrentSelection | Qt::ImSurroundingText);
1311 QCoreApplication::sendEvent(input, &query);
1312
1313 *pIsSelected = query.value(Qt::ImSurroundingText).toString().length() == query.value(Qt::ImCurrentSelection).toString().length();
1314
1315 qInputContextDebug() << *pIsSelected;
1316
1317 return 0;
1318 }
1319
showInputPanel()1320 void QQnxInputContext::showInputPanel()
1321 {
1322 qInputContextDebug();
1323 dispatchRequestSoftwareInputPanel();
1324 }
1325
hideInputPanel()1326 void QQnxInputContext::hideInputPanel()
1327 {
1328 qInputContextDebug();
1329 dispatchCloseSoftwareInputPanel();
1330 }
1331
isInputPanelVisible() const1332 bool QQnxInputContext::isInputPanelVisible() const
1333 {
1334 return m_inputPanelVisible;
1335 }
1336
locale() const1337 QLocale QQnxInputContext::locale() const
1338 {
1339 return m_inputPanelLocale;
1340 }
1341
keyboardVisibilityChanged(bool visible)1342 void QQnxInputContext::keyboardVisibilityChanged(bool visible)
1343 {
1344 qInputContextDebug() << "visible=" << visible;
1345 if (m_inputPanelVisible != visible) {
1346 m_inputPanelVisible = visible;
1347 emitInputPanelVisibleChanged();
1348 }
1349 }
1350
keyboardLocaleChanged(const QLocale & locale)1351 void QQnxInputContext::keyboardLocaleChanged(const QLocale &locale)
1352 {
1353 qInputContextDebug() << "locale=" << locale;
1354 if (m_inputPanelLocale != locale) {
1355 m_inputPanelLocale = locale;
1356 emitLocaleChanged();
1357 }
1358 }
1359
setHighlightColor(int index,const QColor & color)1360 void QQnxInputContext::setHighlightColor(int index, const QColor &color)
1361 {
1362 qInputContextDebug() << "setHighlightColor" << index << color << qGuiApp->focusObject();
1363
1364 if (!sInputContextInstance)
1365 return;
1366
1367 // If the focus has changed, revert all colors to the default.
1368 if (sInputContextInstance->m_focusObject != qGuiApp->focusObject()) {
1369 QColor invalidColor;
1370 sInputContextInstance->m_highlightColor[ActiveRegion] = sSelectedColor;
1371 sInputContextInstance->m_highlightColor[AutoCorrected] = invalidColor;
1372 sInputContextInstance->m_highlightColor[Reverted] = invalidColor;
1373 sInputContextInstance->m_focusObject = qGuiApp->focusObject();
1374 }
1375 if (index >= 0 && index <= Reverted)
1376 sInputContextInstance->m_highlightColor[index] = color;
1377 }
1378
setFocusObject(QObject * object)1379 void QQnxInputContext::setFocusObject(QObject *object)
1380 {
1381 qInputContextDebug() << "input item=" << object;
1382
1383 // Ensure the colors are reset if we've a change in focus object
1384 setHighlightColor(-1, QColor());
1385
1386 if (!inputMethodAccepted()) {
1387 if (m_inputPanelVisible)
1388 hideInputPanel();
1389 if (hasSession())
1390 dispatchFocusLossEvent();
1391 } else {
1392 QInputMethodQueryEvent query(Qt::ImHints | Qt::ImEnterKeyType);
1393 QCoreApplication::sendEvent(object, &query);
1394 int inputHints = query.value(Qt::ImHints).toInt();
1395 Qt::EnterKeyType qtEnterKeyType = Qt::EnterKeyType(query.value(Qt::ImEnterKeyType).toInt());
1396
1397 dispatchFocusGainEvent(inputHints);
1398
1399 m_virtualKeyboard.setInputHints(inputHints);
1400 m_virtualKeyboard.setEnterKeyType(
1401 QQnxAbstractVirtualKeyboard::qtEnterKeyTypeToQnx(qtEnterKeyType)
1402 );
1403
1404 if (!m_inputPanelVisible)
1405 showInputPanel();
1406 }
1407 }
1408
checkSpelling(const QString & text,void * context,void (* spellCheckDone)(void * context,const QString & text,const QList<int> & indices))1409 bool QQnxInputContext::checkSpelling(const QString &text, void *context, void (*spellCheckDone)(void *context, const QString &text, const QList<int> &indices))
1410 {
1411 qInputContextDebug() << "text" << text;
1412
1413 if (!imfAvailable())
1414 return false;
1415
1416 if (!sSpellCheckSession)
1417 sSpellCheckSession = p_ictrl_open_session(&ic_funcs);
1418
1419 action_event_t spellEvent;
1420 initEvent(&spellEvent.event, sSpellCheckSession, EVENT_ACTION, ACTION_CHECK_MISSPELLINGS, sizeof(spellEvent));
1421 int len = text.length();
1422 spellEvent.event_data = alloca(sizeof(wchar_t) * (len + 1));
1423 spellEvent.length_data = text.toWCharArray(static_cast<wchar_t*>(spellEvent.event_data)) * sizeof(wchar_t);
1424
1425 int rc = p_ictrl_dispatch_event(reinterpret_cast<event_t*>(&spellEvent));
1426
1427 if (rc == 0) {
1428 sSpellCheckQueue->enqueue(SpellCheckInfo(context, spellCheckDone));
1429 return true;
1430 }
1431 return false;
1432 }
1433
1434 QT_END_NAMESPACE
1435