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));
92 if (!focusObject->property(
"inputMethodHints").isValid())
100 if (!hasValidFocusObject())
103 qCDebug(lcQpaInputMethods) <<
"@@@ BEGINBATCH";
104 jboolean res = JNI_FALSE;
111 if (!hasValidFocusObject())
114 qCDebug(lcQpaInputMethods) <<
"@@@ ENDBATCH";
116 jboolean res = JNI_FALSE;
124 if (!hasValidFocusObject())
128 const jchar *jstr = env->GetStringChars(text, &isCopy);
129 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
130 env->ReleaseStringChars(text, jstr);
132 qCDebug(lcQpaInputMethods) <<
"@@@ COMMIT" << str << newCursorPosition;
133 jboolean res = JNI_FALSE;
140 if (!hasValidFocusObject())
143 qCDebug(lcQpaInputMethods) <<
"@@@ DELETE" << leftLength << rightLength;
144 jboolean res = JNI_FALSE;
151 if (!hasValidFocusObject())
154 qCDebug(lcQpaInputMethods) <<
"@@@ FINISH";
155 jboolean res = JNI_FALSE;
162 if (!hasValidFocusObject())
166 const jchar *jstr = env->GetStringChars(text, &isCopy);
167 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
168 env->ReleaseStringChars(text, jstr);
170 qCDebug(lcQpaInputMethods) <<
"@@@ REPLACE" << start << end << str << newCursorPosition;
171 jboolean res = JNI_FALSE;
192 QAndroidInputContext::ExtractedText extractedText;
193 runOnQtThread([&]{extractedText =
m_androidInputContext->getExtractedText(hintMaxChars, hintMaxLines, flags);});
195 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;
197 jobject object = env->NewObject(m_extractedTextClass, m_classConstructorMethodID);
198 env->SetIntField(object, m_partialStartOffsetFieldID, extractedText.partialStartOffset);
199 env->SetIntField(object, m_partialEndOffsetFieldID, extractedText.partialEndOffset);
200 env->SetIntField(object, m_selectionStartFieldID, extractedText.selectionStart);
201 env->SetIntField(object, m_selectionEndFieldID, extractedText.selectionEnd);
202 env->SetIntField(object, m_startOffsetFieldID, extractedText.startOffset);
203 env->SetObjectField(object,
205 env->NewString(
reinterpret_cast<
const jchar *>(extractedText.text.constData()),
206 jsize(extractedText.text.length())));
218 qCDebug(lcQpaInputMethods) <<
"@@@ GETSEL" << text;
221 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
231 qCDebug(lcQpaInputMethods) <<
"@@@ GETA" << length << text;
232 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
242 qCDebug(lcQpaInputMethods) <<
"@@@ GETB" << length << text;
243 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
248 if (!hasValidFocusObject())
252 const jchar *jstr = env->GetStringChars(text, &isCopy);
253 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
254 env->ReleaseStringChars(text, jstr);
256 qCDebug(lcQpaInputMethods) <<
"@@@ SET" << str << newCursorPosition;
257 jboolean res = JNI_FALSE;
264 if (!hasValidFocusObject())
267 qCDebug(lcQpaInputMethods) <<
"@@@ SETR" << start << end;
268 jboolean res = JNI_FALSE;
276 if (!hasValidFocusObject())
279 qCDebug(lcQpaInputMethods) <<
"@@@ SETSEL" << start << end;
280 jboolean res = JNI_FALSE;
288 if (!hasValidFocusObject())
291 qCDebug(lcQpaInputMethods) <<
"@@@ SELALL";
292 jboolean res = JNI_FALSE;
299 if (!hasValidFocusObject())
302 qCDebug(lcQpaInputMethods) <<
"@@@";
303 jboolean res = JNI_FALSE;
310 if (!hasValidFocusObject())
313 qCDebug(lcQpaInputMethods) <<
"@@@";
314 jboolean res = JNI_FALSE;
321 if (!hasValidFocusObject())
324 qCDebug(lcQpaInputMethods) <<
"@@@";
325 jboolean res = JNI_FALSE;
332 if (!hasValidFocusObject())
335 qCDebug(lcQpaInputMethods) <<
"@@@ PASTE";
336 jboolean res = JNI_FALSE;
343 if (!hasValidFocusObject())
346 qCDebug(lcQpaInputMethods) <<
"@@@ UPDATECURSORPOS";
369 {
"beginBatchEdit",
"()Z", (
void *)beginBatchEdit},
370 {
"endBatchEdit",
"()Z", (
void *)endBatchEdit},
371 {
"commitText",
"(Ljava/lang/String;I)Z", (
void *)commitText},
372 {
"deleteSurroundingText",
"(II)Z", (
void *)deleteSurroundingText},
373 {
"finishComposingText",
"()Z", (
void *)finishComposingText},
374 {
"getCursorCapsMode",
"(I)I", (
void *)getCursorCapsMode},
375 {
"getExtractedText",
"(III)Lorg/qtproject/qt/android/QtExtractedText;", (
void *)getExtractedText},
376 {
"getSelectedText",
"(I)Ljava/lang/String;", (
void *)getSelectedText},
377 {
"getTextAfterCursor",
"(II)Ljava/lang/String;", (
void *)getTextAfterCursor},
378 {
"getTextBeforeCursor",
"(II)Ljava/lang/String;", (
void *)getTextBeforeCursor},
379 {
"replaceText",
"(IILjava/lang/String;I)Z", (
void *)replaceText},
380 {
"setComposingText",
"(Ljava/lang/String;I)Z", (
void *)setComposingText},
381 {
"setComposingRegion",
"(II)Z", (
void *)setComposingRegion},
382 {
"setSelection",
"(II)Z", (
void *)setSelection},
383 {
"selectAll",
"()Z", (
void *)selectAll},
384 {
"cut",
"()Z", (
void *)cut},
385 {
"copy",
"()Z", (
void *)copy},
386 {
"copyURL",
"()Z", (
void *)copyURL},
387 {
"paste",
"()Z", (
void *)paste},
388 {
"updateCursorPosition",
"()Z", (
void *)updateCursorPosition},
389 {
"reportFullscreenMode",
"(Z)V", (
void *)reportFullscreenMode},
390 {
"fullscreenMode",
"()Z", (
void *)fullscreenMode}
395 QRect windowRect = QPlatformInputContext::inputItemRectangle().toRect();
396 QPlatformWindow *window =
qGuiApp->focusWindow()->handle();
397 return QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
402 , m_composingTextStart(-1)
403 , m_composingCursor(-1)
405 , m_batchEditNestingLevel(0)
407 , m_fullScreenMode(
false)
411 if (Q_UNLIKELY(!clazz)) {
412 qCritical() <<
"Native registration unable to find class '"
418 if (Q_UNLIKELY(env->RegisterNatives(clazz, methods,
sizeof(
methods) /
sizeof(
methods[0])) < 0)) {
419 qCritical() <<
"RegisterNatives failed for '"
426 if (Q_UNLIKELY(!clazz)) {
427 qCritical() <<
"Native registration unable to find class '"
435 if (Q_UNLIKELY(!m_classConstructorMethodID)) {
436 qCritical(
"GetMethodID failed");
441 if (Q_UNLIKELY(!m_partialEndOffsetFieldID)) {
442 qCritical(
"Can't find field partialEndOffset");
447 if (Q_UNLIKELY(!m_partialStartOffsetFieldID)) {
448 qCritical(
"Can't find field partialStartOffset");
453 if (Q_UNLIKELY(!m_selectionEndFieldID)) {
454 qCritical(
"Can't find field selectionEnd");
459 if (Q_UNLIKELY(!m_selectionStartFieldID)) {
460 qCritical(
"Can't find field selectionStart");
465 if (Q_UNLIKELY(!m_startOffsetFieldID)) {
466 qCritical(
"Can't find field startOffset");
470 m_textFieldID = env->GetFieldID(m_extractedTextClass,
"text",
"Ljava/lang/String;");
471 if (Q_UNLIKELY(!m_textFieldID)) {
472 qCritical(
"Can't find field text");
475 qRegisterMetaType<QInputMethodEvent *>(
"QInputMethodEvent*");
476 qRegisterMetaType<QInputMethodQueryEvent *>(
"QInputMethodQueryEvent*");
479 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
480 this, &QAndroidInputContext::updateSelectionHandles);
481 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::anchorRectangleChanged,
482 this, &QAndroidInputContext::updateSelectionHandles);
483 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::inputItemClipRectangleChanged,
this, [
this]{
484 auto im =
qGuiApp->inputMethod();
485 if (!im->inputItemClipRectangle().contains(im->anchorRectangle()) ||
486 !im->inputItemClipRectangle().contains(im->cursorRectangle())) {
487 m_handleMode = Hidden;
488 updateSelectionHandles();
491 m_hideCursorHandleTimer.setInterval(4000);
492 m_hideCursorHandleTimer.setSingleShot(
true);
493 m_hideCursorHandleTimer.setTimerType(Qt::VeryCoarseTimer);
494 connect(&m_hideCursorHandleTimer, &QTimer::timeout,
this, [
this]{
495 m_handleMode = Hidden;
496 updateSelectionHandles();
520 QVariant absolutePos = query->value(Qt::ImAbsolutePosition);
521 return absolutePos.isValid() ? absolutePos.toInt() : query->value(Qt::ImCursorPosition).toInt();
527 QVariant absolutePos = query->value(Qt::ImAbsolutePosition);
528 return absolutePos.isValid() ? absolutePos.toInt() - query->value(Qt::ImCursorPosition).toInt() : 0;
533 focusObjectStopComposing();
535 m_batchEditNestingLevel = 0;
536 m_handleMode = Hidden;
538 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(Qt::ImEnabled);
539 if (!query.isNull() && query->value(Qt::ImEnabled).toBool()) {
549 focusObjectStopComposing();
554 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
555 if (!query.isNull() && m_batchEditNestingLevel == 0) {
556 const int cursorPos = getAbsoluteCursorPosition(query);
557 const int composeLength = m_composingText.length();
560 if (m_composingText.isEmpty() != (m_composingTextStart == -1))
561 qWarning() <<
"Input method out of sync" << m_composingText << m_composingTextStart;
563 int realSelectionStart = cursorPos;
564 int realSelectionEnd = cursorPos;
566 int cpos = query->value(Qt::ImCursorPosition).toInt();
567 int anchor = query->value(Qt::ImAnchorPosition).toInt();
568 if (cpos != anchor) {
569 if (!m_composingText.isEmpty()) {
570 qWarning(
"Selecting text while preediting may give unpredictable results.");
571 focusObjectStopComposing();
573 int blockPos = getBlockPosition(query);
574 realSelectionStart = blockPos + cpos;
575 realSelectionEnd = blockPos + anchor;
578 if (focusObjectIsComposing())
579 realSelectionStart = realSelectionEnd = m_composingCursor;
582 if (realSelectionStart > realSelectionEnd)
583 std::swap(realSelectionStart, realSelectionEnd);
586 m_composingTextStart
, m_composingTextStart + composeLength
);
592 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
595 return query->value(Qt::ImHints).toUInt() & Qt::ImhNoTextHandles;
600 if (m_fullScreenMode) {
604 static bool noHandles = qEnvironmentVariableIntValue(
"QT_QPA_NO_TEXT_HANDLES");
605 if (noHandles || !m_focusObject)
608 if (isImhNoTextHandlesSet()) {
613 auto im =
qGuiApp->inputMethod();
615 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled
616 | Qt::ImCurrentSelection | Qt::ImHints | Qt::ImSurroundingText
618 QCoreApplication::sendEvent(m_focusObject, &query);
620 int cpos = query.value(Qt::ImCursorPosition).toInt();
621 int anchor = query.value(Qt::ImAnchorPosition).toInt();
622 const QVariant readOnlyVariant = query.value(Qt::ImReadOnly);
623 bool readOnly = readOnlyVariant.toBool();
624 QPlatformWindow *qPlatformWindow =
qGuiApp->focusWindow()->handle();
626 if (!readOnly && ((m_handleMode & 0xff) == Hidden)) {
631 if ( cpos == anchor && (!readOnlyVariant.isValid() || readOnly)) {
636 if (cpos == anchor || im->anchorRectangle().isNull()) {
637 auto curRect = cursorRectangle();
638 QPoint cursorPointGlobal = qPlatformWindow->mapToGlobal(
639 QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()));
640 QPoint cursorPoint(curRect.center().x(), curRect.bottom());
645 if (cursorPointGlobal != cursorPoint) {
646 x = cursorPointGlobal.x();
647 y = cursorPointGlobal.y();
650 QPoint editMenuPoint(x, y);
651 m_handleMode &= ShowEditPopup;
652 m_handleMode |= ShowCursor;
654 if (!query.value(Qt::ImSurroundingText).toString().isEmpty())
656 QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPointGlobal);
657 m_hideCursorHandleTimer.start();
662 m_handleMode = ShowSelection | ShowEditPopup ;
663 auto leftRect = cursorRectangle();
664 auto rightRect = anchorRectangle();
666 std::swap(leftRect, rightRect);
670 QPoint leftPoint(qPlatformWindow->mapToGlobal(leftRect.bottomLeft().toPoint()));
671 QPoint rightPoint(qPlatformWindow->mapToGlobal(rightRect.bottomRight().toPoint()));
674 if (platformIntegration) {
678 int rightSideOfScreen = platformIntegration
->screen()->availableGeometry().right();
685 QPoint editPoint(qPlatformWindow->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint()));
689 QtAndroidInput::updateHandles(m_handleMode, editPoint, buttons, leftPoint, rightPoint,
690 query.value(Qt::ImCurrentSelection).toString().isRightToLeft());
691 m_hideCursorHandleTimer.stop();
696
697
698
699
702 if (m_batchEditNestingLevel != 0) {
703 qWarning() <<
"QAndroidInputContext::handleLocationChanged returned";
709 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition
710 | Qt::ImAbsolutePosition | Qt::ImCurrentSelection);
711 QCoreApplication::sendEvent(m_focusObject, &query);
712 int cpos = query.value(Qt::ImCursorPosition).toInt();
713 int anchor = query.value(Qt::ImAnchorPosition).toInt();
714 auto leftRect = cursorRectangle();
715 auto rightRect = anchorRectangle();
717 std::swap(leftRect, rightRect);
720 if (handleId == 2 && point.y() > rightRect.center().y()) {
721 point.setY(rightRect.center().y());
722 }
else if (handleId == 3 && point.y() < leftRect.center().y()) {
723 point.setY(leftRect.center().y());
727 auto object = m_focusObject->parent();
730 if (QString::compare(object->metaObject()->className(),
731 "QDialog", Qt::CaseInsensitive) == 0) {
732 dialogMoveX += object->property(
"x").toInt();
734 object = object->parent();
738 QPointF(QHighDpi::fromNativePixels(point, QGuiApplication::focusWindow()));
739 const QPointF fixedPosition = QPointF(position.x() - dialogMoveX, position.y());
740 const QInputMethod *im = QGuiApplication::inputMethod();
741 const QTransform mapToLocal = im->inputItemTransform().inverted();
742 const int handlePos = im->queryFocusObject(Qt::ImCursorPosition, mapToLocal.map(fixedPosition)).toInt(&ok);
748 int newAnchor = anchor;
749 if (newAnchor > newCpos)
750 std::swap(newAnchor, newCpos);
754 newAnchor = handlePos;
755 }
else if (handleId == 2) {
756 newAnchor = handlePos;
757 }
else if (handleId == 3) {
762
763
764
765
766 if ((handleId == 2 || handleId == 3) && newCpos <= newAnchor) {
767 QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme,
768 query.value(Qt::ImCurrentSelection).toString());
770 const int oldSelectionStartPos = qMin(cpos, anchor);
774 finder.toPreviousBoundary();
775 newAnchor = finder.position() + oldSelectionStartPos;
778 finder.toNextBoundary();
779 newCpos = finder.position() + oldSelectionStartPos;
784 if (!focusObjectIsComposing() && newCpos == cpos && newAnchor == anchor)
788
789
790
791
792 if (focusObjectIsComposing() && handleId == 1) {
793 int absoluteCpos = query.value(Qt::ImAbsolutePosition).toInt(&ok);
796 const int blockPos = absoluteCpos - cpos;
798 if (blockPos + newCpos == m_composingCursor)
802 BatchEditLock batchEditLock(
this);
804 focusObjectStopComposing();
806 QList<QInputMethodEvent::Attribute> attributes;
807 attributes.append({ QInputMethodEvent::Selection, newAnchor, newCpos - newAnchor });
808 if (newCpos != newAnchor)
809 attributes.append({ QInputMethodEvent::Cursor, 0, 0 });
812 QGuiApplication::sendEvent(m_focusObject, &event);
817 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
819 m_handleMode = ShowCursor;
821 m_hideCursorHandleTimer.stop();
823 if (focusObjectIsComposing()) {
824 const int curBlockPos = getBlockPosition(
825 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
826 const int touchPosition = curBlockPos
827 + queryFocusObject(Qt::ImCursorPosition, QPointF(x, y)).toInt();
828 if (touchPosition != m_composingCursor)
829 focusObjectStopComposing();
833 QPlatformWindow *window =
qGuiApp->focusWindow()->handle();
834 const QRectF curRect = cursorRectangle();
835 const QPoint cursorGlobalPoint = window->mapToGlobal(QPoint(curRect.x(), curRect.y()));
836 const QRect windowRect = QPlatformInputContext::inputItemClipRectangle().toRect();
837 const QRect windowGlobalRect = QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
839 if (windowGlobalRect.contains(cursorGlobalPoint.x(), cursorGlobalPoint.y()))
846 static bool noHandles = qEnvironmentVariableIntValue(
"QT_QPA_NO_TEXT_HANDLES");
850 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
851 BatchEditLock batchEditLock(
this);
853 focusObjectStopComposing();
854 const QPointF touchPoint(x, y);
855 setSelectionOnFocusObject(touchPoint, touchPoint);
857 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor);
858 QCoreApplication::sendEvent(m_focusObject, &query);
859 int cursor = query.value(Qt::ImCursorPosition).toInt();
861 QString before = query.value(Qt::ImTextBeforeCursor).toString();
862 QString after = query.value(Qt::ImTextAfterCursor).toString();
863 for (
const auto &ch : after) {
864 if (!ch.isLetterOrNumber())
869 for (
auto itch = before.rbegin(); itch != after.rend(); ++itch) {
870 if (!itch->isLetterOrNumber())
874 if (cursor == anchor || cursor < 0 || cursor - anchor > 500) {
875 m_handleMode = ShowCursor | ShowEditPopup;
879 QList<QInputMethodEvent::Attribute> imAttributes;
880 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
881 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, anchor, cursor - anchor, QVariant()));
883 QGuiApplication::sendEvent(m_focusObject, &event);
885 m_handleMode = ShowSelection | ShowEditPopup;
894 m_handleMode = Hidden;
901 if (m_handleMode & ShowSelection) {
902 m_handleMode = Hidden;
905 m_hideCursorHandleTimer.start();
911 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(queries);
914#warning TODO extract the needed data from query
919#warning TODO Handle at least QInputMethod::ContextMenu action
921 Q_UNUSED(cursorPosition);
929 return QtAndroidInput::softwareKeyboardRect();
939 if (QGuiApplication::applicationState() != Qt::ApplicationActive) {
940 connect(
qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
this, SLOT(showInputPanelLater(Qt::ApplicationState)));
943 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
947 if (!
qGuiApp->focusWindow()->handle())
950 disconnect(m_updateCursorPosConnection);
951 m_updateCursorPosConnection = {};
953 if (
qGuiApp->focusObject()->metaObject()->indexOfSignal(
"cursorPositionChanged(int,int)") >= 0)
954 m_updateCursorPosConnection = connect(
qGuiApp->focusObject(), SIGNAL(cursorPositionChanged(
int,
int)),
this, SLOT(updateCursorPosition()));
955 else if (
qGuiApp->focusObject()->metaObject()->indexOfSignal(
"cursorPositionChanged()") >= 0)
956 m_updateCursorPosConnection = connect(
qGuiApp->focusObject(), SIGNAL(cursorPositionChanged()),
this, SLOT(updateCursorPosition()));
958 QRect rect = screenInputItemRectangle();
959 QtAndroidInput::showSoftwareKeyboard(rect.left(), rect.top(), rect.width(), rect.height(),
960 query->value(Qt::ImHints).toUInt(),
961 query->value(Qt::ImEnterKeyType).toUInt());
966 if (state != Qt::ApplicationActive)
968 disconnect(
qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
this, SLOT(showInputPanelLater(Qt::ApplicationState)));
974 if (
qGuiApp->thread() == QThread::currentThread())
977 QMetaObject::invokeMethod(
this,
"safeCall", conType, Q_ARG(std::function<
void()>, func));
992 return m_composingText.length();
997 m_composingText.clear();
998 m_composingTextStart = -1;
999 m_composingCursor = -1;
1000 m_extractedText.clear();
1006 return m_focusObject;
1011 if (object != m_focusObject) {
1012 focusObjectStopComposing();
1013 m_focusObject = object;
1021 ++m_batchEditNestingLevel;
1027 if (--m_batchEditNestingLevel == 0) {
1028 focusObjectStartComposing();
1035
1036
1037
1040 BatchEditLock batchEditLock(
this);
1041 return setComposingText(text, newCursorPosition) && finishComposingText();
1046 BatchEditLock batchEditLock(
this);
1048 focusObjectStopComposing();
1050 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1054 if (leftLength < 0) {
1055 rightLength += -leftLength;
1059 const int initialBlockPos = getBlockPosition(query);
1060 const int initialCursorPos = getAbsoluteCursorPosition(query);
1061 const int initialAnchorPos = initialBlockPos + query->value(Qt::ImAnchorPosition).toInt();
1064
1065
1066
1067
1068
1069
1070
1071
1073 m_composingText.isEmpty()
1074 ? qMin(initialCursorPos, initialAnchorPos)
1075 : qMin(qMin(initialCursorPos, initialAnchorPos), m_composingTextStart);
1077 const int rightBegin =
1078 m_composingText.isEmpty()
1079 ? qMax(initialCursorPos, initialAnchorPos)
1080 : qMax(qMax(initialCursorPos, initialAnchorPos),
1081 m_composingTextStart + m_composingText.length());
1083 int textBeforeCursorLen;
1084 int textAfterCursorLen;
1086 QVariant textBeforeCursor = query->value(Qt::ImTextBeforeCursor);
1087 QVariant textAfterCursor = query->value(Qt::ImTextAfterCursor);
1088 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1089 textBeforeCursorLen = textBeforeCursor.toString().length();
1090 textAfterCursorLen = textAfterCursor.toString().length();
1092 textBeforeCursorLen = initialCursorPos - initialBlockPos;
1093 textAfterCursorLen =
1094 query->value(Qt::ImSurroundingText).toString().length() - textBeforeCursorLen;
1097 leftLength = qMin(qMax(0, textBeforeCursorLen - (initialCursorPos - leftEnd)), leftLength);
1098 rightLength = qMin(qMax(0, textAfterCursorLen - (rightBegin - initialCursorPos)), rightLength);
1100 if (leftLength == 0 && rightLength == 0)
1103 if (leftEnd == rightBegin) {
1106 event.setCommitString({}, -leftLength, leftLength + rightLength);
1107 QGuiApplication::sendEvent(m_focusObject, &event);
1109 if (initialCursorPos != initialAnchorPos) {
1111 { QInputMethodEvent::Selection, initialCursorPos - initialBlockPos, 0 }
1114 QGuiApplication::sendEvent(m_focusObject, &event);
1117 int currentCursorPos = initialCursorPos;
1119 if (rightLength > 0) {
1121 event.setCommitString({}, rightBegin - currentCursorPos, rightLength);
1122 QGuiApplication::sendEvent(m_focusObject, &event);
1124 currentCursorPos = rightBegin;
1127 if (leftLength > 0) {
1128 const int leftBegin = leftEnd - leftLength;
1131 event.setCommitString({}, leftBegin - currentCursorPos, leftLength);
1132 QGuiApplication::sendEvent(m_focusObject, &event);
1134 currentCursorPos = leftBegin;
1136 if (!m_composingText.isEmpty())
1137 m_composingTextStart -= leftLength;
1141 if (currentCursorPos != initialCursorPos - leftLength
1142 || initialCursorPos != initialAnchorPos) {
1144 const int currentBlockPos = getBlockPosition(
1145 focusObjectInputMethodQuery(Qt::ImAbsolutePosition | Qt::ImCursorPosition));
1148 { QInputMethodEvent::Selection, initialCursorPos - leftLength - currentBlockPos,
1149 initialAnchorPos - initialCursorPos },
1150 { QInputMethodEvent::Cursor, 0, 0 }
1153 QGuiApplication::sendEvent(m_focusObject, &event);
1163 BatchEditLock batchEditLock(
this);
1165 if (!focusObjectStopComposing())
1173
1174
1175
1176
1179 if (!finishComposingText())
1181 if (!setSelection(start, end))
1184 return commitText(text, newCursorPosition);
1189 m_fullScreenMode = enabled;
1190 BatchEditLock batchEditLock(
this);
1191 if (!focusObjectStopComposing())
1195 m_handleMode = Hidden;
1203 return m_fullScreenMode;
1208 return m_composingCursor != -1;
1213 if (focusObjectIsComposing() || m_composingText.isEmpty())
1217 if (m_composingText.contains(u'\n'))
1220 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1224 if (query->value(Qt::ImCursorPosition).toInt() != query->value(Qt::ImAnchorPosition).toInt())
1227 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1228 if (absoluteCursorPos < m_composingTextStart
1229 || absoluteCursorPos > m_composingTextStart + m_composingText.length())
1232 m_composingCursor = absoluteCursorPos;
1234 QTextCharFormat underlined;
1235 underlined.setFontUnderline(
true);
1238 { QInputMethodEvent::Cursor, absoluteCursorPos - m_composingTextStart, 1 },
1239 { QInputMethodEvent::TextFormat, 0,
int(m_composingText.length()), underlined }
1242 event.setCommitString({}, m_composingTextStart - absoluteCursorPos, m_composingText.length());
1244 QGuiApplication::sendEvent(m_focusObject, &event);
1249 if (!focusObjectIsComposing())
1252 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1256 const int blockPos = getBlockPosition(query);
1257 const int localCursorPos = m_composingCursor - blockPos;
1259 m_composingCursor = -1;
1263 QList<QInputMethodEvent::Attribute> attributes;
1265 event.setCommitString(m_composingText);
1266 sendInputMethodEvent(&event);
1270 QList<QInputMethodEvent::Attribute> attributes;
1272 QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0));
1274 sendInputMethodEvent(&event);
1283 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1287 const uint qtInputMethodHints = query->value(Qt::ImHints).toUInt();
1288 const int localPos = query->value(Qt::ImCursorPosition).toInt();
1290 bool atWordBoundary =
1292 && (!focusObjectIsComposing() || m_composingCursor == m_composingTextStart);
1294 if (!atWordBoundary) {
1295 QString surroundingText = query->value(Qt::ImSurroundingText).toString();
1296 surroundingText.truncate(localPos);
1297 if (focusObjectIsComposing())
1298 surroundingText += QStringView{m_composingText}.left(m_composingCursor - m_composingTextStart);
1300 QTextBoundaryFinder finder(QTextBoundaryFinder::Sentence, surroundingText + u'A');
1301 finder.setPosition(surroundingText.length());
1302 if (finder.isAtBoundary())
1303 atWordBoundary = finder.isAtBoundary();
1305 if (atWordBoundary && !(qtInputMethodHints & Qt::ImhLowercaseOnly) && !(qtInputMethodHints & Qt::ImhNoAutoUppercase))
1306 res |= CAP_MODE_SENTENCES;
1308 if (qtInputMethodHints & Qt::ImhUppercaseOnly)
1309 res |= CAP_MODE_CHARACTERS;
1322 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(
1323 Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition);
1325 return m_extractedText;
1327 const int cursorPos = getAbsoluteCursorPosition(query);
1328 const int blockPos = getBlockPosition(query);
1335 QVariant textBeforeCursor = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, INT_MAX);
1336 QVariant textAfterCursor = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, INT_MAX);
1337 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1338 if (focusObjectIsComposing()) {
1339 m_extractedText.text =
1340 textBeforeCursor.toString() + m_composingText + textAfterCursor.toString();
1342 m_extractedText.text = textBeforeCursor.toString() + textAfterCursor.toString();
1345 m_extractedText.startOffset = qMax(0, cursorPos - textBeforeCursor.toString().length());
1347 m_extractedText.text = focusObjectInputMethodQuery(Qt::ImSurroundingText)
1348 ->value(Qt::ImSurroundingText).toString();
1350 if (focusObjectIsComposing())
1351 m_extractedText.text.insert(cursorPos - blockPos, m_composingText);
1353 m_extractedText.startOffset = blockPos;
1356 if (focusObjectIsComposing()) {
1357 m_extractedText.selectionStart = m_composingCursor - m_extractedText.startOffset;
1358 m_extractedText.selectionEnd = m_extractedText.selectionStart;
1360 m_extractedText.selectionStart = cursorPos - m_extractedText.startOffset;
1361 m_extractedText.selectionEnd =
1362 blockPos + query->value(Qt::ImAnchorPosition).toInt() - m_extractedText.startOffset;
1365 if (m_extractedText.selectionStart > m_extractedText.selectionEnd)
1366 std::swap(m_extractedText.selectionStart, m_extractedText.selectionEnd);
1369 return m_extractedText;
1374 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1378 return query->value(Qt::ImCurrentSelection).toString();
1388 QVariant reportedTextAfter = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, length);
1389 if (reportedTextAfter.isValid()) {
1390 text = reportedTextAfter.toString();
1393 QSharedPointer<QInputMethodQueryEvent> query =
1394 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1396 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1397 text = query->value(Qt::ImSurroundingText).toString().mid(cursorPos);
1401 if (focusObjectIsComposing()) {
1403 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1404 text = QStringView{m_composingText}.mid(cursorPosInsidePreedit) + text;
1407 QSharedPointer<QInputMethodQueryEvent> query =
1408 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1410 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1411 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1412 if (anchorPos > cursorPos)
1413 text.remove(0, anchorPos - cursorPos);
1417 text.truncate(length);
1428 QVariant reportedTextBefore = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, length);
1429 if (reportedTextBefore.isValid()) {
1430 text = reportedTextBefore.toString();
1433 QSharedPointer<QInputMethodQueryEvent> query =
1434 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1436 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1437 text = query->value(Qt::ImSurroundingText).toString().left(cursorPos);
1441 if (focusObjectIsComposing()) {
1443 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1444 text += QStringView{m_composingText}.left(cursorPosInsidePreedit);
1447 QSharedPointer<QInputMethodQueryEvent> query =
1448 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1450 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1451 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1452 if (anchorPos < cursorPos)
1453 text.chop(cursorPos - anchorPos);
1457 if (text.length() > length)
1458 text = text.right(length);
1463
1464
1465
1466
1467
1468
1469
1470
1474 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1478 BatchEditLock batchEditLock(
this);
1480 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1481 int absoluteAnchorPos = getBlockPosition(query) + query->value(Qt::ImAnchorPosition).toInt();
1483 auto setCursorPosition = [=]() {
1484 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1485 QInputMethodEvent event({}, { { QInputMethodEvent::Selection, cursorPos, 0 } });
1486 QGuiApplication::sendEvent(m_focusObject, &event);
1491 if (!m_composingText.isEmpty() && absoluteCursorPos != absoluteAnchorPos) {
1492 setCursorPosition();
1493 absoluteAnchorPos = absoluteCursorPos;
1498 if (absoluteCursorPos == 0 && text.length() == 1 && getTextAfterCursor(1,1).length() >= 0) {
1499 setCursorPosition();
1505 const int effectiveAbsoluteCursorPos = qMin(absoluteCursorPos, absoluteAnchorPos);
1506 if (m_composingTextStart == -1)
1507 m_composingTextStart = effectiveAbsoluteCursorPos;
1509 const int oldComposingTextLen = m_composingText.length();
1510 m_composingText = text;
1512 const int newAbsoluteCursorPos =
1513 newCursorPosition <= 0
1514 ? m_composingTextStart + newCursorPosition
1515 : m_composingTextStart + m_composingText.length() + newCursorPosition - 1;
1517 const bool focusObjectWasComposing = focusObjectIsComposing();
1520 if (!m_composingText.isEmpty() && !m_composingText.contains(u'\n')
1521 && newAbsoluteCursorPos >= m_composingTextStart
1522 && newAbsoluteCursorPos <= m_composingTextStart + m_composingText.length())
1523 m_composingCursor = newAbsoluteCursorPos;
1525 m_composingCursor = -1;
1527 if (focusObjectIsComposing()) {
1528 QTextCharFormat underlined;
1529 underlined.setFontUnderline(
true);
1532 { QInputMethodEvent::TextFormat, 0,
int(m_composingText.length()), underlined },
1533 { QInputMethodEvent::Cursor, m_composingCursor - m_composingTextStart, 1 }
1536 if (oldComposingTextLen > 0 && !focusObjectWasComposing) {
1537 event.setCommitString({}, m_composingTextStart - effectiveAbsoluteCursorPos,
1538 oldComposingTextLen);
1540 if (m_composingText.isEmpty())
1543 QGuiApplication::sendEvent(m_focusObject, &event);
1547 if (focusObjectWasComposing) {
1548 event.setCommitString(m_composingText);
1550 event.setCommitString(m_composingText,
1551 m_composingTextStart - effectiveAbsoluteCursorPos,
1552 oldComposingTextLen);
1554 if (m_composingText.isEmpty())
1557 QGuiApplication::sendEvent(m_focusObject, &event);
1560 if (!focusObjectIsComposing() && newCursorPosition != 1) {
1564 const int newBlockPos = getBlockPosition(
1565 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
1568 { QInputMethodEvent::Selection, newAbsoluteCursorPos - newBlockPos, 0 }
1571 QGuiApplication::sendEvent(m_focusObject, &event);
1585 BatchEditLock batchEditLock(
this);
1592 finishComposingText();
1594 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1603 QString text = query->value(Qt::ImSurroundingText).toString();
1604 int textOffset = getBlockPosition(query);
1606 if (start < textOffset || end > textOffset + text.length()) {
1607 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1609 if (end - textOffset > text.length()) {
1610 const QString after = query->value(Qt::ImTextAfterCursor).toString();
1611 const int additionalSuffixLen = after.length() - (text.length() - cursorPos);
1613 if (additionalSuffixLen > 0)
1614 text += QStringView{after}.right(additionalSuffixLen);
1617 if (start < textOffset) {
1618 QString before = query->value(Qt::ImTextBeforeCursor).toString();
1619 before.chop(cursorPos);
1621 if (!before.isEmpty()) {
1622 text = before + text;
1623 textOffset -= before.length();
1627 if (start < textOffset || end - textOffset > text.length()) {
1628 qCDebug(lcQpaInputMethods) <<
"Warning: setComposingRegion: failed to retrieve text from composing region";
1634 m_composingText = text.mid(start - textOffset, end - start);
1635 m_composingTextStart = start;
1642 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1646 BatchEditLock batchEditLock(
this);
1648 int blockPosition = getBlockPosition(query);
1649 int localCursorPos = start - blockPosition;
1651 if (focusObjectIsComposing() && start == end && start >= m_composingTextStart
1652 && start <= m_composingTextStart + m_composingText.length()) {
1655 int localOldPos = query->value(Qt::ImCursorPosition).toInt();
1656 int pos = localCursorPos - localOldPos;
1657 QList<QInputMethodEvent::Attribute> attributes;
1658 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, pos, 1));
1663 QTextCharFormat underlined;
1664 underlined.setFontUnderline(
true);
1665 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, m_composingText.length(),
1666 QVariant(underlined)));
1667 m_composingCursor = start;
1670 QGuiApplication::sendEvent(m_focusObject, &event);
1673 focusObjectStopComposing();
1674 QList<QInputMethodEvent::Attribute> attributes;
1675 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection,
1679 QGuiApplication::sendEvent(m_focusObject, &event);
1686 BatchEditLock batchEditLock(
this);
1688 focusObjectStopComposing();
1689 m_handleMode = ShowCursor;
1690 sendShortcut(QKeySequence::SelectAll);
1696 BatchEditLock batchEditLock(
this);
1700 finishComposingText();
1702 m_handleMode = ShowCursor;
1703 sendShortcut(QKeySequence::Cut);
1709 BatchEditLock batchEditLock(
this);
1711 focusObjectStopComposing();
1712 m_handleMode = ShowCursor;
1713 sendShortcut(QKeySequence::Copy);
1725 BatchEditLock batchEditLock(
this);
1728 finishComposingText();
1730 m_handleMode = ShowCursor;
1731 sendShortcut(QKeySequence::Paste);
1737 for (
int i = 0; i < sequence.count(); ++i) {
1738 const QKeyCombination keys = sequence[i];
1739 Qt::Key key = Qt::Key(keys.toCombined() & ~Qt::KeyboardModifierMask);
1740 Qt::KeyboardModifiers mod = Qt::KeyboardModifiers(keys.toCombined() & Qt::KeyboardModifierMask);
1742 QKeyEvent pressEvent(QEvent::KeyPress, key, mod);
1743 QKeyEvent releaseEvent(QEvent::KeyRelease, key, mod);
1745 QGuiApplication::sendEvent(m_focusObject, &pressEvent);
1746 QGuiApplication::sendEvent(m_focusObject, &releaseEvent);
1750QSharedPointer<QInputMethodQueryEvent>
QAndroidInputContext::focusObjectInputMethodQuery(Qt::InputMethodQueries queries) {
1754 QObject *focusObject =
qGuiApp->focusObject();
1758 QInputMethodQueryEvent *ret =
new QInputMethodQueryEvent(queries);
1759 QCoreApplication::sendEvent(focusObject, ret);
1760 return QSharedPointer<QInputMethodQueryEvent>(ret);
1768 QObject *focusObject =
qGuiApp->focusObject();
1772 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
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)