6#include <android/log.h>
13#include "private/qhighdpiscaling_p.h"
15#include <QTextBoundaryFinder>
16#include <QTextCharFormat>
17#include <QtCore/QJniEnvironment>
18#include <QtCore/QJniObject>
20#include <qguiapplication.h>
21#include <qinputmethod.h>
22#include <qsharedpointer.h>
25#include <qpa/qplatformwindow.h>
29using namespace Qt::StringLiterals;
40 m_context->beginBatchEdit();
45 m_context->endBatchEdit();
48 BatchEditLock(
const BatchEditLock &) =
delete;
49 BatchEditLock &operator=(
const BatchEditLock &) =
delete;
73 QtAndroidPrivate::AndroidDeadlockProtector protector(
74 u"QAndroidInputContext::runOnQtThread()"_s);
75 if (!protector.acquire())
77 QMetaObject::invokeMethod(m_androidInputContext,
"safeCall", Qt::BlockingQueuedConnection, Q_ARG(std::function<
void()>, func));
89 if (!focusObject->property(
"inputMethodHints").isValid())
97 if (!hasValidFocusObject())
100 qCDebug(lcQpaInputMethods) <<
"@@@ BEGINBATCH";
101 jboolean res = JNI_FALSE;
108 if (!hasValidFocusObject())
111 qCDebug(lcQpaInputMethods) <<
"@@@ ENDBATCH";
113 jboolean res = JNI_FALSE;
121 if (!hasValidFocusObject())
125 const jchar *jstr = env->GetStringChars(text, &isCopy);
126 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
127 env->ReleaseStringChars(text, jstr);
129 qCDebug(lcQpaInputMethods) <<
"@@@ COMMIT" << str << newCursorPosition;
130 jboolean res = JNI_FALSE;
137 if (!hasValidFocusObject())
140 qCDebug(lcQpaInputMethods) <<
"@@@ DELETE" << leftLength << rightLength;
141 jboolean res = JNI_FALSE;
148 if (!hasValidFocusObject())
151 qCDebug(lcQpaInputMethods) <<
"@@@ FINISH";
152 jboolean res = JNI_FALSE;
159 if (!hasValidFocusObject())
163 const jchar *jstr = env->GetStringChars(text, &isCopy);
164 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
165 env->ReleaseStringChars(text, jstr);
167 qCDebug(lcQpaInputMethods) <<
"@@@ REPLACE" << start << end << str << newCursorPosition;
168 jboolean res = JNI_FALSE;
189 QAndroidInputContext::ExtractedText extractedText;
190 runOnQtThread([&]{extractedText =
m_androidInputContext->getExtractedText(hintMaxChars, hintMaxLines, flags);});
192 qCDebug(lcQpaInputMethods) <<
"@@@ GETEX" << hintMaxChars << hintMaxLines << QString::fromLatin1(
"0x") + QString::number(flags,16) << extractedText.text <<
"partOff:" << extractedText.partialStartOffset << extractedText.partialEndOffset <<
"sel:" << extractedText.selectionStart << extractedText.selectionEnd <<
"offset:" << extractedText.startOffset;
194 jobject object = env->NewObject(m_extractedTextClass, m_classConstructorMethodID);
195 env->SetIntField(object, m_partialStartOffsetFieldID, extractedText.partialStartOffset);
196 env->SetIntField(object, m_partialEndOffsetFieldID, extractedText.partialEndOffset);
197 env->SetIntField(object, m_selectionStartFieldID, extractedText.selectionStart);
198 env->SetIntField(object, m_selectionEndFieldID, extractedText.selectionEnd);
199 env->SetIntField(object, m_startOffsetFieldID, extractedText.startOffset);
200 env->SetObjectField(object,
202 env->NewString(
reinterpret_cast<
const jchar *>(extractedText.text.constData()),
203 jsize(extractedText.text.length())));
215 qCDebug(lcQpaInputMethods) <<
"@@@ GETSEL" << text;
218 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
228 qCDebug(lcQpaInputMethods) <<
"@@@ GETA" << length << text;
229 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
239 qCDebug(lcQpaInputMethods) <<
"@@@ GETB" << length << text;
240 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
245 if (!hasValidFocusObject())
249 const jchar *jstr = env->GetStringChars(text, &isCopy);
250 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
251 env->ReleaseStringChars(text, jstr);
253 qCDebug(lcQpaInputMethods) <<
"@@@ SET" << str << newCursorPosition;
254 jboolean res = JNI_FALSE;
261 if (!hasValidFocusObject())
264 qCDebug(lcQpaInputMethods) <<
"@@@ SETR" << start << end;
265 jboolean res = JNI_FALSE;
273 if (!hasValidFocusObject())
276 qCDebug(lcQpaInputMethods) <<
"@@@ SETSEL" << start << end;
277 jboolean res = JNI_FALSE;
285 if (!hasValidFocusObject())
288 qCDebug(lcQpaInputMethods) <<
"@@@ SELALL";
289 jboolean res = JNI_FALSE;
296 if (!hasValidFocusObject())
299 qCDebug(lcQpaInputMethods) <<
"@@@";
300 jboolean res = JNI_FALSE;
307 if (!hasValidFocusObject())
310 qCDebug(lcQpaInputMethods) <<
"@@@";
311 jboolean res = JNI_FALSE;
318 if (!hasValidFocusObject())
321 qCDebug(lcQpaInputMethods) <<
"@@@";
322 jboolean res = JNI_FALSE;
329 if (!hasValidFocusObject())
332 qCDebug(lcQpaInputMethods) <<
"@@@ PASTE";
333 jboolean res = JNI_FALSE;
340 if (!hasValidFocusObject())
343 qCDebug(lcQpaInputMethods) <<
"@@@ UPDATECURSORPOS";
366 {
"beginBatchEdit",
"()Z", (
void *)beginBatchEdit},
367 {
"endBatchEdit",
"()Z", (
void *)endBatchEdit},
368 {
"commitText",
"(Ljava/lang/String;I)Z", (
void *)commitText},
369 {
"deleteSurroundingText",
"(II)Z", (
void *)deleteSurroundingText},
370 {
"finishComposingText",
"()Z", (
void *)finishComposingText},
371 {
"getCursorCapsMode",
"(I)I", (
void *)getCursorCapsMode},
372 {
"getExtractedText",
"(III)Lorg/qtproject/qt/android/QtExtractedText;", (
void *)getExtractedText},
373 {
"getSelectedText",
"(I)Ljava/lang/String;", (
void *)getSelectedText},
374 {
"getTextAfterCursor",
"(II)Ljava/lang/String;", (
void *)getTextAfterCursor},
375 {
"getTextBeforeCursor",
"(II)Ljava/lang/String;", (
void *)getTextBeforeCursor},
376 {
"replaceText",
"(IILjava/lang/String;I)Z", (
void *)replaceText},
377 {
"setComposingText",
"(Ljava/lang/String;I)Z", (
void *)setComposingText},
378 {
"setComposingRegion",
"(II)Z", (
void *)setComposingRegion},
379 {
"setSelection",
"(II)Z", (
void *)setSelection},
380 {
"selectAll",
"()Z", (
void *)selectAll},
381 {
"cut",
"()Z", (
void *)cut},
382 {
"copy",
"()Z", (
void *)copy},
383 {
"copyURL",
"()Z", (
void *)copyURL},
384 {
"paste",
"()Z", (
void *)paste},
385 {
"updateCursorPosition",
"()Z", (
void *)updateCursorPosition},
386 {
"reportFullscreenMode",
"(Z)V", (
void *)reportFullscreenMode},
387 {
"fullscreenMode",
"()Z", (
void *)fullscreenMode}
392 QRect windowRect = QPlatformInputContext::inputItemRectangle().toRect();
393 QPlatformWindow *window =
qGuiApp->focusWindow()->handle();
394 return QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
399 , m_composingTextStart(-1)
400 , m_composingCursor(-1)
402 , m_batchEditNestingLevel(0)
404 , m_fullScreenMode(
false)
408 if (Q_UNLIKELY(!clazz)) {
409 qCritical() <<
"Native registration unable to find class '"
415 if (Q_UNLIKELY(env->RegisterNatives(clazz, methods,
sizeof(
methods) /
sizeof(
methods[0])) < 0)) {
416 qCritical() <<
"RegisterNatives failed for '"
423 if (Q_UNLIKELY(!clazz)) {
424 qCritical() <<
"Native registration unable to find class '"
432 if (Q_UNLIKELY(!m_classConstructorMethodID)) {
433 qCritical(
"GetMethodID failed");
438 if (Q_UNLIKELY(!m_partialEndOffsetFieldID)) {
439 qCritical(
"Can't find field partialEndOffset");
444 if (Q_UNLIKELY(!m_partialStartOffsetFieldID)) {
445 qCritical(
"Can't find field partialStartOffset");
450 if (Q_UNLIKELY(!m_selectionEndFieldID)) {
451 qCritical(
"Can't find field selectionEnd");
456 if (Q_UNLIKELY(!m_selectionStartFieldID)) {
457 qCritical(
"Can't find field selectionStart");
462 if (Q_UNLIKELY(!m_startOffsetFieldID)) {
463 qCritical(
"Can't find field startOffset");
467 m_textFieldID = env->GetFieldID(m_extractedTextClass,
"text",
"Ljava/lang/String;");
468 if (Q_UNLIKELY(!m_textFieldID)) {
469 qCritical(
"Can't find field text");
472 qRegisterMetaType<QInputMethodEvent *>(
"QInputMethodEvent*");
473 qRegisterMetaType<QInputMethodQueryEvent *>(
"QInputMethodQueryEvent*");
476 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
477 this, &QAndroidInputContext::updateSelectionHandles);
478 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::anchorRectangleChanged,
479 this, &QAndroidInputContext::updateSelectionHandles);
480 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::inputItemClipRectangleChanged,
this, [
this]{
481 auto im =
qGuiApp->inputMethod();
482 if (!im->inputItemClipRectangle().contains(im->anchorRectangle()) ||
483 !im->inputItemClipRectangle().contains(im->cursorRectangle())) {
484 m_handleMode = Hidden;
485 updateSelectionHandles();
488 m_hideCursorHandleTimer.setInterval(4000);
489 m_hideCursorHandleTimer.setSingleShot(
true);
490 m_hideCursorHandleTimer.setTimerType(Qt::VeryCoarseTimer);
491 connect(&m_hideCursorHandleTimer, &QTimer::timeout,
this, [
this]{
492 m_handleMode = Hidden;
493 updateSelectionHandles();
517 QVariant absolutePos = query->value(Qt::ImAbsolutePosition);
518 return absolutePos.isValid() ? absolutePos.toInt() : query->value(Qt::ImCursorPosition).toInt();
524 QVariant absolutePos = query->value(Qt::ImAbsolutePosition);
525 return absolutePos.isValid() ? absolutePos.toInt() - query->value(Qt::ImCursorPosition).toInt() : 0;
530 focusObjectStopComposing();
532 m_batchEditNestingLevel = 0;
533 m_handleMode = Hidden;
535 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(Qt::ImEnabled);
536 if (!query.isNull() && query->value(Qt::ImEnabled).toBool()) {
546 focusObjectStopComposing();
551 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
552 if (!query.isNull() && m_batchEditNestingLevel == 0) {
553 const int cursorPos = getAbsoluteCursorPosition(query);
554 const int composeLength = m_composingText.length();
557 if (m_composingText.isEmpty() != (m_composingTextStart == -1))
558 qWarning() <<
"Input method out of sync" << m_composingText << m_composingTextStart;
560 int realSelectionStart = cursorPos;
561 int realSelectionEnd = cursorPos;
563 int cpos = query->value(Qt::ImCursorPosition).toInt();
564 int anchor = query->value(Qt::ImAnchorPosition).toInt();
565 if (cpos != anchor) {
566 if (!m_composingText.isEmpty()) {
567 qWarning(
"Selecting text while preediting may give unpredictable results.");
568 focusObjectStopComposing();
570 int blockPos = getBlockPosition(query);
571 realSelectionStart = blockPos + cpos;
572 realSelectionEnd = blockPos + anchor;
575 if (focusObjectIsComposing())
576 realSelectionStart = realSelectionEnd = m_composingCursor;
579 if (realSelectionStart > realSelectionEnd)
580 std::swap(realSelectionStart, realSelectionEnd);
583 m_composingTextStart
, m_composingTextStart + composeLength
);
589 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
592 return query->value(Qt::ImHints).toUInt() & Qt::ImhNoTextHandles;
597 if (m_fullScreenMode) {
601 static bool noHandles = qEnvironmentVariableIntValue(
"QT_QPA_NO_TEXT_HANDLES");
602 if (noHandles || !m_focusObject)
605 if (isImhNoTextHandlesSet()) {
610 auto im =
qGuiApp->inputMethod();
612 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled
613 | Qt::ImCurrentSelection | Qt::ImHints | Qt::ImSurroundingText
615 QCoreApplication::sendEvent(m_focusObject, &query);
617 int cpos = query.value(Qt::ImCursorPosition).toInt();
618 int anchor = query.value(Qt::ImAnchorPosition).toInt();
619 const QVariant readOnlyVariant = query.value(Qt::ImReadOnly);
620 bool readOnly = readOnlyVariant.toBool();
621 QPlatformWindow *qPlatformWindow =
qGuiApp->focusWindow()->handle();
623 if (!readOnly && ((m_handleMode & 0xff) == Hidden)) {
628 if ( cpos == anchor && (!readOnlyVariant.isValid() || readOnly)) {
633 if (cpos == anchor || im->anchorRectangle().isNull()) {
634 auto curRect = cursorRectangle();
635 QPoint cursorPointGlobal = qPlatformWindow->mapToGlobal(
636 QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()));
637 QPoint cursorPoint(curRect.center().x(), curRect.bottom());
642 if (cursorPointGlobal != cursorPoint) {
643 x = cursorPointGlobal.x();
644 y = cursorPointGlobal.y();
647 QPoint editMenuPoint(x, y);
648 m_handleMode &= ShowEditPopup;
649 m_handleMode |= ShowCursor;
651 if (!query.value(Qt::ImSurroundingText).toString().isEmpty())
653 QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPointGlobal);
654 m_hideCursorHandleTimer.start();
659 m_handleMode = ShowSelection | ShowEditPopup ;
660 auto leftRect = cursorRectangle();
661 auto rightRect = anchorRectangle();
663 std::swap(leftRect, rightRect);
667 QPoint leftPoint(qPlatformWindow->mapToGlobal(leftRect.bottomLeft().toPoint()));
668 QPoint rightPoint(qPlatformWindow->mapToGlobal(rightRect.bottomRight().toPoint()));
671 if (platformIntegration) {
675 int rightSideOfScreen = platformIntegration
->screen()->availableGeometry().right();
682 QPoint editPoint(qPlatformWindow->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint()));
686 QtAndroidInput::updateHandles(m_handleMode, editPoint, buttons, leftPoint, rightPoint,
687 query.value(Qt::ImCurrentSelection).toString().isRightToLeft());
688 m_hideCursorHandleTimer.stop();
693
694
695
696
699 if (m_batchEditNestingLevel != 0) {
700 qWarning() <<
"QAndroidInputContext::handleLocationChanged returned";
706 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition
707 | Qt::ImAbsolutePosition | Qt::ImCurrentSelection);
708 QCoreApplication::sendEvent(m_focusObject, &query);
709 int cpos = query.value(Qt::ImCursorPosition).toInt();
710 int anchor = query.value(Qt::ImAnchorPosition).toInt();
711 auto leftRect = cursorRectangle();
712 auto rightRect = anchorRectangle();
714 std::swap(leftRect, rightRect);
717 if (handleId == 2 && point.y() > rightRect.center().y()) {
718 point.setY(rightRect.center().y());
719 }
else if (handleId == 3 && point.y() < leftRect.center().y()) {
720 point.setY(leftRect.center().y());
724 auto object = m_focusObject->parent();
727 if (QString::compare(object->metaObject()->className(),
728 "QDialog", Qt::CaseInsensitive) == 0) {
729 dialogMoveX += object->property(
"x").toInt();
731 object = object->parent();
735 QPointF(QHighDpi::fromNativePixels(point, QGuiApplication::focusWindow()));
736 const QPointF fixedPosition = QPointF(position.x() - dialogMoveX, position.y());
737 const QInputMethod *im = QGuiApplication::inputMethod();
738 const QTransform mapToLocal = im->inputItemTransform().inverted();
739 const int handlePos = im->queryFocusObject(Qt::ImCursorPosition, mapToLocal.map(fixedPosition)).toInt(&ok);
745 int newAnchor = anchor;
746 if (newAnchor > newCpos)
747 std::swap(newAnchor, newCpos);
751 newAnchor = handlePos;
752 }
else if (handleId == 2) {
753 newAnchor = handlePos;
754 }
else if (handleId == 3) {
759
760
761
762
763 if ((handleId == 2 || handleId == 3) && newCpos <= newAnchor) {
764 QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme,
765 query.value(Qt::ImCurrentSelection).toString());
767 const int oldSelectionStartPos = qMin(cpos, anchor);
771 finder.toPreviousBoundary();
772 newAnchor = finder.position() + oldSelectionStartPos;
775 finder.toNextBoundary();
776 newCpos = finder.position() + oldSelectionStartPos;
781 if (!focusObjectIsComposing() && newCpos == cpos && newAnchor == anchor)
785
786
787
788
789 if (focusObjectIsComposing() && handleId == 1) {
790 int absoluteCpos = query.value(Qt::ImAbsolutePosition).toInt(&ok);
793 const int blockPos = absoluteCpos - cpos;
795 if (blockPos + newCpos == m_composingCursor)
799 BatchEditLock batchEditLock(
this);
801 focusObjectStopComposing();
803 QList<QInputMethodEvent::Attribute> attributes;
804 attributes.append({ QInputMethodEvent::Selection, newAnchor, newCpos - newAnchor });
805 if (newCpos != newAnchor)
806 attributes.append({ QInputMethodEvent::Cursor, 0, 0 });
809 QGuiApplication::sendEvent(m_focusObject, &event);
814 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
816 m_handleMode = ShowCursor;
818 m_hideCursorHandleTimer.stop();
820 if (focusObjectIsComposing()) {
821 const int curBlockPos = getBlockPosition(
822 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
823 const int touchPosition = curBlockPos
824 + queryFocusObject(Qt::ImCursorPosition, QPointF(x, y)).toInt();
825 if (touchPosition != m_composingCursor)
826 focusObjectStopComposing();
830 QPlatformWindow *window =
qGuiApp->focusWindow()->handle();
831 const QRectF curRect = cursorRectangle();
832 const QPoint cursorGlobalPoint = window->mapToGlobal(QPoint(curRect.x(), curRect.y()));
833 const QRect windowRect = QPlatformInputContext::inputItemClipRectangle().toRect();
834 const QRect windowGlobalRect = QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
836 if (windowGlobalRect.contains(cursorGlobalPoint.x(), cursorGlobalPoint.y()))
843 static bool noHandles = qEnvironmentVariableIntValue(
"QT_QPA_NO_TEXT_HANDLES");
847 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
848 BatchEditLock batchEditLock(
this);
850 focusObjectStopComposing();
851 const QPointF touchPoint(x, y);
852 setSelectionOnFocusObject(touchPoint, touchPoint);
854 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor);
855 QCoreApplication::sendEvent(m_focusObject, &query);
856 int cursor = query.value(Qt::ImCursorPosition).toInt();
858 QString before = query.value(Qt::ImTextBeforeCursor).toString();
859 QString after = query.value(Qt::ImTextAfterCursor).toString();
860 for (
const auto &ch : after) {
861 if (!ch.isLetterOrNumber())
866 for (
auto itch = before.rbegin(); itch != after.rend(); ++itch) {
867 if (!itch->isLetterOrNumber())
871 if (cursor == anchor || cursor < 0 || cursor - anchor > 500) {
872 m_handleMode = ShowCursor | ShowEditPopup;
876 QList<QInputMethodEvent::Attribute> imAttributes;
877 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
878 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, anchor, cursor - anchor, QVariant()));
880 QGuiApplication::sendEvent(m_focusObject, &event);
882 m_handleMode = ShowSelection | ShowEditPopup;
891 m_handleMode = Hidden;
898 if (m_handleMode & ShowSelection) {
899 m_handleMode = Hidden;
902 m_hideCursorHandleTimer.start();
908 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(queries);
911#warning TODO extract the needed data from query
916#warning TODO Handle at least QInputMethod::ContextMenu action
918 Q_UNUSED(cursorPosition);
926 return QtAndroidInput::softwareKeyboardRect();
936 if (QGuiApplication::applicationState() != Qt::ApplicationActive) {
937 connect(
qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
this, SLOT(showInputPanelLater(Qt::ApplicationState)));
940 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
944 if (!
qGuiApp->focusWindow()->handle())
947 disconnect(m_updateCursorPosConnection);
948 m_updateCursorPosConnection = {};
950 if (
qGuiApp->focusObject()->metaObject()->indexOfSignal(
"cursorPositionChanged(int,int)") >= 0)
951 m_updateCursorPosConnection = connect(
qGuiApp->focusObject(), SIGNAL(cursorPositionChanged(
int,
int)),
this, SLOT(updateCursorPosition()));
952 else if (
qGuiApp->focusObject()->metaObject()->indexOfSignal(
"cursorPositionChanged()") >= 0)
953 m_updateCursorPosConnection = connect(
qGuiApp->focusObject(), SIGNAL(cursorPositionChanged()),
this, SLOT(updateCursorPosition()));
955 QRect rect = screenInputItemRectangle();
956 QtAndroidInput::showSoftwareKeyboard(rect.left(), rect.top(), rect.width(), rect.height(),
957 query->value(Qt::ImHints).toUInt(),
958 query->value(Qt::ImEnterKeyType).toUInt());
963 if (state != Qt::ApplicationActive)
965 disconnect(
qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
this, SLOT(showInputPanelLater(Qt::ApplicationState)));
971 if (
qGuiApp->thread() == QThread::currentThread())
974 QMetaObject::invokeMethod(
this,
"safeCall", conType, Q_ARG(std::function<
void()>, func));
989 return m_composingText.length();
994 m_composingText.clear();
995 m_composingTextStart = -1;
996 m_composingCursor = -1;
997 m_extractedText.clear();
1003 return m_focusObject;
1008 if (object != m_focusObject) {
1009 focusObjectStopComposing();
1010 m_focusObject = object;
1018 ++m_batchEditNestingLevel;
1024 if (--m_batchEditNestingLevel == 0) {
1025 focusObjectStartComposing();
1032
1033
1034
1037 BatchEditLock batchEditLock(
this);
1038 return setComposingText(text, newCursorPosition) && finishComposingText();
1043 BatchEditLock batchEditLock(
this);
1045 focusObjectStopComposing();
1047 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1051 if (leftLength < 0) {
1052 rightLength += -leftLength;
1056 const int initialBlockPos = getBlockPosition(query);
1057 const int initialCursorPos = getAbsoluteCursorPosition(query);
1058 const int initialAnchorPos = initialBlockPos + query->value(Qt::ImAnchorPosition).toInt();
1061
1062
1063
1064
1065
1066
1067
1068
1070 m_composingText.isEmpty()
1071 ? qMin(initialCursorPos, initialAnchorPos)
1072 : qMin(qMin(initialCursorPos, initialAnchorPos), m_composingTextStart);
1074 const int rightBegin =
1075 m_composingText.isEmpty()
1076 ? qMax(initialCursorPos, initialAnchorPos)
1077 : qMax(qMax(initialCursorPos, initialAnchorPos),
1078 m_composingTextStart + m_composingText.length());
1080 int textBeforeCursorLen;
1081 int textAfterCursorLen;
1083 QVariant textBeforeCursor = query->value(Qt::ImTextBeforeCursor);
1084 QVariant textAfterCursor = query->value(Qt::ImTextAfterCursor);
1085 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1086 textBeforeCursorLen = textBeforeCursor.toString().length();
1087 textAfterCursorLen = textAfterCursor.toString().length();
1089 textBeforeCursorLen = initialCursorPos - initialBlockPos;
1090 textAfterCursorLen =
1091 query->value(Qt::ImSurroundingText).toString().length() - textBeforeCursorLen;
1094 leftLength = qMin(qMax(0, textBeforeCursorLen - (initialCursorPos - leftEnd)), leftLength);
1095 rightLength = qMin(qMax(0, textAfterCursorLen - (rightBegin - initialCursorPos)), rightLength);
1097 if (leftLength == 0 && rightLength == 0)
1100 if (leftEnd == rightBegin) {
1103 event.setCommitString({}, -leftLength, leftLength + rightLength);
1104 QGuiApplication::sendEvent(m_focusObject, &event);
1106 if (initialCursorPos != initialAnchorPos) {
1108 { QInputMethodEvent::Selection, initialCursorPos - initialBlockPos, 0 }
1111 QGuiApplication::sendEvent(m_focusObject, &event);
1114 int currentCursorPos = initialCursorPos;
1116 if (rightLength > 0) {
1118 event.setCommitString({}, rightBegin - currentCursorPos, rightLength);
1119 QGuiApplication::sendEvent(m_focusObject, &event);
1121 currentCursorPos = rightBegin;
1124 if (leftLength > 0) {
1125 const int leftBegin = leftEnd - leftLength;
1128 event.setCommitString({}, leftBegin - currentCursorPos, leftLength);
1129 QGuiApplication::sendEvent(m_focusObject, &event);
1131 currentCursorPos = leftBegin;
1133 if (!m_composingText.isEmpty())
1134 m_composingTextStart -= leftLength;
1138 if (currentCursorPos != initialCursorPos - leftLength
1139 || initialCursorPos != initialAnchorPos) {
1141 const int currentBlockPos = getBlockPosition(
1142 focusObjectInputMethodQuery(Qt::ImAbsolutePosition | Qt::ImCursorPosition));
1145 { QInputMethodEvent::Selection, initialCursorPos - leftLength - currentBlockPos,
1146 initialAnchorPos - initialCursorPos },
1147 { QInputMethodEvent::Cursor, 0, 0 }
1150 QGuiApplication::sendEvent(m_focusObject, &event);
1160 BatchEditLock batchEditLock(
this);
1162 if (!focusObjectStopComposing())
1170
1171
1172
1173
1176 if (!finishComposingText())
1178 if (!setSelection(start, end))
1181 return commitText(text, newCursorPosition);
1186 m_fullScreenMode = enabled;
1187 BatchEditLock batchEditLock(
this);
1188 if (!focusObjectStopComposing())
1192 m_handleMode = Hidden;
1200 return m_fullScreenMode;
1205 return m_composingCursor != -1;
1210 if (focusObjectIsComposing() || m_composingText.isEmpty())
1214 if (m_composingText.contains(u'\n'))
1217 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1221 if (query->value(Qt::ImCursorPosition).toInt() != query->value(Qt::ImAnchorPosition).toInt())
1224 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1225 if (absoluteCursorPos < m_composingTextStart
1226 || absoluteCursorPos > m_composingTextStart + m_composingText.length())
1229 m_composingCursor = absoluteCursorPos;
1231 QTextCharFormat underlined;
1232 underlined.setFontUnderline(
true);
1235 { QInputMethodEvent::Cursor, absoluteCursorPos - m_composingTextStart, 1 },
1236 { QInputMethodEvent::TextFormat, 0,
int(m_composingText.length()), underlined }
1239 event.setCommitString({}, m_composingTextStart - absoluteCursorPos, m_composingText.length());
1241 QGuiApplication::sendEvent(m_focusObject, &event);
1246 if (!focusObjectIsComposing())
1249 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1253 const int blockPos = getBlockPosition(query);
1254 const int localCursorPos = m_composingCursor - blockPos;
1256 m_composingCursor = -1;
1260 QList<QInputMethodEvent::Attribute> attributes;
1262 event.setCommitString(m_composingText);
1263 sendInputMethodEvent(&event);
1267 QList<QInputMethodEvent::Attribute> attributes;
1269 QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0));
1271 sendInputMethodEvent(&event);
1280 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1284 const uint qtInputMethodHints = query->value(Qt::ImHints).toUInt();
1285 const int localPos = query->value(Qt::ImCursorPosition).toInt();
1287 bool atWordBoundary =
1289 && (!focusObjectIsComposing() || m_composingCursor == m_composingTextStart);
1291 if (!atWordBoundary) {
1292 QString surroundingText = query->value(Qt::ImSurroundingText).toString();
1293 surroundingText.truncate(localPos);
1294 if (focusObjectIsComposing())
1295 surroundingText += QStringView{m_composingText}.left(m_composingCursor - m_composingTextStart);
1297 QTextBoundaryFinder finder(QTextBoundaryFinder::Sentence, surroundingText + u'A');
1298 finder.setPosition(surroundingText.length());
1299 if (finder.isAtBoundary())
1300 atWordBoundary = finder.isAtBoundary();
1302 if (atWordBoundary && !(qtInputMethodHints & Qt::ImhLowercaseOnly) && !(qtInputMethodHints & Qt::ImhNoAutoUppercase))
1303 res |= CAP_MODE_SENTENCES;
1305 if (qtInputMethodHints & Qt::ImhUppercaseOnly)
1306 res |= CAP_MODE_CHARACTERS;
1319 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(
1320 Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition);
1322 return m_extractedText;
1324 const int cursorPos = getAbsoluteCursorPosition(query);
1325 const int blockPos = getBlockPosition(query);
1332 QVariant textBeforeCursor = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, INT_MAX);
1333 QVariant textAfterCursor = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, INT_MAX);
1334 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1335 if (focusObjectIsComposing()) {
1336 m_extractedText.text =
1337 textBeforeCursor.toString() + m_composingText + textAfterCursor.toString();
1339 m_extractedText.text = textBeforeCursor.toString() + textAfterCursor.toString();
1342 m_extractedText.startOffset = qMax(0, cursorPos - textBeforeCursor.toString().length());
1344 m_extractedText.text = focusObjectInputMethodQuery(Qt::ImSurroundingText)
1345 ->value(Qt::ImSurroundingText).toString();
1347 if (focusObjectIsComposing())
1348 m_extractedText.text.insert(cursorPos - blockPos, m_composingText);
1350 m_extractedText.startOffset = blockPos;
1353 if (focusObjectIsComposing()) {
1354 m_extractedText.selectionStart = m_composingCursor - m_extractedText.startOffset;
1355 m_extractedText.selectionEnd = m_extractedText.selectionStart;
1357 m_extractedText.selectionStart = cursorPos - m_extractedText.startOffset;
1358 m_extractedText.selectionEnd =
1359 blockPos + query->value(Qt::ImAnchorPosition).toInt() - m_extractedText.startOffset;
1362 if (m_extractedText.selectionStart > m_extractedText.selectionEnd)
1363 std::swap(m_extractedText.selectionStart, m_extractedText.selectionEnd);
1366 return m_extractedText;
1371 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1375 return query->value(Qt::ImCurrentSelection).toString();
1385 QVariant reportedTextAfter = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, length);
1386 if (reportedTextAfter.isValid()) {
1387 text = reportedTextAfter.toString();
1390 QSharedPointer<QInputMethodQueryEvent> query =
1391 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1393 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1394 text = query->value(Qt::ImSurroundingText).toString().mid(cursorPos);
1398 if (focusObjectIsComposing()) {
1400 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1401 text = QStringView{m_composingText}.mid(cursorPosInsidePreedit) + text;
1404 QSharedPointer<QInputMethodQueryEvent> query =
1405 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1407 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1408 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1409 if (anchorPos > cursorPos)
1410 text.remove(0, anchorPos - cursorPos);
1414 text.truncate(length);
1425 QVariant reportedTextBefore = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, length);
1426 if (reportedTextBefore.isValid()) {
1427 text = reportedTextBefore.toString();
1430 QSharedPointer<QInputMethodQueryEvent> query =
1431 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1433 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1434 text = query->value(Qt::ImSurroundingText).toString().left(cursorPos);
1438 if (focusObjectIsComposing()) {
1440 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1441 text += QStringView{m_composingText}.left(cursorPosInsidePreedit);
1444 QSharedPointer<QInputMethodQueryEvent> query =
1445 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1447 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1448 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1449 if (anchorPos < cursorPos)
1450 text.chop(cursorPos - anchorPos);
1454 if (text.length() > length)
1455 text = text.right(length);
1460
1461
1462
1463
1464
1465
1466
1467
1471 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1475 BatchEditLock batchEditLock(
this);
1477 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1478 int absoluteAnchorPos = getBlockPosition(query) + query->value(Qt::ImAnchorPosition).toInt();
1480 auto setCursorPosition = [=]() {
1481 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1482 QInputMethodEvent event({}, { { QInputMethodEvent::Selection, cursorPos, 0 } });
1483 QGuiApplication::sendEvent(m_focusObject, &event);
1488 if (!m_composingText.isEmpty() && absoluteCursorPos != absoluteAnchorPos) {
1489 setCursorPosition();
1490 absoluteAnchorPos = absoluteCursorPos;
1495 if (absoluteCursorPos == 0 && text.length() == 1 && getTextAfterCursor(1,1).length() >= 0) {
1496 setCursorPosition();
1502 const int effectiveAbsoluteCursorPos = qMin(absoluteCursorPos, absoluteAnchorPos);
1503 if (m_composingTextStart == -1)
1504 m_composingTextStart = effectiveAbsoluteCursorPos;
1506 const int oldComposingTextLen = m_composingText.length();
1507 m_composingText = text;
1509 const int newAbsoluteCursorPos =
1510 newCursorPosition <= 0
1511 ? m_composingTextStart + newCursorPosition
1512 : m_composingTextStart + m_composingText.length() + newCursorPosition - 1;
1514 const bool focusObjectWasComposing = focusObjectIsComposing();
1517 if (!m_composingText.isEmpty() && !m_composingText.contains(u'\n')
1518 && newAbsoluteCursorPos >= m_composingTextStart
1519 && newAbsoluteCursorPos <= m_composingTextStart + m_composingText.length())
1520 m_composingCursor = newAbsoluteCursorPos;
1522 m_composingCursor = -1;
1524 if (focusObjectIsComposing()) {
1525 QTextCharFormat underlined;
1526 underlined.setFontUnderline(
true);
1529 { QInputMethodEvent::TextFormat, 0,
int(m_composingText.length()), underlined },
1530 { QInputMethodEvent::Cursor, m_composingCursor - m_composingTextStart, 1 }
1533 if (oldComposingTextLen > 0 && !focusObjectWasComposing) {
1534 event.setCommitString({}, m_composingTextStart - effectiveAbsoluteCursorPos,
1535 oldComposingTextLen);
1537 if (m_composingText.isEmpty())
1540 QGuiApplication::sendEvent(m_focusObject, &event);
1544 if (focusObjectWasComposing) {
1545 event.setCommitString(m_composingText);
1547 event.setCommitString(m_composingText,
1548 m_composingTextStart - effectiveAbsoluteCursorPos,
1549 oldComposingTextLen);
1551 if (m_composingText.isEmpty())
1554 QGuiApplication::sendEvent(m_focusObject, &event);
1557 if (!focusObjectIsComposing() && newCursorPosition != 1) {
1561 const int newBlockPos = getBlockPosition(
1562 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
1565 { QInputMethodEvent::Selection, newAbsoluteCursorPos - newBlockPos, 0 }
1568 QGuiApplication::sendEvent(m_focusObject, &event);
1582 BatchEditLock batchEditLock(
this);
1589 finishComposingText();
1591 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1600 QString text = query->value(Qt::ImSurroundingText).toString();
1601 int textOffset = getBlockPosition(query);
1603 if (start < textOffset || end > textOffset + text.length()) {
1604 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1606 if (end - textOffset > text.length()) {
1607 const QString after = query->value(Qt::ImTextAfterCursor).toString();
1608 const int additionalSuffixLen = after.length() - (text.length() - cursorPos);
1610 if (additionalSuffixLen > 0)
1611 text += QStringView{after}.right(additionalSuffixLen);
1614 if (start < textOffset) {
1615 QString before = query->value(Qt::ImTextBeforeCursor).toString();
1616 before.chop(cursorPos);
1618 if (!before.isEmpty()) {
1619 text = before + text;
1620 textOffset -= before.length();
1624 if (start < textOffset || end - textOffset > text.length()) {
1625 qCDebug(lcQpaInputMethods) <<
"Warning: setComposingRegion: failed to retrieve text from composing region";
1631 m_composingText = text.mid(start - textOffset, end - start);
1632 m_composingTextStart = start;
1639 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1643 BatchEditLock batchEditLock(
this);
1645 int blockPosition = getBlockPosition(query);
1646 int localCursorPos = start - blockPosition;
1648 if (focusObjectIsComposing() && start == end && start >= m_composingTextStart
1649 && start <= m_composingTextStart + m_composingText.length()) {
1652 int localOldPos = query->value(Qt::ImCursorPosition).toInt();
1653 int pos = localCursorPos - localOldPos;
1654 QList<QInputMethodEvent::Attribute> attributes;
1655 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, pos, 1));
1660 QTextCharFormat underlined;
1661 underlined.setFontUnderline(
true);
1662 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, m_composingText.length(),
1663 QVariant(underlined)));
1664 m_composingCursor = start;
1667 QGuiApplication::sendEvent(m_focusObject, &event);
1670 focusObjectStopComposing();
1671 QList<QInputMethodEvent::Attribute> attributes;
1672 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection,
1676 QGuiApplication::sendEvent(m_focusObject, &event);
1683 BatchEditLock batchEditLock(
this);
1685 focusObjectStopComposing();
1686 m_handleMode = ShowCursor;
1687 sendShortcut(QKeySequence::SelectAll);
1693 BatchEditLock batchEditLock(
this);
1697 finishComposingText();
1699 m_handleMode = ShowCursor;
1700 sendShortcut(QKeySequence::Cut);
1706 BatchEditLock batchEditLock(
this);
1708 focusObjectStopComposing();
1709 m_handleMode = ShowCursor;
1710 sendShortcut(QKeySequence::Copy);
1722 BatchEditLock batchEditLock(
this);
1725 finishComposingText();
1727 m_handleMode = ShowCursor;
1728 sendShortcut(QKeySequence::Paste);
1734 for (
int i = 0; i < sequence.count(); ++i) {
1735 const QKeyCombination keys = sequence[i];
1736 Qt::Key key = Qt::Key(keys.toCombined() & ~Qt::KeyboardModifierMask);
1737 Qt::KeyboardModifiers mod = Qt::KeyboardModifiers(keys.toCombined() & Qt::KeyboardModifierMask);
1739 QKeyEvent pressEvent(QEvent::KeyPress, key, mod);
1740 QKeyEvent releaseEvent(QEvent::KeyRelease, key, mod);
1742 QGuiApplication::sendEvent(m_focusObject, &pressEvent);
1743 QGuiApplication::sendEvent(m_focusObject, &releaseEvent);
1747QSharedPointer<QInputMethodQueryEvent>
QAndroidInputContext::focusObjectInputMethodQuery(Qt::InputMethodQueries queries) {
1751 QObject *focusObject =
qGuiApp->focusObject();
1755 QInputMethodQueryEvent *ret =
new QInputMethodQueryEvent(queries);
1756 QCoreApplication::sendEvent(focusObject, ret);
1757 return QSharedPointer<QInputMethodQueryEvent>(ret);
1765 QObject *focusObject =
qGuiApp->focusObject();
1769 QCoreApplication::sendEvent(focusObject, event);
jboolean beginBatchEdit()
jboolean setSelection(jint start, jint end)
void touchDown(int x, int y)
void longPress(int x, int y)
jboolean finishComposingText()
jint getCursorCapsMode(jint reqModes)
QString getSelectedText(jint flags)
bool isAnimating() const override
This function can be reimplemented to return true whenever input method is animating shown or hidden.
void hideSelectionHandles()
QString getTextAfterCursor(jint length, jint flags)
void reportFullscreenMode(jboolean enabled)
QRectF keyboardRect() const override
This function can be reimplemented to return virtual keyboard rectangle in currently active window co...
void reset() override
Method to be called when input method needs to be reset.
jboolean setComposingText(const QString &text, jint newCursorPosition)
void hideInputPanel() override
Request to hide input panel.
void setFocusObject(QObject *object) override
This virtual method gets called to notify updated focus to object.
jboolean commitText(const QString &text, jint newCursorPosition)
jboolean setComposingRegion(jint start, jint end)
static QAndroidInputContext * androidInputContext()
void updateSelectionHandles()
void update(Qt::InputMethodQueries queries) override
Notification on editor updates.
QString getTextBeforeCursor(jint length, jint flags)
void sendShortcut(const QKeySequence &)
void invokeAction(QInputMethod::Action action, int cursorPosition) override
Called when the word currently being composed in the input item is tapped by the user.
void updateCursorPosition()
jboolean deleteSurroundingText(jint leftLength, jint rightLength)
jboolean replaceText(jint start, jint end, const QString text, jint newCursorPosition)
void handleLocationChanged(int handleId, int x, int y)
void showInputPanel() override
Request to show input panel.
const ExtractedText & getExtractedText(jint hintMaxChars, jint hintMaxLines, jint flags)
bool isInputPanelVisible() const override
Returns input panel visibility status.
jboolean fullscreenMode()
\inmodule QtCore\reentrant
Combined button and popup list for selecting options.
QAndroidPlatformIntegration * androidPlatformIntegration()
static jfieldID m_startOffsetFieldID
static int getBlockPosition(const QSharedPointer< QInputMethodQueryEvent > &query)
static jboolean cut(JNIEnv *, jobject)
static jint getCursorCapsMode(JNIEnv *, jobject, jint reqModes)
static jfieldID m_partialEndOffsetFieldID
static jfieldID m_textFieldID
static char const *const QtExtractedTextClassName
static bool hasValidFocusObject()
static jboolean fullscreenMode(JNIEnv *, jobject)
static QRect screenInputItemRectangle()
static jboolean finishComposingText(JNIEnv *, jobject)
static jobject getExtractedText(JNIEnv *env, jobject, int hintMaxChars, int hintMaxLines, jint flags)
static jfieldID m_selectionStartFieldID
static JNINativeMethod methods[]
static QAndroidInputContext * m_androidInputContext
static jboolean copy(JNIEnv *, jobject)
static jstring getTextBeforeCursor(JNIEnv *env, jobject, jint length, jint flags)
static jfieldID m_selectionEndFieldID
static jboolean copyURL(JNIEnv *, jobject)
static char const *const QtNativeInputConnectionClassName
static int m_selectHandleWidth
static void runOnQtThread(const std::function< void()> &func)
static jboolean commitText(JNIEnv *env, jobject, jstring text, jint newCursorPosition)
static jboolean paste(JNIEnv *, jobject)
static jboolean replaceText(JNIEnv *env, jobject, jint start, jint end, jstring text, jint newCursorPosition)
static jboolean beginBatchEdit(JNIEnv *, jobject)
static jmethodID m_classConstructorMethodID
static jstring getSelectedText(JNIEnv *env, jobject, jint flags)
static jboolean setComposingRegion(JNIEnv *, jobject, jint start, jint end)
static jboolean deleteSurroundingText(JNIEnv *, jobject, jint leftLength, jint rightLength)
static jboolean setComposingText(JNIEnv *env, jobject, jstring text, jint newCursorPosition)
static jboolean updateCursorPosition(JNIEnv *, jobject)
static jstring getTextAfterCursor(JNIEnv *env, jobject, jint length, jint flags)
static int getAbsoluteCursorPosition(const QSharedPointer< QInputMethodQueryEvent > &query)
static void reportFullscreenMode(JNIEnv *, jobject, jboolean enabled)
static jclass m_extractedTextClass
static jboolean endBatchEdit(JNIEnv *, jobject)
static jfieldID m_partialStartOffsetFieldID
static jboolean setSelection(JNIEnv *, jobject, jint start, jint end)
static jboolean selectAll(JNIEnv *, jobject)