1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtVirtualKeyboard/private/qvirtualkeyboardinputcontext_p.h>
5#include <QtVirtualKeyboard/private/platforminputcontext_p.h>
6#include <QtVirtualKeyboard/private/settings_p.h>
7#include <QtVirtualKeyboard/private/shifthandler_p.h>
8#include <QtVirtualKeyboard/private/virtualkeyboarddebug_p.h>
9#include <QtVirtualKeyboard/private/enterkeyaction_p.h>
10#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
11#include <QtVirtualKeyboard/qvirtualkeyboardobserver.h>
12#include <QtVirtualKeyboard/private/virtualkeyboardattachedtype_p.h>
13#include <QtVirtualKeyboard/qvirtualkeyboarddictionarymanager.h>
14
15#include <QFile>
16#include <QGuiApplication>
17#include <QtQuick/qquickitem.h>
18#include <QtQuick/qquickwindow.h>
19#include <QtGui/qpa/qplatformintegration.h>
20#include <QtGui/private/qguiapplication_p.h>
21#include <QQmlEngine>
22
23QT_BEGIN_NAMESPACE
24
25using namespace QtVirtualKeyboard;
26
27const bool QtVirtualKeyboard::QT_VIRTUALKEYBOARD_FORCE_EVENTS_WITHOUT_FOCUS = qEnvironmentVariableIsSet(varName: "QT_VIRTUALKEYBOARD_FORCE_EVENTS_WITHOUT_FOCUS");
28
29QVirtualKeyboardInputContextPrivate::QVirtualKeyboardInputContextPrivate(QVirtualKeyboardInputContext *q_ptr) :
30 QObject(nullptr),
31 q_ptr(q_ptr),
32 platformInputContext(nullptr),
33 inputEngine(nullptr),
34 _shiftHandler(nullptr),
35 keyboardRect(),
36 previewRect(),
37 _previewVisible(false),
38 animating(false),
39 _focus(false),
40 cursorPosition(0),
41 anchorPosition(0),
42 forceAnchorPosition(-1),
43 _forceCursorPosition(-1),
44 inputMethodHints(Qt::ImhNone),
45 preeditText(),
46 preeditTextAttributes(),
47 surroundingText(),
48 selectedText(),
49 anchorRectangle(),
50 cursorRectangle(),
51 selectionControlVisible(false),
52 anchorRectIntersectsClipRect(false),
53 cursorRectIntersectsClipRect(false)
54#ifdef QT_VIRTUALKEYBOARD_ARROW_KEY_NAVIGATION
55 , activeNavigationKeys()
56#endif
57{
58}
59
60void QVirtualKeyboardInputContextPrivate::init()
61{
62 Q_Q(QVirtualKeyboardInputContext);
63 QGuiApplicationPrivate *guiApplicationPrivate = QGuiApplicationPrivate::instance();
64 QPlatformIntegration *platformIntegration = guiApplicationPrivate->platformIntegration();
65 QPlatformInputContext *unknownPlatformInputContext = platformIntegration->inputContext();
66 platformInputContext = qobject_cast<PlatformInputContext *>(object: unknownPlatformInputContext);
67 inputEngine = new QVirtualKeyboardInputEngine(q);
68 _shiftHandler = new ShiftHandler(q);
69 inputEngine->init();
70 _shiftHandler->init();
71 _shadow.setInputContext(q);
72 if (platformInputContext) {
73 platformInputContext->setInputContext(q);
74 QObject::connect(sender: platformInputContext, signal: &PlatformInputContext::focusObjectChanged, context: this, slot: &QVirtualKeyboardInputContextPrivate::onInputItemChanged);
75 QObject::connect(sender: platformInputContext, signal: &PlatformInputContext::focusObjectChanged, context: this, slot: &QVirtualKeyboardInputContextPrivate::inputItemChanged);
76 }
77}
78
79QVirtualKeyboardInputContextPrivate::~QVirtualKeyboardInputContextPrivate()
80{
81}
82
83bool QVirtualKeyboardInputContextPrivate::focus() const
84{
85 return _focus;
86}
87
88void QVirtualKeyboardInputContextPrivate::setFocus(bool focus)
89{
90 if (_focus != focus) {
91 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::setFocus():" << focus;
92 _focus = focus;
93 emit focusChanged();
94 }
95}
96
97QRectF QVirtualKeyboardInputContextPrivate::keyboardRectangle() const
98{
99 return keyboardRect;
100}
101
102void QVirtualKeyboardInputContextPrivate::setKeyboardRectangle(QRectF rectangle)
103{
104 if (keyboardRect != rectangle) {
105 keyboardRect = rectangle;
106 emit keyboardRectangleChanged();
107 platformInputContext->emitKeyboardRectChanged();
108 }
109}
110
111QRectF QVirtualKeyboardInputContextPrivate::previewRectangle() const
112{
113 return previewRect;
114}
115
116void QVirtualKeyboardInputContextPrivate::setPreviewRectangle(QRectF rectangle)
117{
118 if (previewRect != rectangle) {
119 previewRect = rectangle;
120 emit previewRectangleChanged();
121 }
122}
123
124bool QVirtualKeyboardInputContextPrivate::previewVisible() const
125{
126 return _previewVisible;
127}
128
129void QVirtualKeyboardInputContextPrivate::setPreviewVisible(bool visible)
130{
131 if (_previewVisible != visible) {
132 _previewVisible = visible;
133 emit previewVisibleChanged();
134 }
135}
136
137QString QVirtualKeyboardInputContextPrivate::locale() const
138{
139 return platformInputContext ? platformInputContext->locale().name() : QString();
140}
141
142void QVirtualKeyboardInputContextPrivate::setLocale(const QString &locale)
143{
144 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::setLocale():" << locale;
145 if (!platformInputContext)
146 return;
147 QLocale newLocale(locale);
148 if (newLocale != platformInputContext->locale()) {
149 platformInputContext->setLocale(newLocale);
150 platformInputContext->setInputDirection(newLocale.textDirection());
151 emit localeChanged();
152 }
153}
154
155QObject *QVirtualKeyboardInputContextPrivate::inputItem() const
156{
157 return platformInputContext ? platformInputContext->focusObject() : nullptr;
158}
159
160ShiftHandler *QVirtualKeyboardInputContextPrivate::shiftHandler() const
161{
162 return _shiftHandler;
163}
164
165ShadowInputContext *QVirtualKeyboardInputContextPrivate::shadow() const
166{
167 return const_cast<ShadowInputContext *>(&_shadow);
168}
169
170void QVirtualKeyboardInputContextPrivate::setKeyboardObserver(QVirtualKeyboardObserver *keyboardObserver)
171{
172 if (!this->keyboardObserver.isNull())
173 return;
174
175 this->keyboardObserver = keyboardObserver;
176}
177
178bool QVirtualKeyboardInputContextPrivate::fileExists(const QUrl &fileUrl)
179{
180 QString fileName;
181 if (fileUrl.scheme() == QLatin1String("qrc")) {
182 fileName = QLatin1Char(':') + fileUrl.path();
183 } else {
184 fileName = fileUrl.toLocalFile();
185 }
186 return !fileName.isEmpty() && QFile::exists(fileName);
187}
188
189bool QVirtualKeyboardInputContextPrivate::hasEnterKeyAction(QObject *item) const
190{
191 return item != nullptr && qmlAttachedPropertiesObject<EnterKeyAction>(obj: item, create: false);
192}
193
194void QVirtualKeyboardInputContextPrivate::registerInputPanel(QObject *inputPanel)
195{
196 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::registerInputPanel():" << inputPanel;
197 Q_ASSERT(!this->inputPanel);
198 this->inputPanel = inputPanel;
199}
200
201void QVirtualKeyboardInputContextPrivate::hideInputPanel()
202{
203 platformInputContext->hideInputPanel();
204}
205
206void QVirtualKeyboardInputContextPrivate::updateAvailableLocales(const QStringList &availableLocales)
207{
208 Settings *settings = Settings::instance();
209 if (settings)
210 settings->setAvailableLocales(availableLocales);
211}
212
213void QVirtualKeyboardInputContextPrivate::forceCursorPosition(int anchorPosition, int cursorPosition)
214{
215 if (!_shadow.inputItem())
216 return;
217 if (!platformInputContext->m_visible)
218 return;
219 if (testState(state: State::Reselect))
220 return;
221 if (testState(state: State::SyncShadowInput))
222 return;
223
224 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::forceCursorPosition():" << cursorPosition << "anchorPosition:" << anchorPosition;
225 if (!preeditText.isEmpty()) {
226 forceAnchorPosition = -1;
227 _forceCursorPosition = cursorPosition;
228 if (cursorPosition > this->cursorPosition)
229 _forceCursorPosition += preeditText.size();
230 commit();
231 } else {
232 forceAnchorPosition = anchorPosition;
233 _forceCursorPosition = cursorPosition;
234 Q_Q(QVirtualKeyboardInputContext);
235 q->setPreeditText(text: QString());
236 if (!inputMethodHints.testFlag(flag: Qt::ImhNoPredictiveText) &&
237 cursorPosition > 0 && selectedText.isEmpty()) {
238 QVirtualKeyboardScopedState reselectState(this, State::Reselect);
239 if (inputEngine->reselect(cursorPosition, reselectFlags: QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor))
240 setState(State::InputMethodClick);
241 }
242 }
243}
244
245/*! \internal
246 The context private becomes a containment mask for a dimmer opened by a
247 modal QQuickPopup. The dimmer eats events, and the virtual keyboard must
248 continue to work during modal sessions as well. This implementation lets
249 all pointer events within the area of the input panel through.
250*/
251bool QVirtualKeyboardInputContextPrivate::contains(const QPointF &point) const
252{
253 bool hit = false;
254 if (dimmer) {
255 const auto scenePoint = dimmer->mapToScene(point);
256 if (keyboardRectangle().contains(p: scenePoint)) {
257 hit = true;
258 } else if (QQuickItem *vkbPanel = qobject_cast<QQuickItem*>(o: inputPanel)) {
259 const auto vkbPanelPoint = vkbPanel->mapFromScene(point: scenePoint);
260 if (vkbPanel->contains(point: vkbPanelPoint))
261 hit = true;
262 }
263 }
264 // dimmer doesn't contain points that hit the input panel
265 return !hit;
266}
267
268KeyboardFunctionKey QVirtualKeyboardInputContextPrivate::keyboardFunctionKey(QtVirtualKeyboard::KeyboardFunction keyboardFunction) const
269{
270 switch (keyboardFunction) {
271 case KeyboardFunction::HideInputPanel:
272 return KeyboardFunctionKey::Hide;
273 case KeyboardFunction::ChangeLanguage:
274 return KeyboardFunctionKey::Language;
275 case KeyboardFunction::ToggleHandwritingMode:
276 return KeyboardFunctionKey::None;
277 }
278 return KeyboardFunctionKey::None;
279}
280
281void QVirtualKeyboardInputContextPrivate::onInputItemChanged()
282{
283 QObject *item = inputItem();
284 if (item) {
285 if (QQuickItem *vkbPanel = qobject_cast<QQuickItem*>(o: inputPanel)) {
286 if (QQuickItem *quickItem = qobject_cast<QQuickItem*>(o: item)) {
287 const QVariant isDesktopPanel = vkbPanel->property(name: "desktopPanel");
288 if (isDesktopPanel.isValid() && !isDesktopPanel.toBool()) {
289 // Integrated keyboards used in a Qt Quick Controls UI must continue to
290 // work during a modal session, which is implemented using an overlay
291 // and dimmer item. So, make use of some QQC2 internals to find out if
292 // there is a dimmer, and if so, make ourselves the containment mask
293 // that can let pointer events through to the keyboard.
294 if (QQuickWindow *quickWindow = quickItem->window()) {
295 if (QQuickItem *overlay = quickWindow->property(name: "_q_QQuickOverlay").value<QQuickItem*>()) {
296 if (dimmer && dimmer->containmentMask() == this) {
297 dimmer->setContainmentMask(nullptr);
298 dimmer = nullptr;
299 }
300 if (overlay && overlay->isVisible()) {
301 dimmer = overlay->property(name: "_q_dimmerItem").value<QQuickItem*>();
302 if (dimmer)
303 dimmer->setContainmentMask(this);
304 }
305 }
306 }
307 }
308 }
309 }
310 } else {
311 if (!activeKeys.isEmpty()) {
312 // After losing keyboard focus it is impossible to track pressed keys
313 activeKeys.clear();
314 clearState(state: State::KeyEvent);
315 }
316 }
317 clearState(state: State::InputMethodClick);
318
319 QStringList extraDictionaries;
320 if (item) {
321 VirtualKeyboardAttachedType *virtualKeyboardAttachedType = static_cast<VirtualKeyboardAttachedType *>(qmlAttachedPropertiesObject<VirtualKeyboard>(obj: item, create: false));
322 if (virtualKeyboardAttachedType)
323 extraDictionaries = virtualKeyboardAttachedType->extraDictionaries();
324 }
325 QVirtualKeyboardDictionaryManager::instance()->setExtraDictionaries(extraDictionaries);
326}
327
328void QVirtualKeyboardInputContextPrivate::sendPreedit(const QString &text, const QList<QInputMethodEvent::Attribute> &attributes, int replaceFrom, int replaceLength)
329{
330 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::sendPreedit()"
331#ifdef SENSITIVE_DEBUG
332 << text << replaceFrom << replaceLength
333#endif
334 ;
335
336 bool textChanged = preeditText != text;
337 bool attributesChanged = preeditTextAttributes != attributes;
338
339 if (textChanged || attributesChanged) {
340 preeditText = text;
341 preeditTextAttributes = attributes;
342
343 if (platformInputContext) {
344 QInputMethodEvent event(text, attributes);
345 const bool replace = replaceFrom != 0 || replaceLength > 0;
346 if (replace)
347 event.setCommitString(commitString: QString(), replaceFrom, replaceLength);
348
349 sendInputMethodEvent(event: &event);
350
351 // Send also to shadow input if only attributes changed.
352 // In this case the update() may not be called, so the shadow
353 // input may be out of sync.
354 if (_shadow.inputItem() && !replace && !text.isEmpty() &&
355 !textChanged && attributesChanged) {
356 VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::sendPreedit(shadow)"
357#ifdef SENSITIVE_DEBUG
358 << text << replaceFrom << replaceLength
359#endif
360 ;
361 event.setAccepted(true);
362 QGuiApplication::sendEvent(receiver: _shadow.inputItem(), event: &event);
363 }
364 }
365
366 if (textChanged) {
367 Q_Q(QVirtualKeyboardInputContext);
368 emit q->preeditTextChanged();
369 }
370 }
371
372 if (preeditText.isEmpty())
373 preeditTextAttributes.clear();
374}
375
376void QVirtualKeyboardInputContextPrivate::sendInputMethodEvent(QInputMethodEvent *event)
377{
378 QVirtualKeyboardScopedState inputMethodEventState(this, State::InputMethodEvent);
379 platformInputContext->sendEvent(event);
380}
381
382void QVirtualKeyboardInputContextPrivate::reset()
383{
384 inputEngine->reset();
385}
386
387void QVirtualKeyboardInputContextPrivate::commit()
388{
389 inputEngine->update();
390}
391
392void QVirtualKeyboardInputContextPrivate::update(Qt::InputMethodQueries queries)
393{
394 Q_Q(QVirtualKeyboardInputContext);
395
396 // No need to fetch input clip rectangle during animation
397 if (!(queries & ~Qt::ImInputItemClipRectangle) && animating)
398 return;
399
400 // fetch
401 QInputMethodQueryEvent imQueryEvent(Qt::InputMethodQueries(Qt::ImHints |
402 Qt::ImQueryInput | Qt::ImInputItemClipRectangle));
403 platformInputContext->sendEvent(event: &imQueryEvent);
404 Qt::InputMethodHints inputMethodHints = Qt::InputMethodHints(imQueryEvent.value(query: Qt::ImHints).toInt());
405 inputMethodHints |= Settings::instance()->inputMethodHints();
406 const int cursorPosition = imQueryEvent.value(query: Qt::ImCursorPosition).toInt();
407 const int anchorPosition = imQueryEvent.value(query: Qt::ImAnchorPosition).toInt();
408 QRectF anchorRectangle;
409 QRectF cursorRectangle;
410 if (const QGuiApplication *app = qApp) {
411 anchorRectangle = app->inputMethod()->anchorRectangle();
412 cursorRectangle = app->inputMethod()->cursorRectangle();
413 } else {
414 anchorRectangle = this->anchorRectangle;
415 cursorRectangle = this->cursorRectangle;
416 }
417 QString surroundingText = imQueryEvent.value(query: Qt::ImSurroundingText).toString();
418 QString selectedText = imQueryEvent.value(query: Qt::ImCurrentSelection).toString();
419
420 // check against changes
421 bool newInputMethodHints = inputMethodHints != this->inputMethodHints;
422 bool newSurroundingText = surroundingText != this->surroundingText;
423 bool newSelectedText = selectedText != this->selectedText;
424 bool newAnchorPosition = anchorPosition != this->anchorPosition;
425 bool newCursorPosition = cursorPosition != this->cursorPosition;
426 bool newAnchorRectangle = anchorRectangle != this->anchorRectangle;
427 bool newCursorRectangle = cursorRectangle != this->cursorRectangle;
428 bool selectionControlVisible = platformInputContext->evaluateInputPanelVisible() && (cursorPosition != anchorPosition) && !inputMethodHints.testFlag(flag: Qt::ImhNoTextHandles);
429 bool newSelectionControlVisible = selectionControlVisible != this->selectionControlVisible;
430
431 QRectF inputItemClipRect = imQueryEvent.value(query: Qt::ImInputItemClipRectangle).toRectF();
432 QRectF anchorRect = imQueryEvent.value(query: Qt::ImAnchorRectangle).toRectF();
433 QRectF cursorRect = imQueryEvent.value(query: Qt::ImCursorRectangle).toRectF();
434
435 bool anchorRectIntersectsClipRect = inputItemClipRect.intersects(r: anchorRect);
436 bool newAnchorRectIntersectsClipRect = anchorRectIntersectsClipRect != this->anchorRectIntersectsClipRect;
437
438 bool cursorRectIntersectsClipRect = inputItemClipRect.intersects(r: cursorRect);
439 bool newCursorRectIntersectsClipRect = cursorRectIntersectsClipRect != this->cursorRectIntersectsClipRect;
440
441 // update
442 this->inputMethodHints = inputMethodHints;
443 this->surroundingText = surroundingText;
444 this->selectedText = selectedText;
445 this->anchorPosition = anchorPosition;
446 this->cursorPosition = cursorPosition;
447 this->anchorRectangle = anchorRectangle;
448 this->cursorRectangle = cursorRectangle;
449 this->selectionControlVisible = selectionControlVisible;
450 this->anchorRectIntersectsClipRect = anchorRectIntersectsClipRect;
451 this->cursorRectIntersectsClipRect = cursorRectIntersectsClipRect;
452
453 // update input engine
454 if ((newSurroundingText || newCursorPosition) &&
455 !testState(state: State::InputMethodEvent)) {
456 commit();
457 }
458 if (newInputMethodHints) {
459 reset();
460 }
461
462 // notify
463 if (newInputMethodHints) {
464 emit q->inputMethodHintsChanged();
465 }
466 if (newSurroundingText) {
467 emit q->surroundingTextChanged();
468 }
469 if (newSelectedText) {
470 emit q->selectedTextChanged();
471 }
472 if (newAnchorPosition) {
473 emit q->anchorPositionChanged();
474 }
475 if (newCursorPosition) {
476 emit q->cursorPositionChanged();
477 }
478 if (newAnchorRectangle) {
479 emit q->anchorRectangleChanged();
480 }
481 if (newCursorRectangle) {
482 emit q->cursorRectangleChanged();
483 }
484 if (newSelectionControlVisible) {
485 emit q->selectionControlVisibleChanged();
486 }
487 if (newAnchorRectIntersectsClipRect) {
488 emit q->anchorRectIntersectsClipRectChanged();
489 }
490 if (newCursorRectIntersectsClipRect) {
491 emit q->cursorRectIntersectsClipRectChanged();
492 }
493
494 // word reselection
495 if (newInputMethodHints || newSurroundingText || newSelectedText)
496 clearState(state: State::InputMethodClick);
497 if ((newSurroundingText || newCursorPosition) && !newSelectedText && isEmptyState() &&
498 !inputMethodHints.testFlag(flag: Qt::ImhNoPredictiveText) &&
499 cursorPosition > 0 && this->selectedText.isEmpty()) {
500 QVirtualKeyboardScopedState reselectState(this, State::Reselect);
501 if (inputEngine->reselect(cursorPosition, reselectFlags: QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor))
502 setState(State::InputMethodClick);
503 }
504
505 if (!testState(state: State::SyncShadowInput)) {
506 QVirtualKeyboardScopedState syncShadowInputState(this, State::SyncShadowInput);
507 _shadow.update(queries);
508 }
509}
510
511void QVirtualKeyboardInputContextPrivate::invokeAction(QInputMethod::Action action, int cursorPosition)
512{
513 switch (action) {
514 case QInputMethod::Click:
515 if (isEmptyState()) {
516 if (inputEngine->clickPreeditText(cursorPosition))
517 break;
518
519 bool reselect = !inputMethodHints.testFlag(flag: Qt::ImhNoPredictiveText) && selectedText.isEmpty() && cursorPosition < preeditText.size();
520 if (reselect) {
521 QVirtualKeyboardScopedState reselectState(this, State::Reselect);
522 _forceCursorPosition = this->cursorPosition + cursorPosition;
523 commit();
524 inputEngine->reselect(cursorPosition: this->cursorPosition, reselectFlags: QVirtualKeyboardInputEngine::ReselectFlag::WordBeforeCursor);
525 } else if (!preeditText.isEmpty() && cursorPosition == preeditText.size()) {
526 commit();
527 }
528 }
529 clearState(state: State::InputMethodClick);
530 break;
531
532 case QInputMethod::ContextMenu:
533 break;
534 }
535}
536
537bool QVirtualKeyboardInputContextPrivate::filterEvent(const QEvent *event)
538{
539 QEvent::Type type = event->type();
540 if (type == QEvent::KeyPress || type == QEvent::KeyRelease) {
541 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
542 const int key = keyEvent->key();
543
544 // Keep track of pressed keys update key event state
545 if (type == QEvent::KeyPress)
546 activeKeys += keyEvent->nativeScanCode();
547 else if (type == QEvent::KeyRelease)
548 activeKeys -= keyEvent->nativeScanCode();
549
550 if (activeKeys.isEmpty())
551 clearState(state: State::KeyEvent);
552 else
553 setState(State::KeyEvent);
554
555#ifdef QT_VIRTUALKEYBOARD_ARROW_KEY_NAVIGATION
556 if ((key >= Qt::Key_Left && key <= Qt::Key_Down) || key == Qt::Key_Return) {
557 if (type == QEvent::KeyPress && platformInputContext->isInputPanelVisible()) {
558 activeNavigationKeys += key;
559 emit navigationKeyPressed(key, keyEvent->isAutoRepeat());
560 return true;
561 } else if (type == QEvent::KeyRelease && activeNavigationKeys.contains(key)) {
562 activeNavigationKeys -= key;
563 emit navigationKeyReleased(key, keyEvent->isAutoRepeat());
564 return true;
565 }
566 }
567#endif
568
569 // Break composing text since the virtual keyboard does not support hard keyboard events
570 if (!preeditText.isEmpty()) {
571 if (type == QEvent::KeyPress && (key == Qt::Key_Delete || key == Qt::Key_Backspace)) {
572 reset();
573 Q_Q(QVirtualKeyboardInputContext);
574 q->clear();
575 return true;
576 } else {
577 commit();
578 }
579 }
580 }
581#ifdef QT_VIRTUALKEYBOARD_ARROW_KEY_NAVIGATION
582 else if (type == QEvent::ShortcutOverride) {
583 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
584 int key = keyEvent->key();
585 if ((key >= Qt::Key_Left && key <= Qt::Key_Down) || key == Qt::Key_Return)
586 return true;
587 }
588#endif
589
590 return false;
591}
592
593void QVirtualKeyboardInputContextPrivate::addSelectionAttribute(QList<QInputMethodEvent::Attribute> &attributes)
594{
595 if (!testAttribute(attributes, attributeType: QInputMethodEvent::Selection)) {
596 // Convert Cursor attribute to Selection attribute.
597 // In this case the cursor is set in pre-edit text, but
598 // the cursor is not being forced to specific location.
599 if (_forceCursorPosition == -1) {
600 int cursorAttributeIndex = findAttribute(attributes: preeditTextAttributes, attributeType: QInputMethodEvent::Cursor);
601 if (cursorAttributeIndex != -1 && preeditTextAttributes[cursorAttributeIndex].length > 0)
602 _forceCursorPosition = cursorPosition + preeditTextAttributes[cursorAttributeIndex].start;
603 forceAnchorPosition = -1;
604 }
605
606 if (_forceCursorPosition != -1) {
607 if (forceAnchorPosition != -1)
608 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::Selection, forceAnchorPosition, _forceCursorPosition - forceAnchorPosition, QVariant()));
609 else
610 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::Selection, _forceCursorPosition, 0, QVariant()));
611 }
612 }
613 forceAnchorPosition = -1;
614 _forceCursorPosition = -1;
615}
616
617bool QVirtualKeyboardInputContextPrivate::testAttribute(const QList<QInputMethodEvent::Attribute> &attributes, QInputMethodEvent::AttributeType attributeType) const
618{
619 for (const QInputMethodEvent::Attribute &attribute : std::as_const(t: attributes)) {
620 if (attribute.type == attributeType)
621 return true;
622 }
623 return false;
624}
625
626int QVirtualKeyboardInputContextPrivate::findAttribute(const QList<QInputMethodEvent::Attribute> &attributes, QInputMethodEvent::AttributeType attributeType) const
627{
628 const int count = attributes.size();
629 for (int i = 0; i < count; ++i) {
630 if (attributes.at(i).type == attributeType)
631 return i;
632 }
633 return -1;
634}
635
636void QVirtualKeyboardInputContextPrivate::updateSelectionControlVisible(bool inputPanelVisible)
637{
638 Q_Q(QVirtualKeyboardInputContext);
639 bool newSelectionControlVisible = inputPanelVisible && (cursorPosition != anchorPosition) && !inputMethodHints.testFlag(flag: Qt::ImhNoTextHandles);
640 if (selectionControlVisible != newSelectionControlVisible) {
641 selectionControlVisible = newSelectionControlVisible;
642 emit q->selectionControlVisibleChanged();
643 }
644}
645
646QVirtualKeyboardInputContext *QVirtualKeyboardInputContextForeign::create(
647 QQmlEngine *qmlEngine, QJSEngine *)
648{
649 static QMutex mutex;
650 static QHash<QQmlEngine *, QVirtualKeyboardInputContext *> instances;
651 QMutexLocker locker(&mutex);
652 QVirtualKeyboardInputContext *&instance = instances[qmlEngine];
653 if (instance == nullptr)
654 instance = new QVirtualKeyboardInputContext(qmlEngine);
655 return instance;
656}
657
658QT_END_NAMESPACE
659

source code of qtvirtualkeyboard/src/virtualkeyboard/qvirtualkeyboardinputcontext_p.cpp