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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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