7#include <android/log.h>
14#include "private/qhighdpiscaling_p.h"
16#include <QTextBoundaryFinder>
17#include <QTextCharFormat>
18#include <QtCore/QJniEnvironment>
19#include <QtCore/QJniObject>
21#include <qguiapplication.h>
22#include <qinputmethod.h>
23#include <qsharedpointer.h>
26#include <qpa/qplatformwindow.h>
30using namespace Qt::StringLiterals;
41 m_context->beginBatchEdit();
46 m_context->endBatchEdit();
49 BatchEditLock(
const BatchEditLock &) =
delete;
50 BatchEditLock &operator=(
const BatchEditLock &) =
delete;
74 QtAndroidPrivate::AndroidDeadlockProtector protector(
75 u"QAndroidInputContext::runOnQtThread()"_s);
76 if (!protector.acquire())
78 QMetaObject::invokeMethod(m_androidInputContext,
"safeCall", Qt::BlockingQueuedConnection, Q_ARG(std::function<
void()>, func));
90 if (!focusObject->property(
"inputMethodHints").isValid())
98 if (!hasValidFocusObject())
101 qCDebug(lcQpaInputMethods) <<
"@@@ BEGINBATCH";
102 jboolean res = JNI_FALSE;
109 if (!hasValidFocusObject())
112 qCDebug(lcQpaInputMethods) <<
"@@@ ENDBATCH";
114 jboolean res = JNI_FALSE;
122 if (!hasValidFocusObject())
126 const jchar *jstr = env->GetStringChars(text, &isCopy);
127 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
128 env->ReleaseStringChars(text, jstr);
130 qCDebug(lcQpaInputMethods) <<
"@@@ COMMIT" << str << newCursorPosition;
131 jboolean res = JNI_FALSE;
138 if (!hasValidFocusObject())
141 qCDebug(lcQpaInputMethods) <<
"@@@ DELETE" << leftLength << rightLength;
142 jboolean res = JNI_FALSE;
149 if (!hasValidFocusObject())
152 qCDebug(lcQpaInputMethods) <<
"@@@ FINISH";
153 jboolean res = JNI_FALSE;
160 if (!hasValidFocusObject())
164 const jchar *jstr = env->GetStringChars(text, &isCopy);
165 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
166 env->ReleaseStringChars(text, jstr);
168 qCDebug(lcQpaInputMethods) <<
"@@@ REPLACE" << start << end << str << newCursorPosition;
169 jboolean res = JNI_FALSE;
190 QAndroidInputContext::ExtractedText extractedText;
191 runOnQtThread([&]{extractedText =
m_androidInputContext->getExtractedText(hintMaxChars, hintMaxLines, flags);});
193 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;
195 jobject object = env->NewObject(m_extractedTextClass, m_classConstructorMethodID);
196 env->SetIntField(object, m_partialStartOffsetFieldID, extractedText.partialStartOffset);
197 env->SetIntField(object, m_partialEndOffsetFieldID, extractedText.partialEndOffset);
198 env->SetIntField(object, m_selectionStartFieldID, extractedText.selectionStart);
199 env->SetIntField(object, m_selectionEndFieldID, extractedText.selectionEnd);
200 env->SetIntField(object, m_startOffsetFieldID, extractedText.startOffset);
201 env->SetObjectField(object,
203 env->NewString(
reinterpret_cast<
const jchar *>(extractedText.text.constData()),
204 jsize(extractedText.text.length())));
216 qCDebug(lcQpaInputMethods) <<
"@@@ GETSEL" << text;
219 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
229 qCDebug(lcQpaInputMethods) <<
"@@@ GETA" << length << text;
230 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
240 qCDebug(lcQpaInputMethods) <<
"@@@ GETB" << length << text;
241 return env->NewString(
reinterpret_cast<
const jchar *>(text.constData()), jsize(text.length()));
246 if (!hasValidFocusObject())
250 const jchar *jstr = env->GetStringChars(text, &isCopy);
251 QString str(
reinterpret_cast<
const QChar *>(jstr), env->GetStringLength(text));
252 env->ReleaseStringChars(text, jstr);
254 qCDebug(lcQpaInputMethods) <<
"@@@ SET" << str << newCursorPosition;
255 jboolean res = JNI_FALSE;
262 if (!hasValidFocusObject())
265 qCDebug(lcQpaInputMethods) <<
"@@@ SETR" << start << end;
266 jboolean res = JNI_FALSE;
274 if (!hasValidFocusObject())
277 qCDebug(lcQpaInputMethods) <<
"@@@ SETSEL" << start << end;
278 jboolean res = JNI_FALSE;
286 if (!hasValidFocusObject())
289 qCDebug(lcQpaInputMethods) <<
"@@@ SELALL";
290 jboolean res = JNI_FALSE;
297 if (!hasValidFocusObject())
300 qCDebug(lcQpaInputMethods) <<
"@@@";
301 jboolean res = JNI_FALSE;
308 if (!hasValidFocusObject())
311 qCDebug(lcQpaInputMethods) <<
"@@@";
312 jboolean res = JNI_FALSE;
319 if (!hasValidFocusObject())
322 qCDebug(lcQpaInputMethods) <<
"@@@";
323 jboolean res = JNI_FALSE;
330 if (!hasValidFocusObject())
333 qCDebug(lcQpaInputMethods) <<
"@@@ PASTE";
334 jboolean res = JNI_FALSE;
341 if (!hasValidFocusObject())
344 qCDebug(lcQpaInputMethods) <<
"@@@ UPDATECURSORPOS";
367 {
"beginBatchEdit",
"()Z", (
void *)beginBatchEdit},
368 {
"endBatchEdit",
"()Z", (
void *)endBatchEdit},
369 {
"commitText",
"(Ljava/lang/String;I)Z", (
void *)commitText},
370 {
"deleteSurroundingText",
"(II)Z", (
void *)deleteSurroundingText},
371 {
"finishComposingText",
"()Z", (
void *)finishComposingText},
372 {
"getCursorCapsMode",
"(I)I", (
void *)getCursorCapsMode},
373 {
"getExtractedText",
"(III)Lorg/qtproject/qt/android/QtExtractedText;", (
void *)getExtractedText},
374 {
"getSelectedText",
"(I)Ljava/lang/String;", (
void *)getSelectedText},
375 {
"getTextAfterCursor",
"(II)Ljava/lang/String;", (
void *)getTextAfterCursor},
376 {
"getTextBeforeCursor",
"(II)Ljava/lang/String;", (
void *)getTextBeforeCursor},
377 {
"replaceText",
"(IILjava/lang/String;I)Z", (
void *)replaceText},
378 {
"setComposingText",
"(Ljava/lang/String;I)Z", (
void *)setComposingText},
379 {
"setComposingRegion",
"(II)Z", (
void *)setComposingRegion},
380 {
"setSelection",
"(II)Z", (
void *)setSelection},
381 {
"selectAll",
"()Z", (
void *)selectAll},
382 {
"cut",
"()Z", (
void *)cut},
383 {
"copy",
"()Z", (
void *)copy},
384 {
"copyURL",
"()Z", (
void *)copyURL},
385 {
"paste",
"()Z", (
void *)paste},
386 {
"updateCursorPosition",
"()Z", (
void *)updateCursorPosition},
387 {
"reportFullscreenMode",
"(Z)V", (
void *)reportFullscreenMode},
388 {
"fullscreenMode",
"()Z", (
void *)fullscreenMode}
393 QRect windowRect = QPlatformInputContext::inputItemRectangle().toRect();
394 QPlatformWindow *window =
qGuiApp->focusWindow()->handle();
395 return QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
400 , m_composingTextStart(-1)
401 , m_composingCursor(-1)
403 , m_batchEditNestingLevel(0)
405 , m_fullScreenMode(
false)
409 if (Q_UNLIKELY(!clazz)) {
410 qCritical() <<
"Native registration unable to find class '"
416 if (Q_UNLIKELY(env->RegisterNatives(clazz, methods,
sizeof(
methods) /
sizeof(
methods[0])) < 0)) {
417 qCritical() <<
"RegisterNatives failed for '"
424 if (Q_UNLIKELY(!clazz)) {
425 qCritical() <<
"Native registration unable to find class '"
433 if (Q_UNLIKELY(!m_classConstructorMethodID)) {
434 qCritical(
"GetMethodID failed");
439 if (Q_UNLIKELY(!m_partialEndOffsetFieldID)) {
440 qCritical(
"Can't find field partialEndOffset");
445 if (Q_UNLIKELY(!m_partialStartOffsetFieldID)) {
446 qCritical(
"Can't find field partialStartOffset");
451 if (Q_UNLIKELY(!m_selectionEndFieldID)) {
452 qCritical(
"Can't find field selectionEnd");
457 if (Q_UNLIKELY(!m_selectionStartFieldID)) {
458 qCritical(
"Can't find field selectionStart");
463 if (Q_UNLIKELY(!m_startOffsetFieldID)) {
464 qCritical(
"Can't find field startOffset");
468 m_textFieldID = env->GetFieldID(m_extractedTextClass,
"text",
"Ljava/lang/String;");
469 if (Q_UNLIKELY(!m_textFieldID)) {
470 qCritical(
"Can't find field text");
473 qRegisterMetaType<QInputMethodEvent *>(
"QInputMethodEvent*");
474 qRegisterMetaType<QInputMethodQueryEvent *>(
"QInputMethodQueryEvent*");
477 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
478 this, &QAndroidInputContext::updateSelectionHandles);
479 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::anchorRectangleChanged,
480 this, &QAndroidInputContext::updateSelectionHandles);
481 QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::inputItemClipRectangleChanged,
this, [
this]{
482 auto im =
qGuiApp->inputMethod();
483 if (!im->inputItemClipRectangle().contains(im->anchorRectangle()) ||
484 !im->inputItemClipRectangle().contains(im->cursorRectangle())) {
485 m_handleMode = Hidden;
486 updateSelectionHandles();
489 m_hideCursorHandleTimer.setInterval(4000);
490 m_hideCursorHandleTimer.setSingleShot(
true);
491 m_hideCursorHandleTimer.setTimerType(Qt::VeryCoarseTimer);
492 connect(&m_hideCursorHandleTimer, &QTimer::timeout,
this, [
this]{
493 m_handleMode = Hidden;
518 QVariant absolutePos = query->value(Qt::ImAbsolutePosition);
519 return absolutePos.isValid() ? absolutePos.toInt() : query->value(Qt::ImCursorPosition).toInt();
525 QVariant absolutePos = query->value(Qt::ImAbsolutePosition);
526 return absolutePos.isValid() ? absolutePos.toInt() - query->value(Qt::ImCursorPosition).toInt() : 0;
531 focusObjectStopComposing();
533 m_batchEditNestingLevel = 0;
534 m_handleMode = Hidden;
536 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(Qt::ImEnabled);
537 if (!query.isNull() && query->value(Qt::ImEnabled).toBool()) {
547 focusObjectStopComposing();
552 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
553 if (!query.isNull() && m_batchEditNestingLevel == 0) {
554 const int cursorPos = getAbsoluteCursorPosition(query);
555 const int composeLength = m_composingText.length();
558 if (m_composingText.isEmpty() != (m_composingTextStart == -1))
559 qWarning() <<
"Input method out of sync" << m_composingText << m_composingTextStart;
561 int realSelectionStart = cursorPos;
562 int realSelectionEnd = cursorPos;
564 int cpos = query->value(Qt::ImCursorPosition).toInt();
565 int anchor = query->value(Qt::ImAnchorPosition).toInt();
566 if (cpos != anchor) {
567 if (!m_composingText.isEmpty()) {
568 qWarning(
"Selecting text while preediting may give unpredictable results.");
569 focusObjectStopComposing();
571 int blockPos = getBlockPosition(query);
572 realSelectionStart = blockPos + cpos;
573 realSelectionEnd = blockPos + anchor;
576 if (focusObjectIsComposing())
577 realSelectionStart = realSelectionEnd = m_composingCursor;
580 if (realSelectionStart > realSelectionEnd)
581 std::swap(realSelectionStart, realSelectionEnd);
584 m_composingTextStart
, m_composingTextStart + composeLength
);
590 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
593 return query->value(Qt::ImHints).toUInt() & Qt::ImhNoTextHandles;
598 if (m_fullScreenMode) {
602 static bool noHandles = qEnvironmentVariableIntValue(
"QT_QPA_NO_TEXT_HANDLES");
603 if (noHandles || !m_focusObject)
606 if (isImhNoTextHandlesSet()) {
611 auto im =
qGuiApp->inputMethod();
613 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled
614 | Qt::ImCurrentSelection | Qt::ImHints | Qt::ImSurroundingText
616 QCoreApplication::sendEvent(m_focusObject, &query);
618 int cpos = query.value(Qt::ImCursorPosition).toInt();
619 int anchor = query.value(Qt::ImAnchorPosition).toInt();
620 const QVariant readOnlyVariant = query.value(Qt::ImReadOnly);
621 bool readOnly = readOnlyVariant.toBool();
622 QPlatformWindow *qPlatformWindow =
qGuiApp->focusWindow()->handle();
624 if (!readOnly && ((m_handleMode & 0xff) == Hidden)) {
629 if ( cpos == anchor && (!readOnlyVariant.isValid() || readOnly)) {
634 if (cpos == anchor || im->anchorRectangle().isNull()) {
635 auto curRect = cursorRectangle();
636 QPoint cursorPointGlobal = qPlatformWindow->mapToGlobal(
637 QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()));
638 QPoint cursorPoint(curRect.center().x(), curRect.bottom());
643 if (cursorPointGlobal != cursorPoint) {
644 x = cursorPointGlobal.x();
645 y = cursorPointGlobal.y();
648 QPoint editMenuPoint(x, y);
649 m_handleMode &= ShowEditPopup;
650 m_handleMode |= ShowCursor;
652 if (!query.value(Qt::ImSurroundingText).toString().isEmpty())
654 QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPointGlobal);
655 m_hideCursorHandleTimer.start();
660 m_handleMode = ShowSelection | ShowEditPopup ;
661 auto leftRect = cursorRectangle();
662 auto rightRect = anchorRectangle();
664 std::swap(leftRect, rightRect);
668 QPoint leftPoint(qPlatformWindow->mapToGlobal(leftRect.bottomLeft().toPoint()));
669 QPoint rightPoint(qPlatformWindow->mapToGlobal(rightRect.bottomRight().toPoint()));
672 if (platformIntegration) {
676 int rightSideOfScreen = platformIntegration
->screen()->availableGeometry().right();
683 QPoint editPoint(qPlatformWindow->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint()));
687 QtAndroidInput::updateHandles(m_handleMode, editPoint, buttons, leftPoint, rightPoint,
688 query.value(Qt::ImCurrentSelection).toString().isRightToLeft());
689 m_hideCursorHandleTimer.stop();
694
695
696
697
700 if (m_batchEditNestingLevel != 0) {
701 qWarning() <<
"QAndroidInputContext::handleLocationChanged returned";
707 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition
708 | Qt::ImAbsolutePosition | Qt::ImCurrentSelection);
709 QCoreApplication::sendEvent(m_focusObject, &query);
710 int cpos = query.value(Qt::ImCursorPosition).toInt();
711 int anchor = query.value(Qt::ImAnchorPosition).toInt();
712 auto leftRect = cursorRectangle();
713 auto rightRect = anchorRectangle();
715 std::swap(leftRect, rightRect);
718 if (handleId == 2 && point.y() > rightRect.center().y()) {
719 point.setY(rightRect.center().y());
720 }
else if (handleId == 3 && point.y() < leftRect.center().y()) {
721 point.setY(leftRect.center().y());
725 auto object = m_focusObject->parent();
728 if (QString::compare(object->metaObject()->className(),
729 "QDialog", Qt::CaseInsensitive) == 0) {
730 dialogMoveX += object->property(
"x").toInt();
732 object = object->parent();
736 QPointF(QHighDpi::fromNativePixels(point, QGuiApplication::focusWindow()));
737 const QPointF fixedPosition = QPointF(position.x() - dialogMoveX, position.y());
738 const QInputMethod *im = QGuiApplication::inputMethod();
739 const QTransform mapToLocal = im->inputItemTransform().inverted();
740 const int handlePos = im->queryFocusObject(Qt::ImCursorPosition, mapToLocal.map(fixedPosition)).toInt(&ok);
746 int newAnchor = anchor;
747 if (newAnchor > newCpos)
748 std::swap(newAnchor, newCpos);
752 newAnchor = handlePos;
753 }
else if (handleId == 2) {
754 newAnchor = handlePos;
755 }
else if (handleId == 3) {
760
761
762
763
764 if ((handleId == 2 || handleId == 3) && newCpos <= newAnchor) {
765 QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme,
766 query.value(Qt::ImCurrentSelection).toString());
768 const int oldSelectionStartPos = qMin(cpos, anchor);
772 finder.toPreviousBoundary();
773 newAnchor = finder.position() + oldSelectionStartPos;
776 finder.toNextBoundary();
777 newCpos = finder.position() + oldSelectionStartPos;
782 if (!focusObjectIsComposing() && newCpos == cpos && newAnchor == anchor)
786
787
788
789
790 if (focusObjectIsComposing() && handleId == 1) {
791 int absoluteCpos = query.value(Qt::ImAbsolutePosition).toInt(&ok);
794 const int blockPos = absoluteCpos - cpos;
796 if (blockPos + newCpos == m_composingCursor)
800 BatchEditLock batchEditLock(
this);
802 focusObjectStopComposing();
804 QList<QInputMethodEvent::Attribute> attributes;
805 attributes.append({ QInputMethodEvent::Selection, newAnchor, newCpos - newAnchor });
806 if (newCpos != newAnchor)
807 attributes.append({ QInputMethodEvent::Cursor, 0, 0 });
810 QGuiApplication::sendEvent(m_focusObject, &event);
815 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
817 m_handleMode = ShowCursor;
819 m_hideCursorHandleTimer.stop();
821 if (focusObjectIsComposing()) {
822 const int curBlockPos = getBlockPosition(
823 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
824 const int touchPosition = curBlockPos
825 + queryFocusObject(Qt::ImCursorPosition, QPointF(x, y)).toInt();
826 if (touchPosition != m_composingCursor)
827 focusObjectStopComposing();
831 QPlatformWindow *window =
qGuiApp->focusWindow()->handle();
832 const QRectF curRect = cursorRectangle();
833 const QPoint cursorGlobalPoint = window->mapToGlobal(QPoint(curRect.x(), curRect.y()));
834 const QRect windowRect = QPlatformInputContext::inputItemClipRectangle().toRect();
835 const QRect windowGlobalRect = QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
837 if (windowGlobalRect.contains(cursorGlobalPoint.x(), cursorGlobalPoint.y()))
844 static bool noHandles = qEnvironmentVariableIntValue(
"QT_QPA_NO_TEXT_HANDLES");
848 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
849 BatchEditLock batchEditLock(
this);
851 focusObjectStopComposing();
852 const QPointF touchPoint(x, y);
853 setSelectionOnFocusObject(touchPoint, touchPoint);
855 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor);
856 QCoreApplication::sendEvent(m_focusObject, &query);
857 int cursor = query.value(Qt::ImCursorPosition).toInt();
859 QString before = query.value(Qt::ImTextBeforeCursor).toString();
860 QString after = query.value(Qt::ImTextAfterCursor).toString();
861 for (
const auto &ch : after) {
862 if (!ch.isLetterOrNumber())
867 for (
auto itch = before.rbegin(); itch != after.rend(); ++itch) {
868 if (!itch->isLetterOrNumber())
872 if (cursor == anchor || cursor < 0 || cursor - anchor > 500) {
873 m_handleMode = ShowCursor | ShowEditPopup;
877 QList<QInputMethodEvent::Attribute> imAttributes;
878 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
879 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, anchor, cursor - anchor, QVariant()));
881 QGuiApplication::sendEvent(m_focusObject, &event);
883 m_handleMode = ShowSelection | ShowEditPopup;
892 m_handleMode = Hidden;
899 if (m_handleMode & ShowSelection) {
900 m_handleMode = Hidden;
903 m_hideCursorHandleTimer.start();
909 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(queries);
912#warning TODO extract the needed data from query
917#warning TODO Handle at least QInputMethod::ContextMenu action
919 Q_UNUSED(cursorPosition);
927 return QtAndroidInput::softwareKeyboardRect();
937 if (QGuiApplication::applicationState() != Qt::ApplicationActive) {
938 connect(
qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
this, SLOT(showInputPanelLater(Qt::ApplicationState)));
941 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
945 if (!
qGuiApp->focusWindow()->handle())
948 disconnect(m_updateCursorPosConnection);
949 m_updateCursorPosConnection = {};
951 if (
qGuiApp->focusObject()->metaObject()->indexOfSignal(
"cursorPositionChanged(int,int)") >= 0)
952 m_updateCursorPosConnection = connect(
qGuiApp->focusObject(), SIGNAL(cursorPositionChanged(
int,
int)),
this, SLOT(updateCursorPosition()));
953 else if (
qGuiApp->focusObject()->metaObject()->indexOfSignal(
"cursorPositionChanged()") >= 0)
954 m_updateCursorPosConnection = connect(
qGuiApp->focusObject(), SIGNAL(cursorPositionChanged()),
this, SLOT(updateCursorPosition()));
956 QRect rect = screenInputItemRectangle();
958 query->value(Qt::ImHints).toUInt()
,
959 query->value(Qt::ImEnterKeyType).toUInt()
);
964 if (state != Qt::ApplicationActive)
966 disconnect(
qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
this, SLOT(showInputPanelLater(Qt::ApplicationState)));
972 if (
qGuiApp->thread() == QThread::currentThread())
975 QMetaObject::invokeMethod(
this,
"safeCall", conType, Q_ARG(std::function<
void()>, func));
990 return m_composingText.length();
995 m_composingText.clear();
996 m_composingTextStart = -1;
997 m_composingCursor = -1;
998 m_extractedText.clear();
1004 return m_focusObject;
1009 if (object != m_focusObject) {
1010 focusObjectStopComposing();
1011 m_focusObject = object;
1019 ++m_batchEditNestingLevel;
1025 if (--m_batchEditNestingLevel == 0) {
1026 focusObjectStartComposing();
1033
1034
1035
1038 BatchEditLock batchEditLock(
this);
1039 return setComposingText(text, newCursorPosition) && finishComposingText();
1044 BatchEditLock batchEditLock(
this);
1046 focusObjectStopComposing();
1048 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1052 if (leftLength < 0) {
1053 rightLength += -leftLength;
1057 const int initialBlockPos = getBlockPosition(query);
1058 const int initialCursorPos = getAbsoluteCursorPosition(query);
1059 const int initialAnchorPos = initialBlockPos + query->value(Qt::ImAnchorPosition).toInt();
1062
1063
1064
1065
1066
1067
1068
1069
1071 m_composingText.isEmpty()
1072 ? qMin(initialCursorPos, initialAnchorPos)
1073 : qMin(qMin(initialCursorPos, initialAnchorPos), m_composingTextStart);
1075 const int rightBegin =
1076 m_composingText.isEmpty()
1077 ? qMax(initialCursorPos, initialAnchorPos)
1078 : qMax(qMax(initialCursorPos, initialAnchorPos),
1079 m_composingTextStart + m_composingText.length());
1081 int textBeforeCursorLen;
1082 int textAfterCursorLen;
1084 QVariant textBeforeCursor = query->value(Qt::ImTextBeforeCursor);
1085 QVariant textAfterCursor = query->value(Qt::ImTextAfterCursor);
1086 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1087 textBeforeCursorLen = textBeforeCursor.toString().length();
1088 textAfterCursorLen = textAfterCursor.toString().length();
1090 textBeforeCursorLen = initialCursorPos - initialBlockPos;
1091 textAfterCursorLen =
1092 query->value(Qt::ImSurroundingText).toString().length() - textBeforeCursorLen;
1095 leftLength = qMin(qMax(0, textBeforeCursorLen - (initialCursorPos - leftEnd)), leftLength);
1096 rightLength = qMin(qMax(0, textAfterCursorLen - (rightBegin - initialCursorPos)), rightLength);
1098 if (leftLength == 0 && rightLength == 0)
1101 if (leftEnd == rightBegin) {
1104 event.setCommitString({}, -leftLength, leftLength + rightLength);
1105 QGuiApplication::sendEvent(m_focusObject, &event);
1107 if (initialCursorPos != initialAnchorPos) {
1109 { QInputMethodEvent::Selection, initialCursorPos - initialBlockPos, 0 }
1112 QGuiApplication::sendEvent(m_focusObject, &event);
1115 int currentCursorPos = initialCursorPos;
1117 if (rightLength > 0) {
1119 event.setCommitString({}, rightBegin - currentCursorPos, rightLength);
1120 QGuiApplication::sendEvent(m_focusObject, &event);
1122 currentCursorPos = rightBegin;
1125 if (leftLength > 0) {
1126 const int leftBegin = leftEnd - leftLength;
1129 event.setCommitString({}, leftBegin - currentCursorPos, leftLength);
1130 QGuiApplication::sendEvent(m_focusObject, &event);
1132 currentCursorPos = leftBegin;
1134 if (!m_composingText.isEmpty())
1135 m_composingTextStart -= leftLength;
1139 if (currentCursorPos != initialCursorPos - leftLength
1140 || initialCursorPos != initialAnchorPos) {
1142 const int currentBlockPos = getBlockPosition(
1143 focusObjectInputMethodQuery(Qt::ImAbsolutePosition | Qt::ImCursorPosition));
1146 { QInputMethodEvent::Selection, initialCursorPos - leftLength - currentBlockPos,
1147 initialAnchorPos - initialCursorPos },
1148 { QInputMethodEvent::Cursor, 0, 0 }
1151 QGuiApplication::sendEvent(m_focusObject, &event);
1161 BatchEditLock batchEditLock(
this);
1163 if (!focusObjectStopComposing())
1171
1172
1173
1174
1177 if (!finishComposingText())
1179 if (!setSelection(start, end))
1182 return commitText(text, newCursorPosition);
1187 m_fullScreenMode = enabled;
1188 BatchEditLock batchEditLock(
this);
1189 if (!focusObjectStopComposing())
1193 m_handleMode = Hidden;
1201 return m_fullScreenMode;
1206 return m_composingCursor != -1;
1211 if (focusObjectIsComposing() || m_composingText.isEmpty())
1215 if (m_composingText.contains(u'\n'))
1218 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1222 if (query->value(Qt::ImCursorPosition).toInt() != query->value(Qt::ImAnchorPosition).toInt())
1225 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1226 if (absoluteCursorPos < m_composingTextStart
1227 || absoluteCursorPos > m_composingTextStart + m_composingText.length())
1230 m_composingCursor = absoluteCursorPos;
1232 QTextCharFormat underlined;
1233 underlined.setFontUnderline(
true);
1236 { QInputMethodEvent::Cursor, absoluteCursorPos - m_composingTextStart, 1 },
1237 { QInputMethodEvent::TextFormat, 0,
int(m_composingText.length()), underlined }
1240 event.setCommitString({}, m_composingTextStart - absoluteCursorPos, m_composingText.length());
1242 QGuiApplication::sendEvent(m_focusObject, &event);
1247 if (!focusObjectIsComposing())
1250 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1254 const int blockPos = getBlockPosition(query);
1255 const int localCursorPos = m_composingCursor - blockPos;
1257 m_composingCursor = -1;
1261 QList<QInputMethodEvent::Attribute> attributes;
1263 event.setCommitString(m_composingText);
1264 sendInputMethodEvent(&event);
1268 QList<QInputMethodEvent::Attribute> attributes;
1270 QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0));
1272 sendInputMethodEvent(&event);
1281 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1285 const uint qtInputMethodHints = query->value(Qt::ImHints).toUInt();
1286 const int localPos = query->value(Qt::ImCursorPosition).toInt();
1288 bool atWordBoundary =
1290 && (!focusObjectIsComposing() || m_composingCursor == m_composingTextStart);
1292 if (!atWordBoundary) {
1293 QString surroundingText = query->value(Qt::ImSurroundingText).toString();
1294 surroundingText.truncate(localPos);
1295 if (focusObjectIsComposing())
1296 surroundingText += QStringView{m_composingText}.left(m_composingCursor - m_composingTextStart);
1298 QTextBoundaryFinder finder(QTextBoundaryFinder::Sentence, surroundingText + u'A');
1299 finder.setPosition(surroundingText.length());
1300 if (finder.isAtBoundary())
1301 atWordBoundary = finder.isAtBoundary();
1303 if (atWordBoundary && !(qtInputMethodHints & Qt::ImhLowercaseOnly) && !(qtInputMethodHints & Qt::ImhNoAutoUppercase))
1304 res |= CAP_MODE_SENTENCES;
1306 if (qtInputMethodHints & Qt::ImhUppercaseOnly)
1307 res |= CAP_MODE_CHARACTERS;
1320 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(
1321 Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition);
1323 return m_extractedText;
1325 const int cursorPos = getAbsoluteCursorPosition(query);
1326 const int blockPos = getBlockPosition(query);
1333 QVariant textBeforeCursor = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, INT_MAX);
1334 QVariant textAfterCursor = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, INT_MAX);
1335 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1336 if (focusObjectIsComposing()) {
1337 m_extractedText.text =
1338 textBeforeCursor.toString() + m_composingText + textAfterCursor.toString();
1340 m_extractedText.text = textBeforeCursor.toString() + textAfterCursor.toString();
1343 m_extractedText.startOffset = qMax(0, cursorPos - textBeforeCursor.toString().length());
1345 m_extractedText.text = focusObjectInputMethodQuery(Qt::ImSurroundingText)
1346 ->value(Qt::ImSurroundingText).toString();
1348 if (focusObjectIsComposing())
1349 m_extractedText.text.insert(cursorPos - blockPos, m_composingText);
1351 m_extractedText.startOffset = blockPos;
1354 if (focusObjectIsComposing()) {
1355 m_extractedText.selectionStart = m_composingCursor - m_extractedText.startOffset;
1356 m_extractedText.selectionEnd = m_extractedText.selectionStart;
1358 m_extractedText.selectionStart = cursorPos - m_extractedText.startOffset;
1359 m_extractedText.selectionEnd =
1360 blockPos + query->value(Qt::ImAnchorPosition).toInt() - m_extractedText.startOffset;
1363 if (m_extractedText.selectionStart > m_extractedText.selectionEnd)
1364 std::swap(m_extractedText.selectionStart, m_extractedText.selectionEnd);
1367 return m_extractedText;
1372 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1376 return query->value(Qt::ImCurrentSelection).toString();
1386 QVariant reportedTextAfter = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, length);
1387 if (reportedTextAfter.isValid()) {
1388 text = reportedTextAfter.toString();
1391 QSharedPointer<QInputMethodQueryEvent> query =
1392 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1394 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1395 text = query->value(Qt::ImSurroundingText).toString().mid(cursorPos);
1399 if (focusObjectIsComposing()) {
1401 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1402 text = QStringView{m_composingText}.mid(cursorPosInsidePreedit) + text;
1405 QSharedPointer<QInputMethodQueryEvent> query =
1406 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1408 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1409 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1410 if (anchorPos > cursorPos)
1411 text.remove(0, anchorPos - cursorPos);
1415 text.truncate(length);
1426 QVariant reportedTextBefore = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, length);
1427 if (reportedTextBefore.isValid()) {
1428 text = reportedTextBefore.toString();
1431 QSharedPointer<QInputMethodQueryEvent> query =
1432 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1434 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1435 text = query->value(Qt::ImSurroundingText).toString().left(cursorPos);
1439 if (focusObjectIsComposing()) {
1441 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1442 text += QStringView{m_composingText}.left(cursorPosInsidePreedit);
1445 QSharedPointer<QInputMethodQueryEvent> query =
1446 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1448 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1449 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1450 if (anchorPos < cursorPos)
1451 text.chop(cursorPos - anchorPos);
1455 if (text.length() > length)
1456 text = text.right(length);
1461
1462
1463
1464
1465
1466
1467
1468
1472 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1476 BatchEditLock batchEditLock(
this);
1478 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1479 int absoluteAnchorPos = getBlockPosition(query) + query->value(Qt::ImAnchorPosition).toInt();
1481 auto setCursorPosition = [=]() {
1482 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1483 QInputMethodEvent event({}, { { QInputMethodEvent::Selection, cursorPos, 0 } });
1484 QGuiApplication::sendEvent(m_focusObject, &event);
1489 if (!m_composingText.isEmpty() && absoluteCursorPos != absoluteAnchorPos) {
1490 setCursorPosition();
1491 absoluteAnchorPos = absoluteCursorPos;
1496 if (absoluteCursorPos == 0 && text.length() == 1 && getTextAfterCursor(1,1).length() >= 0) {
1497 setCursorPosition();
1503 const int effectiveAbsoluteCursorPos = qMin(absoluteCursorPos, absoluteAnchorPos);
1504 if (m_composingTextStart == -1)
1505 m_composingTextStart = effectiveAbsoluteCursorPos;
1507 const int oldComposingTextLen = m_composingText.length();
1508 m_composingText = text;
1510 const int newAbsoluteCursorPos =
1511 newCursorPosition <= 0
1512 ? m_composingTextStart + newCursorPosition
1513 : m_composingTextStart + m_composingText.length() + newCursorPosition - 1;
1515 const bool focusObjectWasComposing = focusObjectIsComposing();
1518 if (!m_composingText.isEmpty() && !m_composingText.contains(u'\n')
1519 && newAbsoluteCursorPos >= m_composingTextStart
1520 && newAbsoluteCursorPos <= m_composingTextStart + m_composingText.length())
1521 m_composingCursor = newAbsoluteCursorPos;
1523 m_composingCursor = -1;
1525 if (focusObjectIsComposing()) {
1526 QTextCharFormat underlined;
1527 underlined.setFontUnderline(
true);
1530 { QInputMethodEvent::TextFormat, 0,
int(m_composingText.length()), underlined },
1531 { QInputMethodEvent::Cursor, m_composingCursor - m_composingTextStart, 1 }
1534 if (oldComposingTextLen > 0 && !focusObjectWasComposing) {
1535 event.setCommitString({}, m_composingTextStart - effectiveAbsoluteCursorPos,
1536 oldComposingTextLen);
1538 if (m_composingText.isEmpty())
1541 QGuiApplication::sendEvent(m_focusObject, &event);
1545 if (focusObjectWasComposing) {
1546 event.setCommitString(m_composingText);
1548 event.setCommitString(m_composingText,
1549 m_composingTextStart - effectiveAbsoluteCursorPos,
1550 oldComposingTextLen);
1552 if (m_composingText.isEmpty())
1555 QGuiApplication::sendEvent(m_focusObject, &event);
1558 if (!focusObjectIsComposing() && newCursorPosition != 1) {
1562 const int newBlockPos = getBlockPosition(
1563 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
1566 { QInputMethodEvent::Selection, newAbsoluteCursorPos - newBlockPos, 0 }
1569 QGuiApplication::sendEvent(m_focusObject, &event);
1583 BatchEditLock batchEditLock(
this);
1590 finishComposingText();
1592 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1601 QString text = query->value(Qt::ImSurroundingText).toString();
1602 int textOffset = getBlockPosition(query);
1604 if (start < textOffset || end > textOffset + text.length()) {
1605 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1607 if (end - textOffset > text.length()) {
1608 const QString after = query->value(Qt::ImTextAfterCursor).toString();
1609 const int additionalSuffixLen = after.length() - (text.length() - cursorPos);
1611 if (additionalSuffixLen > 0)
1612 text += QStringView{after}.right(additionalSuffixLen);
1615 if (start < textOffset) {
1616 QString before = query->value(Qt::ImTextBeforeCursor).toString();
1617 before.chop(cursorPos);
1619 if (!before.isEmpty()) {
1620 text = before + text;
1621 textOffset -= before.length();
1625 if (start < textOffset || end - textOffset > text.length()) {
1626 qCDebug(lcQpaInputMethods) <<
"Warning: setComposingRegion: failed to retrieve text from composing region";
1632 m_composingText = text.mid(start - textOffset, end - start);
1633 m_composingTextStart = start;
1640 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1644 BatchEditLock batchEditLock(
this);
1646 int blockPosition = getBlockPosition(query);
1647 int localCursorPos = start - blockPosition;
1649 if (focusObjectIsComposing() && start == end && start >= m_composingTextStart
1650 && start <= m_composingTextStart + m_composingText.length()) {
1653 int localOldPos = query->value(Qt::ImCursorPosition).toInt();
1654 int pos = localCursorPos - localOldPos;
1655 QList<QInputMethodEvent::Attribute> attributes;
1656 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, pos, 1));
1661 QTextCharFormat underlined;
1662 underlined.setFontUnderline(
true);
1663 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, m_composingText.length(),
1664 QVariant(underlined)));
1665 m_composingCursor = start;
1668 QGuiApplication::sendEvent(m_focusObject, &event);
1671 focusObjectStopComposing();
1672 QList<QInputMethodEvent::Attribute> attributes;
1673 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection,
1677 QGuiApplication::sendEvent(m_focusObject, &event);
1684 BatchEditLock batchEditLock(
this);
1686 focusObjectStopComposing();
1687 m_handleMode = ShowCursor;
1688 sendShortcut(QKeySequence::SelectAll);
1694 BatchEditLock batchEditLock(
this);
1698 finishComposingText();
1700 m_handleMode = ShowCursor;
1701 sendShortcut(QKeySequence::Cut);
1707 BatchEditLock batchEditLock(
this);
1709 focusObjectStopComposing();
1710 m_handleMode = ShowCursor;
1711 sendShortcut(QKeySequence::Copy);
1723 BatchEditLock batchEditLock(
this);
1726 finishComposingText();
1728 m_handleMode = ShowCursor;
1729 sendShortcut(QKeySequence::Paste);
1735 for (
int i = 0; i < sequence.count(); ++i) {
1736 const QKeyCombination keys = sequence[i];
1737 Qt::Key key = Qt::Key(keys.toCombined() & ~Qt::KeyboardModifierMask);
1738 Qt::KeyboardModifiers mod = Qt::KeyboardModifiers(keys.toCombined() & Qt::KeyboardModifierMask);
1740 QKeyEvent pressEvent(QEvent::KeyPress, key, mod);
1741 QKeyEvent releaseEvent(QEvent::KeyRelease, key, mod);
1743 QGuiApplication::sendEvent(m_focusObject, &pressEvent);
1744 QGuiApplication::sendEvent(m_focusObject, &releaseEvent);
1748QSharedPointer<QInputMethodQueryEvent>
QAndroidInputContext::focusObjectInputMethodQuery(Qt::InputMethodQueries queries) {
1752 QObject *focusObject =
qGuiApp->focusObject();
1756 QInputMethodQueryEvent *ret =
new QInputMethodQueryEvent(queries);
1757 QCoreApplication::sendEvent(focusObject, ret);
1758 return QSharedPointer<QInputMethodQueryEvent>(ret);
1766 QObject *focusObject =
qGuiApp->focusObject();
1770 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)