diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java index aa18dfc2d1..ea8e5cd44c 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java @@ -301,6 +301,8 @@ public class QtActivityDelegate protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case InputMethodManager.RESULT_SHOWN: + QtNativeInputConnection.updateCursorPosition(); + //FALLTHROUGH case InputMethodManager.RESULT_UNCHANGED_SHOWN: setKeyboardVisibility(true); break; diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java b/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java index 4b2d50ca1f..5fdeb12c15 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java @@ -78,6 +78,7 @@ class QtNativeInputConnection static native boolean copy(); static native boolean copyURL(); static native boolean paste(); + static native boolean updateCursorPosition(); } class HideKeyboardRunnable implements Runnable { diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index 9d605c7a17..9efdcad158 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -67,8 +67,6 @@ namespace QtAndroidInput static QPointer m_mouseGrabber; - static int m_lastCursorPos = -1; - void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd) { AttachedJNIEnv env; @@ -78,21 +76,6 @@ namespace QtAndroidInput #ifdef QT_DEBUG_ANDROID_IM_PROTOCOL qDebug() << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd; #endif - if (candidatesStart == -1 && candidatesEnd == -1 && selStart == selEnd) { - // Qt only gives us position inside the block, so if we move to the - // same position in another block, the Android keyboard will believe - // we have not changed position, and be terribly confused. - if (selStart == m_lastCursorPos) { -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << ">>> FAKEUPDATESELECTION" << selStart+1; -#endif - env.jniEnv->CallStaticVoidMethod(applicationClass(), m_updateSelectionMethodID, - selStart+1, selEnd+1, candidatesStart, candidatesEnd); - } - m_lastCursorPos = selStart; - } else { - m_lastCursorPos = -1; - } env.jniEnv->CallStaticVoidMethod(applicationClass(), m_updateSelectionMethodID, selStart, selEnd, candidatesStart, candidatesEnd); } diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index 326972e71e..bfb13811e3 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -81,7 +81,7 @@ static jboolean commitText(JNIEnv *env, jobject /*thiz*/, jstring text, jint new env->ReleaseStringChars(text, jstr); #ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ COMMIT" << str; + qDebug() << "@@@ COMMIT" << str << newCursorPosition; #endif return m_androidInputContext->commitText(str, newCursorPosition); } @@ -160,7 +160,7 @@ static jstring getTextAfterCursor(JNIEnv *env, jobject /*thiz*/, jint length, ji const QString &text = m_androidInputContext->getTextAfterCursor(length, flags); #ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ GET" << length << text; + qDebug() << "@@@ GETA" << length << text; #endif return env->NewString(reinterpret_cast(text.constData()), jsize(text.length())); } @@ -172,7 +172,7 @@ static jstring getTextBeforeCursor(JNIEnv *env, jobject /*thiz*/, jint length, j const QString &text = m_androidInputContext->getTextBeforeCursor(length, flags); #ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ GET" << length << text; + qDebug() << "@@@ GETB" << length << text; #endif return env->NewString(reinterpret_cast(text.constData()), jsize(text.length())); } @@ -188,7 +188,7 @@ static jboolean setComposingText(JNIEnv *env, jobject /*thiz*/, jstring text, ji env->ReleaseStringChars(text, jstr); #ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ SET" << str; + qDebug() << "@@@ SET" << str << newCursorPosition; #endif return m_androidInputContext->setComposingText(str, newCursorPosition); } @@ -271,6 +271,18 @@ static jboolean paste(JNIEnv */*env*/, jobject /*thiz*/) return m_androidInputContext->paste(); } +static jboolean updateCursorPosition(JNIEnv */*env*/, jobject /*thiz*/) +{ + if (!m_androidInputContext) + return JNI_FALSE; + +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ UPDATECURSORPOS"; +#endif + m_androidInputContext->updateCursorPosition(); + return true; +} + static JNINativeMethod methods[] = { {"commitText", "(Ljava/lang/String;I)Z", (void *)commitText}, @@ -288,7 +300,8 @@ static JNINativeMethod methods[] = { {"cut", "()Z", (void *)cut}, {"copy", "()Z", (void *)copy}, {"copyURL", "()Z", (void *)copyURL}, - {"paste", "()Z", (void *)paste} + {"paste", "()Z", (void *)paste}, + {"updateCursorPosition", "()Z", (void *)updateCursorPosition} }; @@ -404,7 +417,9 @@ void QAndroidInputContext::updateCursorPosition() { QSharedPointer query = focusObjectInputMethodQuery(); if (!query.isNull() && !m_blockUpdateSelection) { - const int cursorPos = query->value(Qt::ImCursorPosition).toInt(); + // make sure it also works with editors that have not been updated to the new API + QVariant absolutePos = query->value(Qt::ImAbsolutePosition); + const int cursorPos = absolutePos.isValid() ? absolutePos.toInt() : query->value(Qt::ImCursorPosition).toInt(); QtAndroidInput::updateSelection(cursorPos, cursorPos, -1, -1); //selection empty and no pre-edit text } } @@ -422,9 +437,9 @@ void QAndroidInputContext::invokeAction(QInputMethod::Action action, int cursorP #warning TODO Handle at least QInputMethod::ContextMenu action Q_UNUSED(action) Q_UNUSED(cursorPosition) - - if (action == QInputMethod::Click) - commit(); + //### click should be passed to the IM, but in the meantime it's better to ignore it than to do something wrong + // if (action == QInputMethod::Click) + // commit(); } QRectF QAndroidInputContext::keyboardRect() const @@ -573,6 +588,12 @@ QString QAndroidInputContext::getSelectedText(jint /*flags*/) QString QAndroidInputContext::getTextAfterCursor(jint length, jint /*flags*/) { + QVariant textAfter = queryFocusObjectThreadSafe(Qt::ImTextAfterCursor, QVariant(length)); + if (textAfter.isValid()) { + return textAfter.toString().left(length); + } + + //compatibility code for old controls that do not implement the new API QSharedPointer query = focusObjectInputMethodQuery(); if (query.isNull()) return QString(); @@ -587,15 +608,21 @@ QString QAndroidInputContext::getTextAfterCursor(jint length, jint /*flags*/) QString QAndroidInputContext::getTextBeforeCursor(jint length, jint /*flags*/) { + QVariant textBefore = queryFocusObjectThreadSafe(Qt::ImTextBeforeCursor, QVariant(length)); + if (textBefore.isValid()) { + return textBefore.toString().left(length); + } + + //compatibility code for old controls that do not implement the new API QSharedPointer query = focusObjectInputMethodQuery(); if (query.isNull()) return QString(); + int cursorPos = query->value(Qt::ImCursorPosition).toInt(); QString text = query->value(Qt::ImSurroundingText).toString(); if (!text.length()) return text; - int cursorPos = query->value(Qt::ImCursorPosition).toInt(); const int wordLeftPos = cursorPos - length; return text.mid(wordLeftPos > 0 ? wordLeftPos : 0, cursorPos); } @@ -621,8 +648,9 @@ jboolean QAndroidInputContext::setComposingText(const QString &text, jint newCur QSharedPointer query = focusObjectInputMethodQuery(); if (!query.isNull() && !m_blockUpdateSelection) { - int cursorPos = query->value(Qt::ImCursorPosition).toInt(); - int preeditLength = text.length(); + QVariant absolutePos = query->value(Qt::ImAbsolutePosition); + const int cursorPos = absolutePos.isValid() ? absolutePos.toInt() : query->value(Qt::ImCursorPosition).toInt(); + const int preeditLength = text.length(); QtAndroidInput::updateSelection(cursorPos+preeditLength, cursorPos+preeditLength, cursorPos, cursorPos+preeditLength); } @@ -650,16 +678,19 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) Therefore, the length of the region is end - start */ int length = end - start; + int localPos = query->value(Qt::ImCursorPosition).toInt(); + QVariant absolutePos = query->value(Qt::ImAbsolutePosition); + int blockPosition = absolutePos.isValid() ? absolutePos.toInt() - localPos : 0; + int localStart = start - blockPosition; // Qt uses position inside block bool updateSelectionWasBlocked = m_blockUpdateSelection; m_blockUpdateSelection = true; QString text = query->value(Qt::ImSurroundingText).toString(); - m_composingText = text.mid(start, length); + m_composingText = text.mid(localStart, length); - //in the Qt text controls, cursor pos is the start of the preedit - int cursorPos = query->value(Qt::ImCursorPosition).toInt(); - int relativeStart = start - cursorPos; + //in the Qt text controls, the cursor position is the start of the preedit + int relativeStart = localStart - localPos; QList attributes; @@ -669,6 +700,9 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, length, QVariant(underlined))); + // Keep the cursor position unchanged (don't move to end of preedit) + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, localPos - localStart, length, QVariant())); + QInputMethodEvent event(m_composingText, attributes); event.setCommitString(QString(), relativeStart, length); sendInputMethodEvent(&event); @@ -720,6 +754,26 @@ jboolean QAndroidInputContext::paste() return JNI_FALSE; } + +Q_INVOKABLE QVariant QAndroidInputContext::queryFocusObjectUnsafe(Qt::InputMethodQuery query, QVariant argument) +{ + return QInputMethod::queryFocusObject(query, argument); +} + +QVariant QAndroidInputContext::queryFocusObjectThreadSafe(Qt::InputMethodQuery query, QVariant argument) +{ + bool inMainThread = qGuiApp->thread() == QThread::currentThread(); + QVariant retval; + + QMetaObject::invokeMethod(this, "queryFocusObjectUnsafe", + inMainThread ? Qt::DirectConnection : Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVariant, retval), + Q_ARG(Qt::InputMethodQuery, query), + Q_ARG(QVariant, argument)); + + return retval; +} + QSharedPointer QAndroidInputContext::focusObjectInputMethodQuery(Qt::InputMethodQueries queries) { #warning TODO make qGuiApp->focusObject() thread safe !!! diff --git a/src/plugins/platforms/android/qandroidinputcontext.h b/src/plugins/platforms/android/qandroidinputcontext.h index 041bd0dc49..2fb54a97c4 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.h +++ b/src/plugins/platforms/android/qandroidinputcontext.h @@ -114,14 +114,19 @@ public: jboolean copyURL(); jboolean paste(); +public slots: + void updateCursorPosition(); + private: QSharedPointer focusObjectInputMethodQuery(Qt::InputMethodQueries queries = Qt::ImQueryAll); void sendInputMethodEvent(QInputMethodEvent *event); + Q_INVOKABLE QVariant queryFocusObjectUnsafe(Qt::InputMethodQuery query, QVariant argument); + QVariant queryFocusObjectThreadSafe(Qt::InputMethodQuery query, QVariant argument); + private slots: virtual void sendEvent(QObject *receiver, QInputMethodEvent *event); virtual void sendEvent(QObject *receiver, QInputMethodQueryEvent *event); - void updateCursorPosition(); private: ExtractedText m_extractedText; diff --git a/src/widgets/widgets/qplaintextedit.cpp b/src/widgets/widgets/qplaintextedit.cpp index d51dce4765..beb44e602e 100644 --- a/src/widgets/widgets/qplaintextedit.cpp +++ b/src/widgets/widgets/qplaintextedit.cpp @@ -2184,6 +2184,13 @@ void QPlainTextEdit::scrollContentsBy(int dx, int /*dy*/) /*!\reimp */ QVariant QPlainTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const +{ + return inputMethodQuery(property, QVariant()); +} + +/*!\internal + */ +QVariant QPlainTextEdit::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const { Q_D(const QPlainTextEdit); QVariant v; @@ -2192,7 +2199,7 @@ QVariant QPlainTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const v = QWidget::inputMethodQuery(property); break; default: - v = d->control->inputMethodQuery(property, QVariant()); + v = d->control->inputMethodQuery(property, argument); const QPoint offset(-d->horizontalOffset(), -0); if (v.type() == QVariant::RectF) v = v.toRectF().toRect().translated(offset); diff --git a/src/widgets/widgets/qplaintextedit.h b/src/widgets/widgets/qplaintextedit.h index 1fb4625fb1..54cd3e14ed 100644 --- a/src/widgets/widgets/qplaintextedit.h +++ b/src/widgets/widgets/qplaintextedit.h @@ -185,6 +185,7 @@ public: int blockCount() const; QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + Q_INVOKABLE QVariant inputMethodQuery(Qt::InputMethodQuery query, QVariant argument) const; public Q_SLOTS: