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 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | using namespace QtVirtualKeyboard; |
26 | |
27 | const bool QtVirtualKeyboard::QT_VIRTUALKEYBOARD_FORCE_EVENTS_WITHOUT_FOCUS = qEnvironmentVariableIsSet(varName: "QT_VIRTUALKEYBOARD_FORCE_EVENTS_WITHOUT_FOCUS" ); |
28 | |
29 | QVirtualKeyboardInputContextPrivate::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 | |
60 | void 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 | |
79 | QVirtualKeyboardInputContextPrivate::~QVirtualKeyboardInputContextPrivate() |
80 | { |
81 | } |
82 | |
83 | bool QVirtualKeyboardInputContextPrivate::focus() const |
84 | { |
85 | return _focus; |
86 | } |
87 | |
88 | void QVirtualKeyboardInputContextPrivate::setFocus(bool focus) |
89 | { |
90 | if (_focus != focus) { |
91 | VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::setFocus():" << focus; |
92 | _focus = focus; |
93 | emit focusChanged(); |
94 | } |
95 | } |
96 | |
97 | QRectF QVirtualKeyboardInputContextPrivate::keyboardRectangle() const |
98 | { |
99 | return keyboardRect; |
100 | } |
101 | |
102 | void QVirtualKeyboardInputContextPrivate::setKeyboardRectangle(QRectF rectangle) |
103 | { |
104 | if (keyboardRect != rectangle) { |
105 | keyboardRect = rectangle; |
106 | emit keyboardRectangleChanged(); |
107 | platformInputContext->emitKeyboardRectChanged(); |
108 | } |
109 | } |
110 | |
111 | QRectF QVirtualKeyboardInputContextPrivate::previewRectangle() const |
112 | { |
113 | return previewRect; |
114 | } |
115 | |
116 | void QVirtualKeyboardInputContextPrivate::setPreviewRectangle(QRectF rectangle) |
117 | { |
118 | if (previewRect != rectangle) { |
119 | previewRect = rectangle; |
120 | emit previewRectangleChanged(); |
121 | } |
122 | } |
123 | |
124 | bool QVirtualKeyboardInputContextPrivate::previewVisible() const |
125 | { |
126 | return _previewVisible; |
127 | } |
128 | |
129 | void QVirtualKeyboardInputContextPrivate::setPreviewVisible(bool visible) |
130 | { |
131 | if (_previewVisible != visible) { |
132 | _previewVisible = visible; |
133 | emit previewVisibleChanged(); |
134 | } |
135 | } |
136 | |
137 | QString QVirtualKeyboardInputContextPrivate::locale() const |
138 | { |
139 | return platformInputContext ? platformInputContext->locale().name() : QString(); |
140 | } |
141 | |
142 | void 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 | |
155 | QObject *QVirtualKeyboardInputContextPrivate::inputItem() const |
156 | { |
157 | return platformInputContext ? platformInputContext->focusObject() : nullptr; |
158 | } |
159 | |
160 | ShiftHandler *QVirtualKeyboardInputContextPrivate::shiftHandler() const |
161 | { |
162 | return _shiftHandler; |
163 | } |
164 | |
165 | ShadowInputContext *QVirtualKeyboardInputContextPrivate::shadow() const |
166 | { |
167 | return const_cast<ShadowInputContext *>(&_shadow); |
168 | } |
169 | |
170 | void QVirtualKeyboardInputContextPrivate::setKeyboardObserver(QVirtualKeyboardObserver *keyboardObserver) |
171 | { |
172 | if (!this->keyboardObserver.isNull()) |
173 | return; |
174 | |
175 | this->keyboardObserver = keyboardObserver; |
176 | } |
177 | |
178 | bool 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 | |
189 | bool QVirtualKeyboardInputContextPrivate::hasEnterKeyAction(QObject *item) const |
190 | { |
191 | return item != nullptr && qmlAttachedPropertiesObject<EnterKeyAction>(obj: item, create: false); |
192 | } |
193 | |
194 | void QVirtualKeyboardInputContextPrivate::registerInputPanel(QObject *inputPanel) |
195 | { |
196 | VIRTUALKEYBOARD_DEBUG() << "QVirtualKeyboardInputContextPrivate::registerInputPanel():" << inputPanel; |
197 | Q_ASSERT(!this->inputPanel); |
198 | this->inputPanel = inputPanel; |
199 | } |
200 | |
201 | void QVirtualKeyboardInputContextPrivate::hideInputPanel() |
202 | { |
203 | platformInputContext->hideInputPanel(); |
204 | } |
205 | |
206 | void QVirtualKeyboardInputContextPrivate::updateAvailableLocales(const QStringList &availableLocales) |
207 | { |
208 | Settings *settings = Settings::instance(); |
209 | if (settings) |
210 | settings->setAvailableLocales(availableLocales); |
211 | } |
212 | |
213 | void 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 | */ |
251 | bool 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 | |
268 | KeyboardFunctionKey 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 | |
281 | void 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 ; |
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 | |
328 | void 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 | |
376 | void QVirtualKeyboardInputContextPrivate::sendInputMethodEvent(QInputMethodEvent *event) |
377 | { |
378 | QVirtualKeyboardScopedState inputMethodEventState(this, State::InputMethodEvent); |
379 | platformInputContext->sendEvent(event); |
380 | } |
381 | |
382 | void QVirtualKeyboardInputContextPrivate::reset() |
383 | { |
384 | inputEngine->reset(); |
385 | } |
386 | |
387 | void QVirtualKeyboardInputContextPrivate::commit() |
388 | { |
389 | inputEngine->update(); |
390 | } |
391 | |
392 | void 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 | |
511 | void 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 | |
537 | bool 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 | |
593 | void 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 | |
617 | bool 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 | |
626 | int 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 | |
636 | void 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 | |
646 | QVirtualKeyboardInputContext *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 | |
658 | QT_END_NAMESPACE |
659 | |