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 = 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();
678 leftPoint = qPlatformWindow->mapFromGlobal(leftPoint);
682 rightPoint = qPlatformWindow->mapFromGlobal(rightPoint);
684 QPoint editPoint(leftRect.united(rightRect).topLeft().toPoint());
688 QtAndroidInput::updateHandles(m_handleMode, editPoint, buttons, leftPoint, rightPoint,
689 query.value(Qt::ImCurrentSelection).toString().isRightToLeft());
690 m_hideCursorHandleTimer.stop();
695
696
697
698
701 if (m_batchEditNestingLevel != 0) {
702 qWarning() <<
"QAndroidInputContext::handleLocationChanged returned";
708 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition
709 | Qt::ImAbsolutePosition | Qt::ImCurrentSelection);
710 QCoreApplication::sendEvent(m_focusObject, &query);
711 int cpos = query.value(Qt::ImCursorPosition).toInt();
712 int anchor = query.value(Qt::ImAnchorPosition).toInt();
713 auto leftRect = cursorRectangle();
714 auto rightRect = anchorRectangle();
716 std::swap(leftRect, rightRect);
719 if (handleId == 2 && point.y() > rightRect.center().y()) {
720 point.setY(rightRect.center().y());
721 }
else if (handleId == 3 && point.y() < leftRect.center().y()) {
722 point.setY(leftRect.center().y());
726 auto object = m_focusObject->parent();
729 if (QString::compare(object->metaObject()->className(),
730 "QDialog", Qt::CaseInsensitive) == 0) {
731 dialogMoveX += object->property(
"x").toInt();
733 object = object->parent();
737 QPointF(QHighDpi::fromNativePixels(point, QGuiApplication::focusWindow()));
738 const QPointF fixedPosition = QPointF(position.x() - dialogMoveX, position.y());
739 const QInputMethod *im = QGuiApplication::inputMethod();
740 const QTransform mapToLocal = im->inputItemTransform().inverted();
741 const int handlePos = im->queryFocusObject(Qt::ImCursorPosition, mapToLocal.map(fixedPosition)).toInt(&ok);
747 int newAnchor = anchor;
748 if (newAnchor > newCpos)
749 std::swap(newAnchor, newCpos);
753 newAnchor = handlePos;
754 }
else if (handleId == 2) {
755 newAnchor = handlePos;
756 }
else if (handleId == 3) {
761
762
763
764
765 if ((handleId == 2 || handleId == 3) && newCpos <= newAnchor) {
766 QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme,
767 query.value(Qt::ImCurrentSelection).toString());
769 const int oldSelectionStartPos = qMin(cpos, anchor);
773 finder.toPreviousBoundary();
774 newAnchor = finder.position() + oldSelectionStartPos;
777 finder.toNextBoundary();
778 newCpos = finder.position() + oldSelectionStartPos;
783 if (!focusObjectIsComposing() && newCpos == cpos && newAnchor == anchor)
787
788
789
790
791 if (focusObjectIsComposing() && handleId == 1) {
792 int absoluteCpos = query.value(Qt::ImAbsolutePosition).toInt(&ok);
795 const int blockPos = absoluteCpos - cpos;
797 if (blockPos + newCpos == m_composingCursor)
801 BatchEditLock batchEditLock(
this);
803 focusObjectStopComposing();
805 QList<QInputMethodEvent::Attribute> attributes;
806 attributes.append({ QInputMethodEvent::Selection, newAnchor, newCpos - newAnchor });
807 if (newCpos != newAnchor)
808 attributes.append({ QInputMethodEvent::Cursor, 0, 0 });
811 QGuiApplication::sendEvent(m_focusObject, &event);
816 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
818 m_handleMode = ShowCursor;
820 m_hideCursorHandleTimer.stop();
822 if (focusObjectIsComposing()) {
823 const int curBlockPos = getBlockPosition(
824 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
825 const int touchPosition = curBlockPos
826 + queryFocusObject(Qt::ImCursorPosition, QPointF(x, y)).toInt();
827 if (touchPosition != m_composingCursor)
828 focusObjectStopComposing();
832 QPlatformWindow *window =
qGuiApp->focusWindow()->handle();
833 const QRectF curRect = cursorRectangle();
834 const QPoint cursorGlobalPoint = window->mapToGlobal(QPoint(curRect.x(), curRect.y()));
835 const QRect windowRect = QPlatformInputContext::inputItemClipRectangle().toRect();
836 const QRect windowGlobalRect = QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
838 if (windowGlobalRect.contains(cursorGlobalPoint.x(), cursorGlobalPoint.y()))
845 static bool noHandles = qEnvironmentVariableIntValue(
"QT_QPA_NO_TEXT_HANDLES");
849 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
850 BatchEditLock batchEditLock(
this);
852 focusObjectStopComposing();
853 const QPointF touchPoint(x, y);
854 setSelectionOnFocusObject(touchPoint, touchPoint);
856 QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor);
857 QCoreApplication::sendEvent(m_focusObject, &query);
858 int cursor = query.value(Qt::ImCursorPosition).toInt();
860 QString before = query.value(Qt::ImTextBeforeCursor).toString();
861 QString after = query.value(Qt::ImTextAfterCursor).toString();
862 for (
const auto &ch : after) {
863 if (!ch.isLetterOrNumber())
868 for (
auto itch = before.rbegin(); itch != after.rend(); ++itch) {
869 if (!itch->isLetterOrNumber())
873 if (cursor == anchor || cursor < 0 || cursor - anchor > 500) {
874 m_handleMode = ShowCursor | ShowEditPopup;
878 QList<QInputMethodEvent::Attribute> imAttributes;
879 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
880 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, anchor, cursor - anchor, QVariant()));
882 QGuiApplication::sendEvent(m_focusObject, &event);
884 m_handleMode = ShowSelection | ShowEditPopup;
893 m_handleMode = Hidden;
900 if (m_handleMode & ShowSelection) {
901 m_handleMode = Hidden;
904 m_hideCursorHandleTimer.start();
910 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(queries);
913#warning TODO extract the needed data from query
918#warning TODO Handle at least QInputMethod::ContextMenu action
920 Q_UNUSED(cursorPosition);
928 return QtAndroidInput::softwareKeyboardRect();
938 if (QGuiApplication::applicationState() != Qt::ApplicationActive) {
939 connect(
qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
this, SLOT(showInputPanelLater(Qt::ApplicationState)));
942 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
946 if (!
qGuiApp->focusWindow()->handle())
949 disconnect(m_updateCursorPosConnection);
950 m_updateCursorPosConnection = {};
952 if (
qGuiApp->focusObject()->metaObject()->indexOfSignal(
"cursorPositionChanged(int,int)") >= 0)
953 m_updateCursorPosConnection = connect(
qGuiApp->focusObject(), SIGNAL(cursorPositionChanged(
int,
int)),
this, SLOT(updateCursorPosition()));
954 else if (
qGuiApp->focusObject()->metaObject()->indexOfSignal(
"cursorPositionChanged()") >= 0)
955 m_updateCursorPosConnection = connect(
qGuiApp->focusObject(), SIGNAL(cursorPositionChanged()),
this, SLOT(updateCursorPosition()));
957 QRect rect = QPlatformInputContext::inputItemRectangle().toRect();
959 query->value(Qt::ImHints).toUInt()
,
960 query->value(Qt::ImEnterKeyType).toUInt()
);
965 if (state != Qt::ApplicationActive)
967 disconnect(
qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
this, SLOT(showInputPanelLater(Qt::ApplicationState)));
973 if (
qGuiApp->thread() == QThread::currentThread())
976 QMetaObject::invokeMethod(
this,
"safeCall", conType, Q_ARG(std::function<
void()>, func));
991 return m_composingText.length();
996 m_composingText.clear();
997 m_composingTextStart = -1;
998 m_composingCursor = -1;
999 m_extractedText.clear();
1005 return m_focusObject;
1010 if (object != m_focusObject) {
1011 focusObjectStopComposing();
1012 m_focusObject = object;
1020 ++m_batchEditNestingLevel;
1026 if (--m_batchEditNestingLevel == 0) {
1027 focusObjectStartComposing();
1034
1035
1036
1039 BatchEditLock batchEditLock(
this);
1040 return setComposingText(text, newCursorPosition) && finishComposingText();
1045 BatchEditLock batchEditLock(
this);
1047 focusObjectStopComposing();
1049 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1053 if (leftLength < 0) {
1054 rightLength += -leftLength;
1058 const int initialBlockPos = getBlockPosition(query);
1059 const int initialCursorPos = getAbsoluteCursorPosition(query);
1060 const int initialAnchorPos = initialBlockPos + query->value(Qt::ImAnchorPosition).toInt();
1063
1064
1065
1066
1067
1068
1069
1070
1072 m_composingText.isEmpty()
1073 ? qMin(initialCursorPos, initialAnchorPos)
1074 : qMin(qMin(initialCursorPos, initialAnchorPos), m_composingTextStart);
1076 const int rightBegin =
1077 m_composingText.isEmpty()
1078 ? qMax(initialCursorPos, initialAnchorPos)
1079 : qMax(qMax(initialCursorPos, initialAnchorPos),
1080 m_composingTextStart + m_composingText.length());
1082 int textBeforeCursorLen;
1083 int textAfterCursorLen;
1085 QVariant textBeforeCursor = query->value(Qt::ImTextBeforeCursor);
1086 QVariant textAfterCursor = query->value(Qt::ImTextAfterCursor);
1087 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1088 textBeforeCursorLen = textBeforeCursor.toString().length();
1089 textAfterCursorLen = textAfterCursor.toString().length();
1091 textBeforeCursorLen = initialCursorPos - initialBlockPos;
1092 textAfterCursorLen =
1093 query->value(Qt::ImSurroundingText).toString().length() - textBeforeCursorLen;
1096 leftLength = qMin(qMax(0, textBeforeCursorLen - (initialCursorPos - leftEnd)), leftLength);
1097 rightLength = qMin(qMax(0, textAfterCursorLen - (rightBegin - initialCursorPos)), rightLength);
1099 if (leftLength == 0 && rightLength == 0)
1102 if (leftEnd == rightBegin) {
1105 event.setCommitString({}, -leftLength, leftLength + rightLength);
1106 QGuiApplication::sendEvent(m_focusObject, &event);
1108 if (initialCursorPos != initialAnchorPos) {
1110 { QInputMethodEvent::Selection, initialCursorPos - initialBlockPos, 0 }
1113 QGuiApplication::sendEvent(m_focusObject, &event);
1116 int currentCursorPos = initialCursorPos;
1118 if (rightLength > 0) {
1120 event.setCommitString({}, rightBegin - currentCursorPos, rightLength);
1121 QGuiApplication::sendEvent(m_focusObject, &event);
1123 currentCursorPos = rightBegin;
1126 if (leftLength > 0) {
1127 const int leftBegin = leftEnd - leftLength;
1130 event.setCommitString({}, leftBegin - currentCursorPos, leftLength);
1131 QGuiApplication::sendEvent(m_focusObject, &event);
1133 currentCursorPos = leftBegin;
1135 if (!m_composingText.isEmpty())
1136 m_composingTextStart -= leftLength;
1140 if (currentCursorPos != initialCursorPos - leftLength
1141 || initialCursorPos != initialAnchorPos) {
1143 const int currentBlockPos = getBlockPosition(
1144 focusObjectInputMethodQuery(Qt::ImAbsolutePosition | Qt::ImCursorPosition));
1147 { QInputMethodEvent::Selection, initialCursorPos - leftLength - currentBlockPos,
1148 initialAnchorPos - initialCursorPos },
1149 { QInputMethodEvent::Cursor, 0, 0 }
1152 QGuiApplication::sendEvent(m_focusObject, &event);
1162 BatchEditLock batchEditLock(
this);
1164 if (!focusObjectStopComposing())
1172
1173
1174
1175
1178 if (!finishComposingText())
1180 if (!setSelection(start, end))
1183 return commitText(text, newCursorPosition);
1188 m_fullScreenMode = enabled;
1189 BatchEditLock batchEditLock(
this);
1190 if (!focusObjectStopComposing())
1194 m_handleMode = Hidden;
1202 return m_fullScreenMode;
1207 return m_composingCursor != -1;
1212 if (focusObjectIsComposing() || m_composingText.isEmpty())
1216 if (m_composingText.contains(u'\n'))
1219 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1223 if (query->value(Qt::ImCursorPosition).toInt() != query->value(Qt::ImAnchorPosition).toInt())
1226 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1227 if (absoluteCursorPos < m_composingTextStart
1228 || absoluteCursorPos > m_composingTextStart + m_composingText.length())
1231 m_composingCursor = absoluteCursorPos;
1233 QTextCharFormat underlined;
1234 underlined.setFontUnderline(
true);
1237 { QInputMethodEvent::Cursor, absoluteCursorPos - m_composingTextStart, 1 },
1238 { QInputMethodEvent::TextFormat, 0,
int(m_composingText.length()), underlined }
1241 event.setCommitString({}, m_composingTextStart - absoluteCursorPos, m_composingText.length());
1243 QGuiApplication::sendEvent(m_focusObject, &event);
1248 if (!focusObjectIsComposing())
1251 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1255 const int blockPos = getBlockPosition(query);
1256 const int localCursorPos = m_composingCursor - blockPos;
1258 m_composingCursor = -1;
1262 QList<QInputMethodEvent::Attribute> attributes;
1264 event.setCommitString(m_composingText);
1265 sendInputMethodEvent(&event);
1269 QList<QInputMethodEvent::Attribute> attributes;
1271 QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0));
1273 sendInputMethodEvent(&event);
1282 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1286 const uint qtInputMethodHints = query->value(Qt::ImHints).toUInt();
1287 const int localPos = query->value(Qt::ImCursorPosition).toInt();
1289 bool atWordBoundary =
1291 && (!focusObjectIsComposing() || m_composingCursor == m_composingTextStart);
1293 if (!atWordBoundary) {
1294 QString surroundingText = query->value(Qt::ImSurroundingText).toString();
1295 surroundingText.truncate(localPos);
1296 if (focusObjectIsComposing())
1297 surroundingText += QStringView{m_composingText}.left(m_composingCursor - m_composingTextStart);
1299 QTextBoundaryFinder finder(QTextBoundaryFinder::Sentence, surroundingText + u'A');
1300 finder.setPosition(surroundingText.length());
1301 if (finder.isAtBoundary())
1302 atWordBoundary = finder.isAtBoundary();
1304 if (atWordBoundary && !(qtInputMethodHints & Qt::ImhLowercaseOnly) && !(qtInputMethodHints & Qt::ImhNoAutoUppercase))
1305 res |= CAP_MODE_SENTENCES;
1307 if (qtInputMethodHints & Qt::ImhUppercaseOnly)
1308 res |= CAP_MODE_CHARACTERS;
1321 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(
1322 Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition);
1324 return m_extractedText;
1326 const int cursorPos = getAbsoluteCursorPosition(query);
1327 const int blockPos = getBlockPosition(query);
1334 QVariant textBeforeCursor = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, INT_MAX);
1335 QVariant textAfterCursor = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, INT_MAX);
1336 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1337 if (focusObjectIsComposing()) {
1338 m_extractedText.text =
1339 textBeforeCursor.toString() + m_composingText + textAfterCursor.toString();
1341 m_extractedText.text = textBeforeCursor.toString() + textAfterCursor.toString();
1344 m_extractedText.startOffset = qMax(0, cursorPos - textBeforeCursor.toString().length());
1346 m_extractedText.text = focusObjectInputMethodQuery(Qt::ImSurroundingText)
1347 ->value(Qt::ImSurroundingText).toString();
1349 if (focusObjectIsComposing())
1350 m_extractedText.text.insert(cursorPos - blockPos, m_composingText);
1352 m_extractedText.startOffset = blockPos;
1355 if (focusObjectIsComposing()) {
1356 m_extractedText.selectionStart = m_composingCursor - m_extractedText.startOffset;
1357 m_extractedText.selectionEnd = m_extractedText.selectionStart;
1359 m_extractedText.selectionStart = cursorPos - m_extractedText.startOffset;
1360 m_extractedText.selectionEnd =
1361 blockPos + query->value(Qt::ImAnchorPosition).toInt() - m_extractedText.startOffset;
1364 if (m_extractedText.selectionStart > m_extractedText.selectionEnd)
1365 std::swap(m_extractedText.selectionStart, m_extractedText.selectionEnd);
1368 return m_extractedText;
1373 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1377 return query->value(Qt::ImCurrentSelection).toString();
1387 QVariant reportedTextAfter = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, length);
1388 if (reportedTextAfter.isValid()) {
1389 text = reportedTextAfter.toString();
1392 QSharedPointer<QInputMethodQueryEvent> query =
1393 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1395 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1396 text = query->value(Qt::ImSurroundingText).toString().mid(cursorPos);
1400 if (focusObjectIsComposing()) {
1402 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1403 text = QStringView{m_composingText}.mid(cursorPosInsidePreedit) + text;
1406 QSharedPointer<QInputMethodQueryEvent> query =
1407 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1409 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1410 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1411 if (anchorPos > cursorPos)
1412 text.remove(0, anchorPos - cursorPos);
1416 text.truncate(length);
1427 QVariant reportedTextBefore = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, length);
1428 if (reportedTextBefore.isValid()) {
1429 text = reportedTextBefore.toString();
1432 QSharedPointer<QInputMethodQueryEvent> query =
1433 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1435 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1436 text = query->value(Qt::ImSurroundingText).toString().left(cursorPos);
1440 if (focusObjectIsComposing()) {
1442 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1443 text += QStringView{m_composingText}.left(cursorPosInsidePreedit);
1446 QSharedPointer<QInputMethodQueryEvent> query =
1447 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1449 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1450 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1451 if (anchorPos < cursorPos)
1452 text.chop(cursorPos - anchorPos);
1456 if (text.length() > length)
1457 text = text.right(length);
1462
1463
1464
1465
1466
1467
1468
1469
1473 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1477 BatchEditLock batchEditLock(
this);
1479 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1480 int absoluteAnchorPos = getBlockPosition(query) + query->value(Qt::ImAnchorPosition).toInt();
1482 auto setCursorPosition = [=]() {
1483 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1484 QInputMethodEvent event({}, { { QInputMethodEvent::Selection, cursorPos, 0 } });
1485 QGuiApplication::sendEvent(m_focusObject, &event);
1490 if (!m_composingText.isEmpty() && absoluteCursorPos != absoluteAnchorPos) {
1491 setCursorPosition();
1492 absoluteAnchorPos = absoluteCursorPos;
1497 if (absoluteCursorPos == 0 && text.length() == 1 && getTextAfterCursor(1,1).length() >= 0) {
1498 setCursorPosition();
1504 const int effectiveAbsoluteCursorPos = qMin(absoluteCursorPos, absoluteAnchorPos);
1505 if (m_composingTextStart == -1)
1506 m_composingTextStart = effectiveAbsoluteCursorPos;
1508 const int oldComposingTextLen = m_composingText.length();
1509 m_composingText = text;
1511 const int newAbsoluteCursorPos =
1512 newCursorPosition <= 0
1513 ? m_composingTextStart + newCursorPosition
1514 : m_composingTextStart + m_composingText.length() + newCursorPosition - 1;
1516 const bool focusObjectWasComposing = focusObjectIsComposing();
1519 if (!m_composingText.isEmpty() && !m_composingText.contains(u'\n')
1520 && newAbsoluteCursorPos >= m_composingTextStart
1521 && newAbsoluteCursorPos <= m_composingTextStart + m_composingText.length())
1522 m_composingCursor = newAbsoluteCursorPos;
1524 m_composingCursor = -1;
1526 if (focusObjectIsComposing()) {
1527 QTextCharFormat underlined;
1528 underlined.setFontUnderline(
true);
1531 { QInputMethodEvent::TextFormat, 0,
int(m_composingText.length()), underlined },
1532 { QInputMethodEvent::Cursor, m_composingCursor - m_composingTextStart, 1 }
1535 if (oldComposingTextLen > 0 && !focusObjectWasComposing) {
1536 event.setCommitString({}, m_composingTextStart - effectiveAbsoluteCursorPos,
1537 oldComposingTextLen);
1539 if (m_composingText.isEmpty())
1542 QGuiApplication::sendEvent(m_focusObject, &event);
1546 if (focusObjectWasComposing) {
1547 event.setCommitString(m_composingText);
1549 event.setCommitString(m_composingText,
1550 m_composingTextStart - effectiveAbsoluteCursorPos,
1551 oldComposingTextLen);
1553 if (m_composingText.isEmpty())
1556 QGuiApplication::sendEvent(m_focusObject, &event);
1559 if (!focusObjectIsComposing() && newCursorPosition != 1) {
1563 const int newBlockPos = getBlockPosition(
1564 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
1567 { QInputMethodEvent::Selection, newAbsoluteCursorPos - newBlockPos, 0 }
1570 QGuiApplication::sendEvent(m_focusObject, &event);
1584 BatchEditLock batchEditLock(
this);
1591 finishComposingText();
1593 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1602 QString text = query->value(Qt::ImSurroundingText).toString();
1603 int textOffset = getBlockPosition(query);
1605 if (start < textOffset || end > textOffset + text.length()) {
1606 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1608 if (end - textOffset > text.length()) {
1609 const QString after = query->value(Qt::ImTextAfterCursor).toString();
1610 const int additionalSuffixLen = after.length() - (text.length() - cursorPos);
1612 if (additionalSuffixLen > 0)
1613 text += QStringView{after}.right(additionalSuffixLen);
1616 if (start < textOffset) {
1617 QString before = query->value(Qt::ImTextBeforeCursor).toString();
1618 before.chop(cursorPos);
1620 if (!before.isEmpty()) {
1621 text = before + text;
1622 textOffset -= before.length();
1626 if (start < textOffset || end - textOffset > text.length()) {
1627 qCDebug(lcQpaInputMethods) <<
"Warning: setComposingRegion: failed to retrieve text from composing region";
1633 m_composingText = text.mid(start - textOffset, end - start);
1634 m_composingTextStart = start;
1641 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1645 BatchEditLock batchEditLock(
this);
1647 int blockPosition = getBlockPosition(query);
1648 int localCursorPos = start - blockPosition;
1650 if (focusObjectIsComposing() && start == end && start >= m_composingTextStart
1651 && start <= m_composingTextStart + m_composingText.length()) {
1654 int localOldPos = query->value(Qt::ImCursorPosition).toInt();
1655 int pos = localCursorPos - localOldPos;
1656 QList<QInputMethodEvent::Attribute> attributes;
1657 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, pos, 1));
1662 QTextCharFormat underlined;
1663 underlined.setFontUnderline(
true);
1664 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, m_composingText.length(),
1665 QVariant(underlined)));
1666 m_composingCursor = start;
1669 QGuiApplication::sendEvent(m_focusObject, &event);
1672 focusObjectStopComposing();
1673 QList<QInputMethodEvent::Attribute> attributes;
1674 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection,
1678 QGuiApplication::sendEvent(m_focusObject, &event);
1685 BatchEditLock batchEditLock(
this);
1687 focusObjectStopComposing();
1688 m_handleMode = ShowCursor;
1689 sendShortcut(QKeySequence::SelectAll);
1695 BatchEditLock batchEditLock(
this);
1699 finishComposingText();
1701 m_handleMode = ShowCursor;
1702 sendShortcut(QKeySequence::Cut);
1708 BatchEditLock batchEditLock(
this);
1710 focusObjectStopComposing();
1711 m_handleMode = ShowCursor;
1712 sendShortcut(QKeySequence::Copy);
1724 BatchEditLock batchEditLock(
this);
1727 finishComposingText();
1729 m_handleMode = ShowCursor;
1730 sendShortcut(QKeySequence::Paste);
1736 for (
int i = 0; i < sequence.count(); ++i) {
1737 const QKeyCombination keys = sequence[i];
1738 Qt::Key key = Qt::Key(keys.toCombined() & ~Qt::KeyboardModifierMask);
1739 Qt::KeyboardModifiers mod = Qt::KeyboardModifiers(keys.toCombined() & Qt::KeyboardModifierMask);
1741 QKeyEvent pressEvent(QEvent::KeyPress, key, mod);
1742 QKeyEvent releaseEvent(QEvent::KeyRelease, key, mod);
1744 QGuiApplication::sendEvent(m_focusObject, &pressEvent);
1745 QGuiApplication::sendEvent(m_focusObject, &releaseEvent);
1749QSharedPointer<QInputMethodQueryEvent>
QAndroidInputContext::focusObjectInputMethodQuery(Qt::InputMethodQueries queries) {
1753 QObject *focusObject =
qGuiApp->focusObject();
1757 QInputMethodQueryEvent *ret =
new QInputMethodQueryEvent(queries);
1758 QCoreApplication::sendEvent(focusObject, ret);
1759 return QSharedPointer<QInputMethodQueryEvent>(ret);
1767 QObject *focusObject =
qGuiApp->focusObject();
1771 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)