diff options
Diffstat (limited to 'src/plugins/platforms/android')
8 files changed, 115 insertions, 49 deletions
diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index 55d44b7377..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); } @@ -517,6 +500,12 @@ namespace QtAndroidInput } } + // maps 0 to the empty string, and anything else to a single-character string + static inline QString toString(jint unicode) + { + return unicode ? QString(QChar(unicode)) : QString(); + } + static void keyDown(JNIEnv */*env*/, jobject /*thiz*/, jint key, jint unicode, jint modifier) { Qt::KeyboardModifiers modifiers; @@ -533,7 +522,7 @@ namespace QtAndroidInput QEvent::KeyPress, mapAndroidKey(key), modifiers, - QChar(unicode), + toString(unicode), false); } @@ -553,7 +542,7 @@ namespace QtAndroidInput QEvent::KeyRelease, mapAndroidKey(key), modifiers, - QChar(unicode), + toString(unicode), false); } diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp index b112e265a5..224a8ca9f7 100644 --- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp +++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp @@ -262,18 +262,20 @@ private: AndroidAssetsFileEngineHandler::AndroidAssetsFileEngineHandler() : m_assetsCache(std::max(5, qgetenv("QT_ANDROID_MAX_ASSETS_CACHE_SIZE").toInt())) , m_hasPrepopulatedCache(false) + , m_hasTriedPrepopulatingCache(false) { m_assetManager = QtAndroid::assetManager(); - prepopulateCache(); } AndroidAssetsFileEngineHandler::~AndroidAssetsFileEngineHandler() { } -void AndroidAssetsFileEngineHandler::prepopulateCache() +void AndroidAssetsFileEngineHandler::prepopulateCache() const { - QMutexLocker locker(&m_assetsCacheMutext); + Q_ASSERT(!m_hasTriedPrepopulatingCache); + m_hasTriedPrepopulatingCache = true; + Q_ASSERT(m_assetsCache.isEmpty()); // Failsafe: Don't read cache files that are larger than 1MB @@ -364,7 +366,11 @@ QAbstractFileEngine * AndroidAssetsFileEngineHandler::create(const QString &file if (!path.size()) path = fileName.left(fileName.length() - 1).toUtf8(); + m_assetsCacheMutext.lock(); + if (!m_hasTriedPrepopulatingCache) + prepopulateCache(); + QSharedPointer<AndroidAssetDir> *aad = m_assetsCache.object(path); m_assetsCacheMutext.unlock(); if (!aad) { diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.h b/src/plugins/platforms/android/qandroidassetsfileenginehandler.h index d56367d4d8..ac16ad7b79 100644 --- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.h +++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.h @@ -58,12 +58,13 @@ public: QAbstractFileEngine *create(const QString &fileName) const; private: - void prepopulateCache(); + void prepopulateCache() const; AAssetManager *m_assetManager; mutable QCache<QByteArray, QSharedPointer<AndroidAssetDir>> m_assetsCache; mutable QMutex m_assetsCacheMutext; - bool m_hasPrepopulatedCache; + mutable bool m_hasPrepopulatedCache; + mutable bool m_hasTriedPrepopulatingCache; }; #endif // QANDROIDASSETSFILEENGINEHANDLER_H 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/plugins/platforms/android/qandroidplatformbackingstore.cpp b/src/plugins/platforms/android/qandroidplatformbackingstore.cpp index b85b1157a8..ff49f59076 100644 --- a/src/plugins/platforms/android/qandroidplatformbackingstore.cpp +++ b/src/plugins/platforms/android/qandroidplatformbackingstore.cpp @@ -50,11 +50,8 @@ QT_BEGIN_NAMESPACE QAndroidPlatformBackingStore::QAndroidPlatformBackingStore(QWindow *window) : QPlatformBackingStore(window) { - Q_ASSERT(window->handle()); - if (window->surfaceType() == QSurface::RasterSurface) - (static_cast<QAndroidPlatformRasterWindow *>(window->handle()))->setBackingStore(this); - else - qWarning("QAndroidPlatformBackingStore does not support GL windows."); + if (window->handle()) + setBackingStore(window); } QPaintDevice *QAndroidPlatformBackingStore::paintDevice() @@ -64,9 +61,11 @@ QPaintDevice *QAndroidPlatformBackingStore::paintDevice() void QAndroidPlatformBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) { - Q_UNUSED(window); Q_UNUSED(offset); + if (!m_backingStoreSet) + setBackingStore(window); + (static_cast<QAndroidPlatformRasterWindow *>(window->handle()))->repaint(region); } @@ -78,4 +77,14 @@ void QAndroidPlatformBackingStore::resize(const QSize &size, const QRegion &stat m_image = QImage(size, window()->screen()->handle()->format()); } +void QAndroidPlatformBackingStore::setBackingStore(QWindow *window) +{ + if (window->surfaceType() == QSurface::RasterSurface) { + (static_cast<QAndroidPlatformRasterWindow *>(window->handle()))->setBackingStore(this); + m_backingStoreSet = true; + } else { + qWarning("QAndroidPlatformBackingStore does not support GL windows."); + } +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidplatformbackingstore.h b/src/plugins/platforms/android/qandroidplatformbackingstore.h index e6ea3dcce0..4c24e129c3 100644 --- a/src/plugins/platforms/android/qandroidplatformbackingstore.h +++ b/src/plugins/platforms/android/qandroidplatformbackingstore.h @@ -56,9 +56,10 @@ public: virtual void flush(QWindow *window, const QRegion ®ion, const QPoint &offset); virtual void resize(const QSize &size, const QRegion &staticContents); const QImage image() { return m_image; } - + void setBackingStore(QWindow *window); protected: QImage m_image; + bool m_backingStoreSet = false; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidplatformintegration.cpp b/src/plugins/platforms/android/qandroidplatformintegration.cpp index 2cca974b41..9adefd5b2c 100644 --- a/src/plugins/platforms/android/qandroidplatformintegration.cpp +++ b/src/plugins/platforms/android/qandroidplatformintegration.cpp @@ -152,7 +152,8 @@ bool QAndroidPlatformIntegration::hasCapability(Capability cap) const case ThreadedOpenGL: if (needsWorkaround()) return false; - // fall through + else + return true; default: return QPlatformIntegration::hasCapability(cap); } |