summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Olav Tvete <paul.tvete@digia.com>2014-02-21 09:58:32 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2014-03-18 09:59:20 +0100
commit68a9229a97d358639357f76cfb186bb61aa6ce92 (patch)
tree62e72015ffed78821d7f1a8b6d7124594867dbff /src
parentb55e0ac57f95b79879bcd301ff3b7365d957acc2 (diff)
Android input method improvements
Use the new inputmethod query API. and get rid of the hack where we would move the cursor back and forwards to make sure that the Android software keyboard noticed that the cursor had moved. The android plugin now uses absolute positions instead of position within the paragraph for all cursor handling (provided that the control supports the new API). Task-number: QTBUG-37511 Change-Id: I03463dbbcb4acbfa41e2eab06889d021d50da01f Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
Diffstat (limited to 'src')
-rw-r--r--src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java2
-rw-r--r--src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java1
-rw-r--r--src/plugins/platforms/android/androidjniinput.cpp17
-rw-r--r--src/plugins/platforms/android/qandroidinputcontext.cpp86
-rw-r--r--src/plugins/platforms/android/qandroidinputcontext.h7
-rw-r--r--src/widgets/widgets/qplaintextedit.cpp9
-rw-r--r--src/widgets/widgets/qplaintextedit.h1
7 files changed, 88 insertions, 35 deletions
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<QWindow> 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<const jchar *>(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<const jchar *>(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<QInputMethodQueryEvent> 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<QInputMethodQueryEvent> 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<QInputMethodQueryEvent> 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<QInputMethodQueryEvent> 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<QInputMethodEvent::Attribute> 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<QInputMethodQueryEvent> 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<QInputMethodQueryEvent> 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
@@ -2185,6 +2185,13 @@ void QPlainTextEdit::scrollContentsBy(int dx, int /*dy*/)
*/
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;
switch (property) {
@@ -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: