summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios/qiosinputcontext.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/ios/qiosinputcontext.mm')
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm127
1 files changed, 63 insertions, 64 deletions
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index 0eb12498b4..5716ad041e 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.mm
+++ b/src/plugins/platforms/ios/qiosinputcontext.mm
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 The Qt Company Ltd.
-** 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) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qiosinputcontext.h"
@@ -54,6 +18,8 @@
#include <QGuiApplication>
#include <QtGui/private/qwindow_p.h>
+#include <QtCore/qpointer.h>
+
// -------------------------------------------------------------------------
static QUIView *focusView()
@@ -155,12 +121,12 @@ static QUIView *focusView()
{
[self keyboardWillOrDidChange:notification];
- UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
if (![firstResponder isKindOfClass:[QIOSTextInputResponder class]])
return;
// Enable hide-keyboard gesture
- self.enabled = YES;
+ self.enabled = m_context->isInputPanelVisible();
m_context->scrollToCursor();
}
@@ -210,7 +176,11 @@ static QUIView *focusView()
{
[super touchesBegan:touches withEvent:event];
- Q_ASSERT(m_context->isInputPanelVisible());
+ if (!m_context->isInputPanelVisible()) {
+ qImDebug("keyboard was hidden by sliding it down, disabling hide-keyboard gesture");
+ self.enabled = NO;
+ return;
+ }
if ([touches count] != 1)
self.state = UIGestureRecognizerStateFailed;
@@ -264,7 +234,7 @@ static QUIView *focusView()
if (self.state == UIGestureRecognizerStateBegan) {
qImDebug("hide keyboard gesture was triggered");
- UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]);
[firstResponder resignFirstResponder];
}
@@ -333,11 +303,7 @@ QIOSInputContext::QIOSInputContext()
, m_keyboardHideGesture([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this])
, m_textResponder(0)
{
- if (isQtApplication()) {
- QIOSScreen *iosScreen = static_cast<QIOSScreen*>(QGuiApplication::primaryScreen()->handle());
- [iosScreen->uiWindow() addGestureRecognizer:m_keyboardHideGesture];
- }
-
+ Q_ASSERT(!qGuiApp->focusWindow());
connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &QIOSInputContext::focusWindowChanged);
}
@@ -382,7 +348,7 @@ void QIOSInputContext::clearCurrentFocusObject()
void QIOSInputContext::updateKeyboardState(NSNotification *notification)
{
-#ifdef Q_OS_TVOS
+#if defined(Q_OS_TVOS) || defined(Q_OS_VISIONOS)
Q_UNUSED(notification);
#else
static CGRect currentKeyboardRect = CGRectZero;
@@ -402,7 +368,7 @@ void QIOSInputContext::updateKeyboardState(NSNotification *notification)
// The isInputPanelVisible() property is based on whether or not the virtual keyboard
// is visible on screen, and does not follow the logic of the iOS WillShow and WillHide
// notifications which are not emitted for undocked keyboards, and are buggy when dealing
- // with input-accesosory-views. The reason for using frameEnd here (the future state),
+ // with input-accessory-views. The reason for using frameEnd here (the future state),
// instead of the current state reflected in frameBegin, is that QInputMethod::isVisible()
// is documented to reflect the future state in the case of animated transitions.
m_keyboardState.keyboardVisible = CGRectIntersectsRect(frameEnd, [UIScreen mainScreen].bounds);
@@ -472,6 +438,7 @@ UIView *QIOSInputContext::scrollableRootView()
void QIOSInputContext::scrollToCursor()
{
+#if !defined(Q_OS_VISIONOS)
if (!isQtApplication())
return;
@@ -499,8 +466,9 @@ void QIOSInputContext::scrollToCursor()
return;
}
- QWindow *focusWindow = qApp->focusWindow();
- QRect cursorRect = qApp->inputMethod()->cursorRectangle().translated(focusWindow->geometry().topLeft()).toRect();
+ QPlatformWindow *focusWindow = qApp->focusWindow()->handle();
+ QRect windowCurosorRect = QPlatformInputContext::cursorRectangle().toRect();
+ QRect cursorRect = QRect(focusWindow->mapToGlobal(windowCurosorRect.topLeft()), windowCurosorRect.size());
// We explicitly ask for the geometry of the screen instead of the availableGeometry,
// as we hide the status bar when scrolling the screen, so the available geometry will
@@ -527,6 +495,7 @@ void QIOSInputContext::scrollToCursor()
} else {
scroll(0);
}
+#endif
}
void QIOSInputContext::scroll(int y)
@@ -638,12 +607,15 @@ void QIOSInputContext::setFocusObject(QObject *focusObject)
void QIOSInputContext::focusWindowChanged(QWindow *focusWindow)
{
- Q_UNUSED(focusWindow);
-
qImDebug() << "new focus window =" << focusWindow;
reset();
+ if (isQtApplication()) {
+ [m_keyboardHideGesture.view removeGestureRecognizer:m_keyboardHideGesture];
+ [focusView().window addGestureRecognizer:m_keyboardHideGesture];
+ }
+
// The keyboard rectangle depend on the focus window, so
// we need to re-evaluate the keyboard state.
updateKeyboardState();
@@ -667,22 +639,30 @@ void QIOSInputContext::update(Qt::InputMethodQueries updatedProperties)
// focus object. We try to detect code paths that fail this assertion and smooth
// over the situation by doing a manual update of the focus object.
if (qApp->focusObject() != m_imeState.focusObject && updatedProperties != Qt::ImQueryAll) {
- qWarning() << "stale focus object" << m_imeState.focusObject << ", doing manual update";
+ qWarning() << "stale focus object" << static_cast<void *>(m_imeState.focusObject)
+ << ", doing manual update";
setFocusObject(qApp->focusObject());
return;
}
// Mask for properties that we are interested in and see if any of them changed
- updatedProperties &= (Qt::ImEnabled | Qt::ImHints | Qt::ImQueryInput | Qt::ImEnterKeyType | Qt::ImPlatformData);
+ updatedProperties &= (Qt::ImEnabled | Qt::ImHints | Qt::ImQueryInput | Qt::ImEnterKeyType | Qt::ImPlatformData | Qt::ImReadOnly);
// Perform update first, so we can trust the value of inputMethodAccepted()
Qt::InputMethodQueries changedProperties = m_imeState.update(updatedProperties);
- if (inputMethodAccepted()) {
+ const bool inputIsReadOnly = m_imeState.currentState.value(Qt::ImReadOnly).toBool();
+
+ if (inputMethodAccepted() || inputIsReadOnly) {
if (!m_textResponder || [m_textResponder needsKeyboardReconfigure:changedProperties]) {
- qImDebug("creating new text responder");
[m_textResponder autorelease];
- m_textResponder = [[QIOSTextInputResponder alloc] initWithInputContext:this];
+ if (inputIsReadOnly) {
+ qImDebug("creating new read-only text responder");
+ m_textResponder = [[QIOSTextResponder alloc] initWithInputContext:this];
+ } else {
+ qImDebug("creating new read/write text responder");
+ m_textResponder = [[QIOSTextInputResponder alloc] initWithInputContext:this];
+ }
} else {
qImDebug("no need to reconfigure keyboard, just notifying input delegate");
[m_textResponder notifyInputDelegate:changedProperties];
@@ -726,12 +706,33 @@ bool QIOSInputContext::inputMethodAccepted() const
*/
void QIOSInputContext::reset()
{
- qImDebug("updating Qt::ImQueryAll and unmarking text");
+ qImDebug("releasing text responder");
+
+ // UIKit will sometimes, for unknown reasons, unset the input delegate on the
+ // current text responder. This seems to happen as a result of us calling
+ // [self.inputDelegate textDidChange:self] from [m_textResponder reset].
+ // But it won't be set to nil directly, only after a character is typed on
+ // the input panel after the reset. This strange behavior seems to be related
+ // to us overriding [QUIView setInteraction] to ignore UITextInteraction. If we
+ // didn't do that, the delegate would be kept. But not overriding that function
+ // has its own share of issues, so it seems better to keep that way for now.
+ // Instead, we choose to recreate the text responder as a brute-force solution
+ // until we have better knowledge of what is going on (or implement the new
+ // UITextInteraction protocol).
+ const auto oldResponder = m_textResponder;
+ [m_textResponder reset];
+ [m_textResponder autorelease];
+ m_textResponder = nullptr;
update(Qt::ImQueryAll);
- [m_textResponder setMarkedText:@"" selectedRange:NSMakeRange(0, 0)];
- [m_textResponder notifyInputDelegate:Qt::ImQueryInput];
+ // If update() didn't end up creating a new text responder, oldResponder will still be
+ // the first responder. In that case we need to resign it, so that the input panel hides.
+ // (the input panel will apparently not hide if the first responder is only released).
+ if ([oldResponder isFirstResponder]) {
+ qImDebug("IM not enabled, resigning autoreleased text responder as first responder");
+ [oldResponder resignFirstResponder];
+ }
}
/*!
@@ -745,9 +746,7 @@ void QIOSInputContext::reset()
void QIOSInputContext::commit()
{
qImDebug("unmarking text");
-
- [m_textResponder unmarkText];
- [m_textResponder notifyInputDelegate:Qt::ImSurroundingText];
+ [m_textResponder commit];
}
QLocale QIOSInputContext::locale() const