1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
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 General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29
30 #include <QtVirtualKeyboard/private/qvirtualkeyboardinputcontext_p.h>
31 #include <QtVirtualKeyboard/private/platforminputcontext_p.h>
32 #include <QtVirtualKeyboard/private/settings_p.h>
33 #include <QtVirtualKeyboard/private/shifthandler_p.h>
34 #include <QtVirtualKeyboard/private/virtualkeyboarddebug_p.h>
35 #include <QtVirtualKeyboard/private/enterkeyaction_p.h>
36 #include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
37
38 #include <QGuiApplication>
39 #include <QtQuick/qquickitem.h>
40 #include <QtQuick/qquickwindow.h>
41 #include <QtGui/qpa/qplatformintegration.h>
42 #include <QtGui/private/qguiapplication_p.h>
43
44 QT_BEGIN_NAMESPACE
45
operator ==(const QInputMethodEvent::Attribute & attribute1,const QInputMethodEvent::Attribute & attribute2)46 bool operator==(const QInputMethodEvent::Attribute &attribute1, const QInputMethodEvent::Attribute &attribute2)
47 {
48 return attribute1.start == attribute2.start &&
49 attribute1.length == attribute2.length &&
50 attribute1.type == attribute2.type &&
51 attribute1.value == attribute2.value;
52 }
53
54 using namespace QtVirtualKeyboard;
55
56 const bool QtVirtualKeyboard::QT_VIRTUALKEYBOARD_FORCE_EVENTS_WITHOUT_FOCUS = qEnvironmentVariableIsSet("QT_VIRTUALKEYBOARD_FORCE_EVENTS_WITHOUT_FOCUS");
57
QVirtualKeyboardInputContextPrivate(QVirtualKeyboardInputContext * q_ptr)58 QVirtualKeyboardInputContextPrivate::QVirtualKeyboardInputContextPrivate(QVirtualKeyboardInputContext *q_ptr) :
59 QObject(nullptr),
60 q_ptr(q_ptr),
61 platformInputContext(nullptr),
62 inputEngine(nullptr),
63 _shiftHandler(nullptr),
64 keyboardRect(),
65 previewRect(),
66 _previewVisible(false),
67 animating(false),
68 _focus(false),
69 cursorPosition(0),
70 anchorPosition(0),
71 forceAnchorPosition(-1),
72 _forceCursorPosition(-1),
73 inputMethodHints(Qt::ImhNone),
74 preeditText(),
75 preeditTextAttributes(),
76 surroundingText(),
77 selectedText(),
78 anchorRectangle(),
79 cursorRectangle(),
80 selectionControlVisible(false),
81 anchorRectIntersectsClipRect(false),
82 cursorRectIntersectsClipRect(false)
83 #ifdef QT_VIRTUALKEYBOARD_ARROW_KEY_NAVIGATION
84 , activeNavigationKeys()
85 #endif
86 {
87 }
88
init()89 void QVirtualKeyboardInputContextPrivate::init()
90 {
91 Q_Q(QVirtualKeyboardInputContext);
92 QGuiApplicationPrivate *guiApplicationPrivate = QGuiApplicationPrivate::instance();
93 QPlatformIntegration *platformIntegration = guiApplicationPrivate->platformIntegration();
94 QPlatformInputContext *unknownPlatformInputContext = platformIntegration->inputContext();
95 platformInputContext = qobject_cast<PlatformInputContext *>(unknownPlatformInputContext);
96 inputEngine = new QVirtualKeyboardInputEngine(q);
97 _shiftHandler = new ShiftHandler(q);
98 inputEngine->init();
99 _shiftHandler->init();
100 _shadow.setInputContext(q);
101 if (platformInputContext) {
102 platformInputContext->setInputContext(q);
103 QObject::connect(platformInputContext, &PlatformInputContext::focusObjectChanged, this, &QVirtualKeyboardInputContextPrivate::onInputItemChanged);
104 QObject::connect(platformInputContext, &PlatformInputContext::focusObjectChanged, this, &QVirtualKeyboardInputContextPrivate::inputItemChanged);
105 }
106 }
107
~QVirtualKeyboardInputContextPrivate()108 QVirtualKeyboardInputContextPrivate::~QVirtualKeyboardInputContextPrivate()
109 {
110 }
111
focus() const112 bool QVirtualKeyboardInputContextPrivate::focus() const
113 {
114 return _focus;
115 }
116
setFocus(bool focus)117 void QVirtualKeyboardInputContextPrivate::setFocus(bool focus)
118 {
119 if (_focus != focus) {
120 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::setFocus():" << focus;
121 _focus = focus;
122 emit focusChanged();
123 }
124 }
125
keyboardRectangle() const126 QRectF QVirtualKeyboardInputContextPrivate::keyboardRectangle() const
127 {
128 return keyboardRect;
129 }
130
setKeyboardRectangle(QRectF rectangle)131 void QVirtualKeyboardInputContextPrivate::setKeyboardRectangle(QRectF rectangle)
132 {
133 if (keyboardRect != rectangle) {
134 keyboardRect = rectangle;
135 emit keyboardRectangleChanged();
136 platformInputContext->emitKeyboardRectChanged();
137 }
138 }
139
previewRectangle() const140 QRectF QVirtualKeyboardInputContextPrivate::previewRectangle() const
141 {
142 return previewRect;
143 }
144
setPreviewRectangle(QRectF rectangle)145 void QVirtualKeyboardInputContextPrivate::setPreviewRectangle(QRectF rectangle)
146 {
147 if (previewRect != rectangle) {
148 previewRect = rectangle;
149 emit previewRectangleChanged();
150 }
151 }
152
previewVisible() const153 bool QVirtualKeyboardInputContextPrivate::previewVisible() const
154 {
155 return _previewVisible;
156 }
157
setPreviewVisible(bool visible)158 void QVirtualKeyboardInputContextPrivate::setPreviewVisible(bool visible)
159 {
160 if (_previewVisible != visible) {
161 _previewVisible = visible;
162 emit previewVisibleChanged();
163 }
164 }
165
locale() const166 QString QVirtualKeyboardInputContextPrivate::locale() const
167 {
168 return platformInputContext ? platformInputContext->locale().name() : QString();
169 }
170
setLocale(const QString & locale)171 void QVirtualKeyboardInputContextPrivate::setLocale(const QString &locale)
172 {
173 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::setLocale():" << locale;
174 QLocale newLocale(locale);
175 if (newLocale != platformInputContext->locale()) {
176 platformInputContext->setLocale(newLocale);
177 platformInputContext->setInputDirection(newLocale.textDirection());
178 emit localeChanged();
179 }
180 }
181
inputItem() const182 QObject *QVirtualKeyboardInputContextPrivate::inputItem() const
183 {
184 return platformInputContext ? platformInputContext->focusObject() : nullptr;
185 }
186
shiftHandler() const187 ShiftHandler *QVirtualKeyboardInputContextPrivate::shiftHandler() const
188 {
189 return _shiftHandler;
190 }
191
shadow() const192 ShadowInputContext *QVirtualKeyboardInputContextPrivate::shadow() const
193 {
194 return const_cast<ShadowInputContext *>(&_shadow);
195 }
196
inputMethods() const197 QStringList QVirtualKeyboardInputContextPrivate::inputMethods() const
198 {
199 return platformInputContext ? platformInputContext->inputMethods() : QStringList();
200 }
201
fileExists(const QUrl & fileUrl)202 bool QVirtualKeyboardInputContextPrivate::fileExists(const QUrl &fileUrl)
203 {
204 QString fileName;
205 if (fileUrl.scheme() == QLatin1String("qrc")) {
206 fileName = QLatin1Char(':') + fileUrl.path();
207 } else {
208 fileName = fileUrl.toLocalFile();
209 }
210 return !fileName.isEmpty() && QFile::exists(fileName);
211 }
212
hasEnterKeyAction(QObject * item) const213 bool QVirtualKeyboardInputContextPrivate::hasEnterKeyAction(QObject *item) const
214 {
215 return item != nullptr && qmlAttachedPropertiesObject<EnterKeyAction>(item, false);
216 }
217
registerInputPanel(QObject * inputPanel)218 void QVirtualKeyboardInputContextPrivate::registerInputPanel(QObject *inputPanel)
219 {
220 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::registerInputPanel():" << inputPanel;
221 Q_ASSERT(!this->inputPanel);
222 this->inputPanel = inputPanel;
223 if (QQuickItem *item = qobject_cast<QQuickItem *>(inputPanel))
224 item->setZ(std::numeric_limits<qreal>::max());
225 }
226
hideInputPanel()227 void QVirtualKeyboardInputContextPrivate::hideInputPanel()
228 {
229 platformInputContext->hideInputPanel();
230 }
231
updateAvailableLocales(const QStringList & availableLocales)232 void QVirtualKeyboardInputContextPrivate::updateAvailableLocales(const QStringList &availableLocales)
233 {
234 Settings *settings = Settings::instance();
235 if (settings)
236 settings->setAvailableLocales(availableLocales);
237 }
238
forceCursorPosition(int anchorPosition,int cursorPosition)239 void QVirtualKeyboardInputContextPrivate::forceCursorPosition(int anchorPosition, int cursorPosition)
240 {
241 if (!_shadow.inputItem())
242 return;
243 if (!platformInputContext->m_visible)
244 return;
245 if (testState(State::Reselect))
246 return;
247 if (testState(State::SyncShadowInput))
248 return;
249
250 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::forceCursorPosition():" << cursorPosition << "anchorPosition:" << anchorPosition;
251 if (!preeditText.isEmpty()) {
252 forceAnchorPosition = -1;
253 _forceCursorPosition = cursorPosition;
254 if (cursorPosition > this->cursorPosition)
255 _forceCursorPosition += preeditText.length();
256 commit();
257 } else {
258 forceAnchorPosition = anchorPosition;
259 _forceCursorPosition = cursorPosition;
260 Q_Q(QVirtualKeyboardInputContext);
261 q->setPreeditText(QString());
262 if (!inputMethodHints.testFlag(Qt::ImhNoPredictiveText) &&
263 cursorPosition > 0 && selectedText.isEmpty()) {
264 QVirtualKeyboardScopedState reselectState(this, State::Reselect);
265 if (inputEngine->reselect(cursorPosition, QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor))
266 setState(State::InputMethodClick);
267 }
268 }
269 }
270
onInputItemChanged()271 void QVirtualKeyboardInputContextPrivate::onInputItemChanged()
272 {
273 if (QObject *item = inputItem()) {
274 if (QQuickItem *vkbPanel = qobject_cast<QQuickItem*>(inputPanel)) {
275 if (QQuickItem *quickItem = qobject_cast<QQuickItem*>(item)) {
276 const QVariant isDesktopPanel = vkbPanel->property("desktopPanel");
277 /*
278 For integrated keyboards, make sure it's a sibling to the overlay. The
279 high z-order will make sure it gets events also during a modal session.
280 */
281 if (isDesktopPanel.isValid() && !isDesktopPanel.toBool()) {
282 if (QQuickWindow *quickWindow = quickItem->window())
283 vkbPanel->setParentItem(quickWindow->contentItem());
284 }
285 }
286 }
287 } else {
288 if (!activeKeys.isEmpty()) {
289 // After losing keyboard focus it is impossible to track pressed keys
290 activeKeys.clear();
291 clearState(State::KeyEvent);
292 }
293 }
294 clearState(State::InputMethodClick);
295 }
296
sendPreedit(const QString & text,const QList<QInputMethodEvent::Attribute> & attributes,int replaceFrom,int replaceLength)297 void QVirtualKeyboardInputContextPrivate::sendPreedit(const QString &text, const QList<QInputMethodEvent::Attribute> &attributes, int replaceFrom, int replaceLength)
298 {
299 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::sendPreedit()"
300 #ifdef SENSITIVE_DEBUG
301 << text << replaceFrom << replaceLength
302 #endif
303 ;
304
305 bool textChanged = preeditText != text;
306 bool attributesChanged = preeditTextAttributes != attributes;
307
308 if (textChanged || attributesChanged) {
309 preeditText = text;
310 preeditTextAttributes = attributes;
311
312 if (platformInputContext) {
313 QInputMethodEvent event(text, attributes);
314 const bool replace = replaceFrom != 0 || replaceLength > 0;
315 if (replace)
316 event.setCommitString(QString(), replaceFrom, replaceLength);
317
318 sendInputMethodEvent(&event);
319
320 // Send also to shadow input if only attributes changed.
321 // In this case the update() may not be called, so the shadow
322 // input may be out of sync.
323 if (_shadow.inputItem() && !replace && !text.isEmpty() &&
324 !textChanged && attributesChanged) {
325 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::sendPreedit(shadow)"
326 #ifdef SENSITIVE_DEBUG
327 << text << replaceFrom << replaceLength
328 #endif
329 ;
330 event.setAccepted(true);
331 QGuiApplication::sendEvent(_shadow.inputItem(), &event);
332 }
333 }
334
335 if (textChanged) {
336 Q_Q(QVirtualKeyboardInputContext);
337 emit q->preeditTextChanged();
338 }
339 }
340
341 if (preeditText.isEmpty())
342 preeditTextAttributes.clear();
343 }
344
sendInputMethodEvent(QInputMethodEvent * event)345 void QVirtualKeyboardInputContextPrivate::sendInputMethodEvent(QInputMethodEvent *event)
346 {
347 QVirtualKeyboardScopedState inputMethodEventState(this, State::InputMethodEvent);
348 platformInputContext->sendEvent(event);
349 }
350
reset()351 void QVirtualKeyboardInputContextPrivate::reset()
352 {
353 inputEngine->reset();
354 }
355
commit()356 void QVirtualKeyboardInputContextPrivate::commit()
357 {
358 inputEngine->update();
359 }
360
update(Qt::InputMethodQueries queries)361 void QVirtualKeyboardInputContextPrivate::update(Qt::InputMethodQueries queries)
362 {
363 Q_Q(QVirtualKeyboardInputContext);
364
365 // No need to fetch input clip rectangle during animation
366 if (!(queries & ~Qt::ImInputItemClipRectangle) && animating)
367 return;
368
369 // fetch
370 QInputMethodQueryEvent imQueryEvent(Qt::InputMethodQueries(Qt::ImHints |
371 Qt::ImQueryInput | Qt::ImInputItemClipRectangle));
372 platformInputContext->sendEvent(&imQueryEvent);
373 Qt::InputMethodHints inputMethodHints = Qt::InputMethodHints(imQueryEvent.value(Qt::ImHints).toInt());
374 const int cursorPosition = imQueryEvent.value(Qt::ImCursorPosition).toInt();
375 const int anchorPosition = imQueryEvent.value(Qt::ImAnchorPosition).toInt();
376 QRectF anchorRectangle;
377 QRectF cursorRectangle;
378 if (const QGuiApplication *app = qApp) {
379 anchorRectangle = app->inputMethod()->anchorRectangle();
380 cursorRectangle = app->inputMethod()->cursorRectangle();
381 } else {
382 anchorRectangle = this->anchorRectangle;
383 cursorRectangle = this->cursorRectangle;
384 }
385 QString surroundingText = imQueryEvent.value(Qt::ImSurroundingText).toString();
386 QString selectedText = imQueryEvent.value(Qt::ImCurrentSelection).toString();
387
388 // check against changes
389 bool newInputMethodHints = inputMethodHints != this->inputMethodHints;
390 bool newSurroundingText = surroundingText != this->surroundingText;
391 bool newSelectedText = selectedText != this->selectedText;
392 bool newAnchorPosition = anchorPosition != this->anchorPosition;
393 bool newCursorPosition = cursorPosition != this->cursorPosition;
394 bool newAnchorRectangle = anchorRectangle != this->anchorRectangle;
395 bool newCursorRectangle = cursorRectangle != this->cursorRectangle;
396 bool selectionControlVisible = platformInputContext->isInputPanelVisible() && (cursorPosition != anchorPosition) && !inputMethodHints.testFlag(Qt::ImhNoTextHandles);
397 bool newSelectionControlVisible = selectionControlVisible != this->selectionControlVisible;
398
399 QRectF inputItemClipRect = imQueryEvent.value(Qt::ImInputItemClipRectangle).toRectF();
400 QRectF anchorRect = imQueryEvent.value(Qt::ImAnchorRectangle).toRectF();
401 QRectF cursorRect = imQueryEvent.value(Qt::ImCursorRectangle).toRectF();
402
403 bool anchorRectIntersectsClipRect = inputItemClipRect.intersects(anchorRect);
404 bool newAnchorRectIntersectsClipRect = anchorRectIntersectsClipRect != this->anchorRectIntersectsClipRect;
405
406 bool cursorRectIntersectsClipRect = inputItemClipRect.intersects(cursorRect);
407 bool newCursorRectIntersectsClipRect = cursorRectIntersectsClipRect != this->cursorRectIntersectsClipRect;
408
409 // update
410 this->inputMethodHints = inputMethodHints;
411 this->surroundingText = surroundingText;
412 this->selectedText = selectedText;
413 this->anchorPosition = anchorPosition;
414 this->cursorPosition = cursorPosition;
415 this->anchorRectangle = anchorRectangle;
416 this->cursorRectangle = cursorRectangle;
417 this->selectionControlVisible = selectionControlVisible;
418 this->anchorRectIntersectsClipRect = anchorRectIntersectsClipRect;
419 this->cursorRectIntersectsClipRect = cursorRectIntersectsClipRect;
420
421 // update input engine
422 if ((newSurroundingText || newCursorPosition) &&
423 !testState(State::InputMethodEvent)) {
424 commit();
425 }
426 if (newInputMethodHints) {
427 reset();
428 }
429
430 // notify
431 if (newInputMethodHints) {
432 emit q->inputMethodHintsChanged();
433 }
434 if (newSurroundingText) {
435 emit q->surroundingTextChanged();
436 }
437 if (newSelectedText) {
438 emit q->selectedTextChanged();
439 }
440 if (newAnchorPosition) {
441 emit q->anchorPositionChanged();
442 }
443 if (newCursorPosition) {
444 emit q->cursorPositionChanged();
445 }
446 if (newAnchorRectangle) {
447 emit q->anchorRectangleChanged();
448 }
449 if (newCursorRectangle) {
450 emit q->cursorRectangleChanged();
451 }
452 if (newSelectionControlVisible) {
453 emit q->selectionControlVisibleChanged();
454 }
455 if (newAnchorRectIntersectsClipRect) {
456 emit q->anchorRectIntersectsClipRectChanged();
457 }
458 if (newCursorRectIntersectsClipRect) {
459 emit q->cursorRectIntersectsClipRectChanged();
460 }
461
462 // word reselection
463 if (newInputMethodHints || newSurroundingText || newSelectedText)
464 clearState(State::InputMethodClick);
465 if ((newSurroundingText || newCursorPosition) && !newSelectedText && isEmptyState() &&
466 !inputMethodHints.testFlag(Qt::ImhNoPredictiveText) &&
467 cursorPosition > 0 && this->selectedText.isEmpty()) {
468 QVirtualKeyboardScopedState reselectState(this, State::Reselect);
469 if (inputEngine->reselect(cursorPosition, QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor))
470 setState(State::InputMethodClick);
471 }
472
473 if (!testState(State::SyncShadowInput)) {
474 QVirtualKeyboardScopedState syncShadowInputState(this, State::SyncShadowInput);
475 _shadow.update(queries);
476 }
477 }
478
invokeAction(QInputMethod::Action action,int cursorPosition)479 void QVirtualKeyboardInputContextPrivate::invokeAction(QInputMethod::Action action, int cursorPosition)
480 {
481 switch (action) {
482 case QInputMethod::Click:
483 if (isEmptyState()) {
484 if (inputEngine->clickPreeditText(cursorPosition))
485 break;
486
487 bool reselect = !inputMethodHints.testFlag(Qt::ImhNoPredictiveText) && selectedText.isEmpty() && cursorPosition < preeditText.length();
488 if (reselect) {
489 QVirtualKeyboardScopedState reselectState(this, State::Reselect);
490 _forceCursorPosition = this->cursorPosition + cursorPosition;
491 commit();
492 inputEngine->reselect(this->cursorPosition, QVirtualKeyboardInputEngine::ReselectFlag::WordBeforeCursor);
493 } else if (!preeditText.isEmpty() && cursorPosition == preeditText.length()) {
494 commit();
495 }
496 }
497 clearState(State::InputMethodClick);
498 break;
499
500 case QInputMethod::ContextMenu:
501 break;
502 }
503 }
504
filterEvent(const QEvent * event)505 bool QVirtualKeyboardInputContextPrivate::filterEvent(const QEvent *event)
506 {
507 QEvent::Type type = event->type();
508 if (type == QEvent::KeyPress || type == QEvent::KeyRelease) {
509 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
510
511 // Keep track of pressed keys update key event state
512 if (type == QEvent::KeyPress)
513 activeKeys += keyEvent->nativeScanCode();
514 else if (type == QEvent::KeyRelease)
515 activeKeys -= keyEvent->nativeScanCode();
516
517 if (activeKeys.isEmpty())
518 clearState(State::KeyEvent);
519 else
520 setState(State::KeyEvent);
521
522 #ifdef QT_VIRTUALKEYBOARD_ARROW_KEY_NAVIGATION
523 int key = keyEvent->key();
524 if ((key >= Qt::Key_Left && key <= Qt::Key_Down) || key == Qt::Key_Return) {
525 if (type == QEvent::KeyPress && platformInputContext->isInputPanelVisible()) {
526 activeNavigationKeys += key;
527 emit navigationKeyPressed(key, keyEvent->isAutoRepeat());
528 return true;
529 } else if (type == QEvent::KeyRelease && activeNavigationKeys.contains(key)) {
530 activeNavigationKeys -= key;
531 emit navigationKeyReleased(key, keyEvent->isAutoRepeat());
532 return true;
533 }
534 }
535 #endif
536
537 // Break composing text since the virtual keyboard does not support hard keyboard events
538 if (!preeditText.isEmpty())
539 commit();
540 }
541 #ifdef QT_VIRTUALKEYBOARD_ARROW_KEY_NAVIGATION
542 else if (type == QEvent::ShortcutOverride) {
543 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
544 int key = keyEvent->key();
545 if ((key >= Qt::Key_Left && key <= Qt::Key_Down) || key == Qt::Key_Return)
546 return true;
547 }
548 #endif
549
550 return false;
551 }
552
addSelectionAttribute(QList<QInputMethodEvent::Attribute> & attributes)553 void QVirtualKeyboardInputContextPrivate::addSelectionAttribute(QList<QInputMethodEvent::Attribute> &attributes)
554 {
555 if (!testAttribute(attributes, QInputMethodEvent::Selection)) {
556 // Convert Cursor attribute to Selection attribute.
557 // In this case the cursor is set in pre-edit text, but
558 // the cursor is not being forced to specific location.
559 if (_forceCursorPosition == -1) {
560 int cursorAttributeIndex = findAttribute(preeditTextAttributes, QInputMethodEvent::Cursor);
561 if (cursorAttributeIndex != -1 && preeditTextAttributes[cursorAttributeIndex].length > 0)
562 _forceCursorPosition = cursorPosition + preeditTextAttributes[cursorAttributeIndex].start;
563 forceAnchorPosition = -1;
564 }
565
566 if (_forceCursorPosition != -1) {
567 if (forceAnchorPosition != -1)
568 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, forceAnchorPosition, _forceCursorPosition - forceAnchorPosition, QVariant()));
569 else
570 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, _forceCursorPosition, 0, QVariant()));
571 }
572 }
573 forceAnchorPosition = -1;
574 _forceCursorPosition = -1;
575 }
576
testAttribute(const QList<QInputMethodEvent::Attribute> & attributes,QInputMethodEvent::AttributeType attributeType) const577 bool QVirtualKeyboardInputContextPrivate::testAttribute(const QList<QInputMethodEvent::Attribute> &attributes, QInputMethodEvent::AttributeType attributeType) const
578 {
579 for (const QInputMethodEvent::Attribute &attribute : qAsConst(attributes)) {
580 if (attribute.type == attributeType)
581 return true;
582 }
583 return false;
584 }
585
findAttribute(const QList<QInputMethodEvent::Attribute> & attributes,QInputMethodEvent::AttributeType attributeType) const586 int QVirtualKeyboardInputContextPrivate::findAttribute(const QList<QInputMethodEvent::Attribute> &attributes, QInputMethodEvent::AttributeType attributeType) const
587 {
588 const int count = attributes.count();
589 for (int i = 0; i < count; ++i) {
590 if (attributes.at(i).type == attributeType)
591 return i;
592 }
593 return -1;
594 }
595
596 QT_END_NAMESPACE
597