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