diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h index 8850bbf80e..46fe35d884 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.h +++ b/src/plugins/platforms/ios/qiosinputcontext.h @@ -65,7 +65,8 @@ public: void showInputPanel(); void hideInputPanel(); - void hideVirtualKeyboard(); + + void clearCurrentFocusObject(); bool isInputPanelVisible() const; void setFocusObject(QObject *object); @@ -81,10 +82,15 @@ public: const ImeState &imeState() { return m_imeState; }; + bool isReloadingInputViewsFromUpdate() const { return m_isReloadingInputViewsFromUpdate; } + + static QIOSInputContext *instance(); + private: QIOSKeyboardListener *m_keyboardListener; QIOSTextInputResponder *m_textResponder; ImeState m_imeState; + bool m_isReloadingInputViewsFromUpdate; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index 70307f7f54..c038628fd9 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -44,6 +44,7 @@ #import #include "qiosglobal.h" +#include "qiosintegration.h" #include "qiostextresponder.h" #include "qioswindow.h" #include "quiview.h" @@ -206,7 +207,10 @@ static QUIView *focusView() CGPoint p = [[touches anyObject] locationInView:m_viewController.view.window]; if (CGRectContainsPoint(m_keyboardEndRect, p)) { m_keyboardHiddenByGesture = YES; - m_context->hideVirtualKeyboard(); + + UIResponder *firstResponder = [UIResponder currentFirstResponder]; + Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]); + [firstResponder resignFirstResponder]; } [super touchesMoved:touches withEvent:event]; @@ -279,10 +283,16 @@ Qt::InputMethodQueries ImeState::update(Qt::InputMethodQueries properties) // ------------------------------------------------------------------------- +QIOSInputContext *QIOSInputContext::instance() +{ + return static_cast(QIOSIntegration::instance()->inputContext()); +} + QIOSInputContext::QIOSInputContext() : QPlatformInputContext() , m_keyboardListener([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this]) , m_textResponder(0) + , m_isReloadingInputViewsFromUpdate(false) { if (isQtApplication()) connect(qGuiApp->inputMethod(), &QInputMethod::cursorRectangleChanged, this, &QIOSInputContext::cursorRectangleChanged); @@ -310,9 +320,10 @@ void QIOSInputContext::hideInputPanel() // No-op, keyboard controlled fully by platform based on focus } -void QIOSInputContext::hideVirtualKeyboard() +void QIOSInputContext::clearCurrentFocusObject() { - static_cast(QObjectPrivate::get(qApp->focusWindow()))->clearFocusObject(); + if (QWindow *focusWindow = qApp->focusWindow()) + static_cast(QObjectPrivate::get(focusWindow))->clearFocusObject(); } bool QIOSInputContext::isInputPanelVisible() const @@ -452,12 +463,24 @@ void QIOSInputContext::update(Qt::InputMethodQueries updatedProperties) updatedProperties |= (Qt::ImHints | Qt::ImPlatformData); } + qImDebug() << "fw =" << qApp->focusWindow() << "fo =" << qApp->focusObject(); + Qt::InputMethodQueries changedProperties = m_imeState.update(updatedProperties); if (changedProperties & (Qt::ImEnabled | Qt::ImHints | Qt::ImPlatformData)) { // Changes to enablement or hints require virtual keyboard reconfigure - [m_textResponder release]; - m_textResponder = [[QIOSTextInputResponder alloc] initWithInputContext:this]; - [m_textResponder reloadInputViews]; + + qImDebug() << "changed IM properties" << changedProperties << "require keyboard reconfigure"; + + if (inputMethodAccepted()) { + qImDebug() << "replacing text responder with new text responder"; + [m_textResponder autorelease]; + m_textResponder = [[QIOSTextInputResponder alloc] initWithInputContext:this]; + [m_textResponder becomeFirstResponder]; + } else { + qImDebug() << "IM not enabled, reloading input views"; + QScopedValueRollback recursionGuard(m_isReloadingInputViewsFromUpdate, true); + [[UIResponder currentFirstResponder] reloadInputViews]; + } } else { [m_textResponder notifyInputDelegate:changedProperties]; } @@ -497,6 +520,12 @@ void QIOSInputContext::commit() @implementation QUIView (InputMethods) - (void)reloadInputViews { - qApp->inputMethod()->reset(); + if (QIOSInputContext::instance()->isReloadingInputViewsFromUpdate()) { + qImDebug() << "preventing recursion by reloading super"; + [super reloadInputViews]; + } else { + qImDebug() << "reseting input methods"; + qApp->inputMethod()->reset(); + } } @end diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index 54362cde7a..b809fc4b51 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -218,11 +218,61 @@ [super dealloc]; } -- (BOOL)isFirstResponder +- (BOOL)canBecomeFirstResponder { return YES; } +- (BOOL)becomeFirstResponder +{ + FirstResponderCandidate firstResponderCandidate(self); + + qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder]; + + if (![super becomeFirstResponder]) { + qImDebug() << self << "was not allowed to become first responder"; + return NO; + } + + qImDebug() << self << "became first responder"; + + return YES; +} + +- (BOOL)resignFirstResponder +{ + qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder]; + + // Don't allow activation events of the window that we're doing text on behalf on + // to steal responder. + if (FirstResponderCandidate::currentCandidate() == [self nextResponder]) { + qImDebug() << "not allowing parent window to steal responder"; + return NO; + } + + if (![super resignFirstResponder]) + return NO; + + qImDebug() << self << "resigned first responder"; + + // Dismissing the keyboard will trigger resignFirstResponder, but so will + // a regular responder transfer to another window. In the former case, iOS + // will set the new first-responder to our next-responder, and in the latter + // case we'll have an active responder candidate. + if ([UIResponder currentFirstResponder] == [self nextResponder]) { + // We have resigned the keyboard, and transferred back to the parent view, so unset focus object + Q_ASSERT(!FirstResponderCandidate::currentCandidate()); + qImDebug() << "keyboard was closed, clearing focus object"; + m_inputContext->clearCurrentFocusObject(); + } else { + // We've lost responder status because another window was made active + Q_ASSERT(FirstResponderCandidate::currentCandidate()); + } + + return YES; +} + + - (UIResponder*)nextResponder { return qApp->focusWindow() ? @@ -577,7 +627,7 @@ Qt::InputMethodHints imeHints = static_cast([self imValue:Qt::ImHints].toUInt()); if (!(imeHints & Qt::ImhMultiLine)) - m_inputContext->hideVirtualKeyboard(); + [self resignFirstResponder]; return; } diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index 33e5b955e3..c4b92618b1 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -44,6 +44,7 @@ #include "qiosglobal.h" #include "qiosintegration.h" #include "qiosviewcontroller.h" +#include "qiostextresponder.h" #include "qioswindow.h" #include "qiosmenu.h" @@ -222,6 +223,15 @@ if ([responder isKindOfClass:[QUIView class]]) return NO; + // Nor do we want to deactivate the Qt window if the new responder + // is temporarily handling text input on behalf of a Qt window. + if ([responder isKindOfClass:[QIOSTextInputResponder class]]) { + while ((responder = [responder nextResponder])) { + if ([responder isKindOfClass:[QUIView class]]) + return NO; + } + } + return YES; }