diff options
Diffstat (limited to 'src/plugins/platforms/android/qandroidinputcontext.cpp')
-rw-r--r-- | src/plugins/platforms/android/qandroidinputcontext.cpp | 254 |
1 files changed, 131 insertions, 123 deletions
diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index 417f3469a8..62212ff63d 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -1,43 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org> -** Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org> +// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <android/log.h> @@ -116,9 +80,7 @@ static jboolean beginBatchEdit(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@ BEGINBATCH"); -#endif + qCDebug(lcQpaInputMethods) << "@@@ BEGINBATCH"; jboolean res = JNI_FALSE; runOnQtThread([&res]{res = m_androidInputContext->beginBatchEdit();}); return res; @@ -129,9 +91,7 @@ static jboolean endBatchEdit(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@ ENDBATCH"); -#endif + qCDebug(lcQpaInputMethods) << "@@@ ENDBATCH"; jboolean res = JNI_FALSE; runOnQtThread([&res]{res = m_androidInputContext->endBatchEdit();}); @@ -149,9 +109,7 @@ static jboolean commitText(JNIEnv *env, jobject /*thiz*/, jstring text, jint new QString str(reinterpret_cast<const QChar *>(jstr), env->GetStringLength(text)); env->ReleaseStringChars(text, jstr); -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ COMMIT" << str << newCursorPosition; -#endif + qCDebug(lcQpaInputMethods) << "@@@ COMMIT" << str << newCursorPosition; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->commitText(str, newCursorPosition);}); return res; @@ -162,9 +120,7 @@ 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 + qCDebug(lcQpaInputMethods) << "@@@ DELETE" << leftLength << rightLength; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->deleteSurroundingText(leftLength, rightLength);}); return res; @@ -175,9 +131,7 @@ static jboolean finishComposingText(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@ FINISH"); -#endif + qCDebug(lcQpaInputMethods) << "@@@ FINISH"; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->finishComposingText();}); return res; @@ -201,9 +155,7 @@ static jobject getExtractedText(JNIEnv *env, jobject /*thiz*/, int hintMaxChars, QAndroidInputContext::ExtractedText extractedText; runOnQtThread([&]{extractedText = m_androidInputContext->getExtractedText(hintMaxChars, hintMaxLines, flags);}); -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ 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; -#endif + 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; jobject object = env->NewObject(m_extractedTextClass, m_classConstructorMethodID); env->SetIntField(object, m_partialStartOffsetFieldID, extractedText.partialStartOffset); @@ -226,9 +178,7 @@ static jstring getSelectedText(JNIEnv *env, jobject /*thiz*/, jint flags) QString text; runOnQtThread([&]{text = m_androidInputContext->getSelectedText(flags);}); -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ GETSEL" << text; -#endif + qCDebug(lcQpaInputMethods) << "@@@ GETSEL" << text; if (text.isEmpty()) return 0; return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length())); @@ -241,9 +191,7 @@ static jstring getTextAfterCursor(JNIEnv *env, jobject /*thiz*/, jint length, ji QString text; runOnQtThread([&]{text = m_androidInputContext->getTextAfterCursor(length, flags);}); -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ GETA" << length << text; -#endif + qCDebug(lcQpaInputMethods) << "@@@ GETA" << length << text; return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length())); } @@ -254,9 +202,7 @@ static jstring getTextBeforeCursor(JNIEnv *env, jobject /*thiz*/, jint length, j QString text; runOnQtThread([&]{text = m_androidInputContext->getTextBeforeCursor(length, flags);}); -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ GETB" << length << text; -#endif + qCDebug(lcQpaInputMethods) << "@@@ GETB" << length << text; return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length())); } @@ -270,9 +216,7 @@ static jboolean setComposingText(JNIEnv *env, jobject /*thiz*/, jstring text, ji QString str(reinterpret_cast<const QChar *>(jstr), env->GetStringLength(text)); env->ReleaseStringChars(text, jstr); -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ SET" << str << newCursorPosition; -#endif + qCDebug(lcQpaInputMethods) << "@@@ SET" << str << newCursorPosition; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->setComposingText(str, newCursorPosition);}); return res; @@ -283,9 +227,7 @@ static jboolean setComposingRegion(JNIEnv */*env*/, jobject /*thiz*/, jint start if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ SETR" << start << end; -#endif + qCDebug(lcQpaInputMethods) << "@@@ SETR" << start << end; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->setComposingRegion(start, end);}); return res; @@ -297,9 +239,7 @@ static jboolean setSelection(JNIEnv */*env*/, jobject /*thiz*/, jint start, jint if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug() << "@@@ SETSEL" << start << end; -#endif + qCDebug(lcQpaInputMethods) << "@@@ SETSEL" << start << end; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->setSelection(start, end);}); return res; @@ -311,9 +251,7 @@ static jboolean selectAll(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@ SELALL"); -#endif + qCDebug(lcQpaInputMethods) << "@@@ SELALL"; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->selectAll();}); return res; @@ -324,9 +262,7 @@ static jboolean cut(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@"); -#endif + qCDebug(lcQpaInputMethods) << "@@@"; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->cut();}); return res; @@ -337,9 +273,7 @@ static jboolean copy(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@"); -#endif + qCDebug(lcQpaInputMethods) << "@@@"; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->copy();}); return res; @@ -350,9 +284,7 @@ static jboolean copyURL(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@"); -#endif + qCDebug(lcQpaInputMethods) << "@@@"; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->copyURL();}); return res; @@ -363,9 +295,7 @@ static jboolean paste(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@ PASTE"); -#endif + qCDebug(lcQpaInputMethods) << "@@@ PASTE"; jboolean res = JNI_FALSE; runOnQtThread([&]{res = m_androidInputContext->paste();}); return res; @@ -376,14 +306,24 @@ static jboolean updateCursorPosition(JNIEnv */*env*/, jobject /*thiz*/) if (!m_androidInputContext) return JNI_FALSE; -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qDebug("@@@ UPDATECURSORPOS"); -#endif + qCDebug(lcQpaInputMethods) << "@@@ UPDATECURSORPOS"; runOnQtThread([&]{m_androidInputContext->updateCursorPosition();}); return true; } +static void reportFullscreenMode(JNIEnv */*env*/, jobject /*thiz*/, jboolean enabled) +{ + if (!m_androidInputContext) + return; + + runOnQtThread([&]{m_androidInputContext->reportFullscreenMode(enabled);}); +} + +static jboolean fullscreenMode(JNIEnv */*env*/, jobject /*thiz*/) +{ + return m_androidInputContext ? m_androidInputContext->fullscreenMode() : false; +} static JNINativeMethod methods[] = { {"beginBatchEdit", "()Z", (void *)beginBatchEdit}, @@ -404,7 +344,9 @@ static JNINativeMethod methods[] = { {"copy", "()Z", (void *)copy}, {"copyURL", "()Z", (void *)copyURL}, {"paste", "()Z", (void *)paste}, - {"updateCursorPosition", "()Z", (void *)updateCursorPosition} + {"updateCursorPosition", "()Z", (void *)updateCursorPosition}, + {"reportFullscreenMode", "(Z)V", (void *)reportFullscreenMode}, + {"fullscreenMode", "()Z", (void *)fullscreenMode} }; static QRect screenInputItemRectangle() @@ -421,6 +363,7 @@ QAndroidInputContext::QAndroidInputContext() , m_handleMode(Hidden) , m_batchEditNestingLevel(0) , m_focusObject(0) + , m_fullScreenMode(false) { QJniEnvironment env; jclass clazz = env.findClass(QtNativeInputConnectionClassName); @@ -603,12 +546,29 @@ void QAndroidInputContext::updateCursorPosition() } } +bool QAndroidInputContext::isImhNoTextHandlesSet() +{ + QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); + if (query.isNull()) + return false; + return query->value(Qt::ImHints).toUInt() & Qt::ImhNoTextHandles; +} + void QAndroidInputContext::updateSelectionHandles() { + if (m_fullScreenMode) { + QtAndroidInput::updateHandles(Hidden); + return; + } static bool noHandles = qEnvironmentVariableIntValue("QT_QPA_NO_TEXT_HANDLES"); if (noHandles || !m_focusObject) return; + if (isImhNoTextHandlesSet()) { + QtAndroidInput::updateHandles(Hidden); + return; + } + auto im = qGuiApp->inputMethod(); QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled @@ -622,6 +582,11 @@ void QAndroidInputContext::updateSelectionHandles() bool readOnly = readOnlyVariant.toBool(); QPlatformWindow *qPlatformWindow = qGuiApp->focusWindow()->handle(); + if (!readOnly && ((m_handleMode & 0xff) == Hidden)) { + QtAndroidInput::updateHandles(Hidden); + return; + } + if ( cpos == anchor && (!readOnlyVariant.isValid() || readOnly)) { QtAndroidInput::updateHandles(Hidden); return; @@ -629,17 +594,27 @@ void QAndroidInputContext::updateSelectionHandles() if (cpos == anchor || im->anchorRectangle().isNull()) { auto curRect = cursorRectangle(); - QPoint cursorPoint = qPlatformWindow->mapToGlobal(QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height())); - QPoint editMenuPoint(cursorPoint.x(), cursorPoint.y()); + QPoint cursorPointGlobal = qPlatformWindow->mapToGlobal( + QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height())); + QPoint cursorPoint(curRect.center().x(), curRect.bottom()); + int x = curRect.x(); + int y = curRect.y(); + + // Use x and y for the editMenuPoint from the cursorPointGlobal when the cursor is in the Dialog + if (cursorPointGlobal != cursorPoint) { + x = cursorPointGlobal.x(); + y = cursorPointGlobal.y(); + } + + QPoint editMenuPoint(x, y); m_handleMode &= ShowEditPopup; m_handleMode |= ShowCursor; uint32_t buttons = readOnly ? 0 : EditContext::PasteButton; if (!query.value(Qt::ImSurroundingText).toString().isEmpty()) buttons |= EditContext::SelectAllButton; - QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPoint); - // The VK is hidden, reset the timer - if (m_hideCursorHandleTimer.isActive()) - m_hideCursorHandleTimer.start(); + QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPointGlobal); + m_hideCursorHandleTimer.start(); + return; } @@ -654,22 +629,26 @@ void QAndroidInputContext::updateSelectionHandles() QPoint leftPoint(qPlatformWindow->mapToGlobal(leftRect.bottomLeft().toPoint())); QPoint rightPoint(qPlatformWindow->mapToGlobal(rightRect.bottomRight().toPoint())); - if (m_selectHandleWidth == 0) - m_selectHandleWidth = QtAndroidInput::getSelectHandleWidth() / 2; - int rightSideOfScreen = QtAndroid::androidPlatformIntegration()->screen()->availableGeometry().right(); - if (leftPoint.x() < m_selectHandleWidth) - leftPoint.setX(m_selectHandleWidth); + QAndroidPlatformIntegration *platformIntegration = QtAndroid::androidPlatformIntegration(); + if (platformIntegration) { + if (m_selectHandleWidth == 0) + m_selectHandleWidth = QtAndroidInput::getSelectHandleWidth() / 2; + + int rightSideOfScreen = platformIntegration->screen()->availableGeometry().right(); + if (leftPoint.x() < m_selectHandleWidth) + leftPoint.setX(m_selectHandleWidth); - if (rightPoint.x() > rightSideOfScreen - m_selectHandleWidth) - rightPoint.setX(rightSideOfScreen - m_selectHandleWidth); + if (rightPoint.x() > rightSideOfScreen - m_selectHandleWidth) + rightPoint.setX(rightSideOfScreen - m_selectHandleWidth); - QPoint editPoint(qPlatformWindow->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint())); - uint32_t buttons = readOnly ? EditContext::CopyButton | EditContext::SelectAllButton - : EditContext::AllButtons; + QPoint editPoint(qPlatformWindow->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint())); + uint32_t buttons = readOnly ? EditContext::CopyButton | EditContext::SelectAllButton + : EditContext::AllButtons; - QtAndroidInput::updateHandles(m_handleMode, editPoint, buttons, leftPoint, rightPoint, - query.value(Qt::ImCurrentSelection).toString().isRightToLeft()); - m_hideCursorHandleTimer.stop(); + QtAndroidInput::updateHandles(m_handleMode, editPoint, buttons, leftPoint, rightPoint, + query.value(Qt::ImCurrentSelection).toString().isRightToLeft()); + m_hideCursorHandleTimer.stop(); + } } /* @@ -809,7 +788,15 @@ void QAndroidInputContext::touchDown(int x, int y) focusObjectStopComposing(); } - updateSelectionHandles(); + // Check if cursor is visible in focused window before updating handles + QPlatformWindow *window = qGuiApp->focusWindow()->handle(); + const QRectF curRect = cursorRectangle(); + const QPoint cursorGlobalPoint = window->mapToGlobal(QPoint(curRect.x(), curRect.y())); + const QRect windowRect = QPlatformInputContext::inputItemClipRectangle().toRect(); + const QRect windowGlobalRect = QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size()); + + if (windowGlobalRect.contains(cursorGlobalPoint.x(), cursorGlobalPoint.y())) + updateSelectionHandles(); } } @@ -916,10 +903,15 @@ void QAndroidInputContext::showInputPanel() if (query.isNull()) return; + if (!qGuiApp->focusWindow()->handle()) + return; // not a real window, probably VR/XR + disconnect(m_updateCursorPosConnection); + m_updateCursorPosConnection = {}; + if (qGuiApp->focusObject()->metaObject()->indexOfSignal("cursorPositionChanged(int,int)") >= 0) // QLineEdit breaks the pattern m_updateCursorPosConnection = connect(qGuiApp->focusObject(), SIGNAL(cursorPositionChanged(int,int)), this, SLOT(updateCursorPosition())); - else + else if (qGuiApp->focusObject()->metaObject()->indexOfSignal("cursorPositionChanged()") >= 0) m_updateCursorPosConnection = connect(qGuiApp->focusObject(), SIGNAL(cursorPositionChanged()), this, SLOT(updateCursorPosition())); QRect rect = screenInputItemRectangle(); @@ -975,7 +967,6 @@ void QAndroidInputContext::setFocusObject(QObject *object) m_focusObject = object; reset(); } - QPlatformInputContext::setFocusObject(object); updateSelectionHandles(); } @@ -1132,6 +1123,25 @@ jboolean QAndroidInputContext::finishComposingText() return JNI_TRUE; } +void QAndroidInputContext::reportFullscreenMode(jboolean enabled) +{ + m_fullScreenMode = enabled; + BatchEditLock batchEditLock(this); + if (!focusObjectStopComposing()) + return; + + if (enabled) + m_handleMode = Hidden; + + updateSelectionHandles(); +} + +// Called in calling thread's context +jboolean QAndroidInputContext::fullscreenMode() +{ + return m_fullScreenMode; +} + bool QAndroidInputContext::focusObjectIsComposing() const { return m_composingCursor != -1; @@ -1143,7 +1153,7 @@ void QAndroidInputContext::focusObjectStartComposing() return; // Composing strings containing newline characters are rare and may cause problems - if (m_composingText.contains(QLatin1Char('\n'))) + if (m_composingText.contains(u'\n')) return; QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); @@ -1226,7 +1236,7 @@ jint QAndroidInputContext::getCursorCapsMode(jint /*reqModes*/) if (focusObjectIsComposing()) surroundingText += QStringView{m_composingText}.left(m_composingCursor - m_composingTextStart); // Add a character to see if it is at the end of the sentence or not - QTextBoundaryFinder finder(QTextBoundaryFinder::Sentence, surroundingText + QLatin1Char('A')); + QTextBoundaryFinder finder(QTextBoundaryFinder::Sentence, surroundingText + u'A'); finder.setPosition(surroundingText.length()); if (finder.isAtBoundary()) atWordBoundary = finder.isAtBoundary(); @@ -1446,7 +1456,7 @@ jboolean QAndroidInputContext::setComposingText(const QString &text, jint newCur const bool focusObjectWasComposing = focusObjectIsComposing(); // Same checks as in focusObjectStartComposing() - if (!m_composingText.isEmpty() && !m_composingText.contains(QLatin1Char('\n')) + if (!m_composingText.isEmpty() && !m_composingText.contains(u'\n') && newAbsoluteCursorPos >= m_composingTextStart && newAbsoluteCursorPos <= m_composingTextStart + m_composingText.length()) m_composingCursor = newAbsoluteCursorPos; @@ -1554,9 +1564,7 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) } if (start < textOffset || end - textOffset > text.length()) { -#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - qWarning("setComposingRegion: failed to retrieve text from composing region"); -#endif + qCDebug(lcQpaInputMethods) << "Warning: setComposingRegion: failed to retrieve text from composing region"; return JNI_TRUE; } |