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

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