From 85fc94db1bb2189ea379033aec23b1779b809cc0 Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Mon, 7 Oct 2013 12:45:36 +0200 Subject: Android: Fix backspace bug with 4.3 stock keyboard The Android 4.3 keyboard will cause setComposingRegion() to be called when backspacing over an existing word. If we don't implement that, the editor will be out of sync with the input method. Task-number: QTBUG-32955 Change-Id: I6c4ff786269a4e74c70a093c5f03c4c5a5727dd5 Reviewed-by: Eskil Abrahamsen Blomfeldt --- .../qtproject/qt5/android/QtInputConnection.java | 7 ++ .../platforms/android/src/qandroidinputcontext.cpp | 112 ++++++++++++++++++++- .../platforms/android/src/qandroidinputcontext.h | 2 + 3 files changed, 118 insertions(+), 3 deletions(-) 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 3bcec030b5..f251369737 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java @@ -73,6 +73,7 @@ class QtNativeInputConnection static native String getTextAfterCursor(int length, int flags); static native String getTextBeforeCursor(int length, int flags); static native boolean setComposingText(String text, int newCursorPosition); + static native boolean setComposingRegion(int start, int end); static native boolean setSelection(int start, int end); static native boolean selectAll(); static native boolean cut(); @@ -236,6 +237,12 @@ public class QtInputConnection extends BaseInputConnection return QtNativeInputConnection.setComposingText(text.toString(), newCursorPosition); } + @Override + public boolean setComposingRegion(int start, int end) + { + return QtNativeInputConnection.setComposingRegion(start, end); + } + @Override public boolean setSelection(int start, int end) { diff --git a/src/plugins/platforms/android/src/qandroidinputcontext.cpp b/src/plugins/platforms/android/src/qandroidinputcontext.cpp index 1981ac0b75..386c8e006a 100644 --- a/src/plugins/platforms/android/src/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/src/qandroidinputcontext.cpp @@ -54,6 +54,8 @@ #include +#include + QT_BEGIN_NAMESPACE static QAndroidInputContext *m_androidInputContext = 0; @@ -78,6 +80,9 @@ static jboolean commitText(JNIEnv *env, jobject /*thiz*/, jstring text, jint new QString str(reinterpret_cast(jstr), env->GetStringLength(text)); env->ReleaseStringChars(text, jstr); +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ COMMIT" << str; +#endif return m_androidInputContext->commitText(str, newCursorPosition); } @@ -86,6 +91,9 @@ static jboolean deleteSurroundingText(JNIEnv */*env*/, jobject /*thiz*/, jint le if (!m_androidInputContext) return JNI_FALSE; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ DELETE" << leftLength << rightLength; +#endif return m_androidInputContext->deleteSurroundingText(leftLength, rightLength); } @@ -94,6 +102,9 @@ static jboolean finishComposingText(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ FINISH"; +#endif return m_androidInputContext->finishComposingText(); } @@ -110,6 +121,9 @@ static jobject getExtractedText(JNIEnv *env, jobject /*thiz*/, int hintMaxChars, if (!m_androidInputContext) return 0; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ GETEX"; +#endif const QAndroidInputContext::ExtractedText &extractedText = m_androidInputContext->getExtractedText(hintMaxChars, hintMaxLines, flags); @@ -133,6 +147,9 @@ static jstring getSelectedText(JNIEnv *env, jobject /*thiz*/, jint flags) return 0; const QString &text = m_androidInputContext->getSelectedText(flags); +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ GETSEL" << text; +#endif return env->NewString(reinterpret_cast(text.constData()), jsize(text.length())); } @@ -142,6 +159,9 @@ static jstring getTextAfterCursor(JNIEnv *env, jobject /*thiz*/, jint length, ji return 0; const QString &text = m_androidInputContext->getTextAfterCursor(length, flags); +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ GET" << length << text; +#endif return env->NewString(reinterpret_cast(text.constData()), jsize(text.length())); } @@ -151,6 +171,9 @@ static jstring getTextBeforeCursor(JNIEnv *env, jobject /*thiz*/, jint length, j return 0; const QString &text = m_androidInputContext->getTextBeforeCursor(length, flags); +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ GET" << length << text; +#endif return env->NewString(reinterpret_cast(text.constData()), jsize(text.length())); } @@ -164,14 +187,32 @@ static jboolean setComposingText(JNIEnv *env, jobject /*thiz*/, jstring text, ji QString str(reinterpret_cast(jstr), env->GetStringLength(text)); env->ReleaseStringChars(text, jstr); +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ SET" << str; +#endif return m_androidInputContext->setComposingText(str, newCursorPosition); } +static jboolean setComposingRegion(JNIEnv */*env*/, jobject /*thiz*/, jint start, jint end) +{ + if (!m_androidInputContext) + return JNI_FALSE; + +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ SETR" << start << end; +#endif + return m_androidInputContext->setComposingRegion(start, end); +} + + static jboolean setSelection(JNIEnv */*env*/, jobject /*thiz*/, jint start, jint end) { if (!m_androidInputContext) return JNI_FALSE; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ SETSEL" << start << end; +#endif return m_androidInputContext->setSelection(start, end); } @@ -180,6 +221,9 @@ static jboolean selectAll(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@ SELALL"; +#endif return m_androidInputContext->selectAll(); } @@ -188,6 +232,9 @@ static jboolean cut(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@"; +#endif return m_androidInputContext->cut(); } @@ -196,6 +243,9 @@ static jboolean copy(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@"; +#endif return m_androidInputContext->copy(); } @@ -204,6 +254,9 @@ static jboolean copyURL(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@"; +#endif return m_androidInputContext->copyURL(); } @@ -212,6 +265,9 @@ static jboolean paste(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; +#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL + qDebug() << "@@@"; +#endif return m_androidInputContext->paste(); } @@ -226,6 +282,7 @@ static JNINativeMethod methods[] = { {"getTextAfterCursor", "(II)Ljava/lang/String;", (void *)getTextAfterCursor}, {"getTextBeforeCursor", "(II)Ljava/lang/String;", (void *)getTextBeforeCursor}, {"setComposingText", "(Ljava/lang/String;I)Z", (void *)setComposingText}, + {"setComposingRegion", "(II)Z", (void *)setComposingRegion}, {"setSelection", "(II)Z", (void *)setSelection}, {"selectAll", "()Z", (void *)selectAll}, {"cut", "()Z", (void *)cut}, @@ -235,7 +292,8 @@ static JNINativeMethod methods[] = { }; -QAndroidInputContext::QAndroidInputContext():QPlatformInputContext() +QAndroidInputContext::QAndroidInputContext() + : QPlatformInputContext(), m_blockUpdateSelection(false) { QtAndroid::AttachedJNIEnv env; if (!env.jniEnv) @@ -340,7 +398,7 @@ void QAndroidInputContext::commit() void QAndroidInputContext::updateCursorPosition() { QSharedPointer query = focusObjectInputMethodQuery(); - if (!query.isNull()) { + if (!query.isNull() && !m_blockUpdateSelection) { const int cursorPos = query->value(Qt::ImCursorPosition).toInt(); QtAndroidInput::updateSelection(cursorPos, cursorPos, -1, -1); //selection empty and no pre-edit text } @@ -557,7 +615,7 @@ jboolean QAndroidInputContext::setComposingText(const QString &text, jint newCur sendInputMethodEvent(&event); QSharedPointer query = focusObjectInputMethodQuery(); - if (!query.isNull()) { + if (!query.isNull() && !m_blockUpdateSelection) { int cursorPos = query->value(Qt::ImCursorPosition).toInt(); int preeditLength = text.length(); QtAndroidInput::updateSelection(cursorPos+preeditLength, cursorPos+preeditLength, cursorPos, cursorPos+preeditLength); @@ -566,6 +624,54 @@ jboolean QAndroidInputContext::setComposingText(const QString &text, jint newCur return JNI_TRUE; } +// Android docs say: +// * start may be after end, same meaning as if swapped +// * this function must not trigger updateSelection +// * if start == end then we should stop composing +jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) +{ + QSharedPointer query = focusObjectInputMethodQuery(); + if (query.isNull()) + return JNI_FALSE; + + if (start > end) + qSwap(start, end); + + /* + start and end are cursor positions, not character positions, + i.e. selecting the first character is done by start == 0 and end == 1, + and start == end means no character selected + + Therefore, the length of the region is end - start + */ + int length = end - start; + + bool updateSelectionWasBlocked = m_blockUpdateSelection; + m_blockUpdateSelection = true; + + QString text = query->value(Qt::ImSurroundingText).toString(); + m_composingText = text.mid(start, 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; + + QList attributes; + + // Show compose text underlined + QTextCharFormat underlined; + underlined.setFontUnderline(true); + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, length, + QVariant(underlined))); + + QInputMethodEvent event(m_composingText, attributes); + event.setCommitString(QString(), relativeStart, length); + sendInputMethodEvent(&event); + + m_blockUpdateSelection = updateSelectionWasBlocked; + return JNI_TRUE; +} + jboolean QAndroidInputContext::setSelection(jint start, jint end) { QList attributes; diff --git a/src/plugins/platforms/android/src/qandroidinputcontext.h b/src/plugins/platforms/android/src/qandroidinputcontext.h index 482aeffa50..d19dcc384b 100644 --- a/src/plugins/platforms/android/src/qandroidinputcontext.h +++ b/src/plugins/platforms/android/src/qandroidinputcontext.h @@ -105,6 +105,7 @@ public: QString getTextAfterCursor(jint length, jint flags); QString getTextBeforeCursor(jint length, jint flags); jboolean setComposingText(const QString &text, jint newCursorPosition); + jboolean setComposingRegion(jint start, jint end); jboolean setSelection(jint start, jint end); jboolean selectAll(); jboolean cut(); @@ -125,6 +126,7 @@ private: ExtractedText m_extractedText; QString m_composingText; QMetaObject::Connection m_updateCursorPosConnection; + bool m_blockUpdateSelection; }; QT_END_NAMESPACE -- cgit v1.2.3