diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/platforms/ios/qiosinputcontext.h | 26 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosinputcontext.mm | 209 |
2 files changed, 136 insertions, 99 deletions
diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h index eb6b04a2ac..863e503c3b 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.h +++ b/src/plugins/platforms/ios/qiosinputcontext.h @@ -50,6 +50,21 @@ QT_BEGIN_NAMESPACE @class QIOSTextInputResponder; @protocol KeyboardState; +struct KeyboardState +{ + KeyboardState() : + keyboardVisible(false), keyboardAnimating(false), + animationDuration(0), animationCurve(UIViewAnimationCurve(-1)) + {} + + bool keyboardVisible; + bool keyboardAnimating; + QRectF keyboardRect; + CGRect keyboardEndRect; + NSTimeInterval animationDuration; + UIViewAnimationCurve animationCurve; +}; + struct ImeState { ImeState() : currentState(0), focusObject(0) {} @@ -70,6 +85,7 @@ public: void hideInputPanel() Q_DECL_OVERRIDE; bool isInputPanelVisible() const Q_DECL_OVERRIDE; + bool isAnimating() const Q_DECL_OVERRIDE; QRectF keyboardRect() const Q_DECL_OVERRIDE; void update(Qt::InputMethodQueries) Q_DECL_OVERRIDE; @@ -85,7 +101,11 @@ public: void scrollToCursor(); void scroll(int y); + void updateKeyboardState(NSNotification *notification = 0); + const ImeState &imeState() { return m_imeState; }; + const KeyboardState &keyboardState() { return m_keyboardState; }; + bool inputMethodAccepted() const; static QIOSInputContext *instance(); @@ -93,11 +113,9 @@ public: private: UIView* scrollableRootView(); - union { - QIOSKeyboardListener *m_keyboardHideGesture; - id <KeyboardState> m_keyboardState; - }; + QIOSKeyboardListener *m_keyboardHideGesture; QIOSTextInputResponder *m_textResponder; + KeyboardState m_keyboardState; ImeState m_imeState; }; diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index 49eff2b88a..e2e764d231 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -64,19 +64,7 @@ static QUIView *focusView() // ------------------------------------------------------------------------- -@protocol KeyboardState -- (void)updateKeyboardRect; -@property(nonatomic) BOOL keyboardVisible; -@property(nonatomic) BOOL keyboardVisibleAndDocked; -@property(nonatomic, assign) QRectF keyboardRect; -@property(nonatomic) CGRect keyboardEndRect; -@property(nonatomic) NSTimeInterval animationDuration; -@property(nonatomic) UIViewAnimationCurve animationCurve; -@end - -// ------------------------------------------------------------------------- - -@interface QIOSKeyboardListener : UIGestureRecognizer <KeyboardState, UIGestureRecognizerDelegate> { +@interface QIOSKeyboardListener : UIGestureRecognizer <UIGestureRecognizerDelegate> { @private QIOSInputContext *m_context; } @@ -84,14 +72,6 @@ static QUIView *focusView() @implementation QIOSKeyboardListener -// KeyboardState protocol -@synthesize keyboardVisible; -@synthesize keyboardVisibleAndDocked; -@synthesize keyboardRect; -@synthesize keyboardEndRect; -@synthesize animationDuration; -@synthesize animationCurve; - - (id)initWithQIOSInputContext:(QIOSInputContext *)context { if (self = [super initWithTarget:self action:@selector(gestureStateChanged:)]) { @@ -102,24 +82,23 @@ static QUIView *focusView() self.cancelsTouchesInView = NO; self.delaysTouchesEnded = NO; - /// KeyboardState - self.keyboardVisible = NO; - self.keyboardVisibleAndDocked = NO; - self.animationDuration = 0; - self.animationCurve = UIViewAnimationCurveEaseOut; + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [[NSNotificationCenter defaultCenter] - addObserver:self + [notificationCenter addObserver:self selector:@selector(keyboardWillShow:) - name:@"UIKeyboardWillShowNotification" object:nil]; - [[NSNotificationCenter defaultCenter] - addObserver:self + name:UIKeyboardWillShowNotification object:nil]; + [notificationCenter addObserver:self + selector:@selector(keyboardWillOrDidChange:) + name:UIKeyboardDidShowNotification object:nil]; + [notificationCenter addObserver:self selector:@selector(keyboardWillHide:) - name:@"UIKeyboardWillHideNotification" object:nil]; - [[NSNotificationCenter defaultCenter] - addObserver:self + name:UIKeyboardWillHideNotification object:nil]; + [notificationCenter addObserver:self + selector:@selector(keyboardWillOrDidChange:) + name:UIKeyboardDidHideNotification object:nil]; + [notificationCenter addObserver:self selector:@selector(keyboardDidChangeFrame:) - name:@"UIKeyboardDidChangeFrameNotification" object:nil]; + name:UIKeyboardDidChangeFrameNotification object:nil]; } return self; @@ -127,41 +106,16 @@ static QUIView *focusView() - (void)dealloc { - [[NSNotificationCenter defaultCenter] - removeObserver:self - name:@"UIKeyboardWillShowNotification" object:nil]; - [[NSNotificationCenter defaultCenter] - removeObserver:self - name:@"UIKeyboardWillHideNotification" object:nil]; - [[NSNotificationCenter defaultCenter] - removeObserver:self - name:@"UIKeyboardDidChangeFrameNotification" object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } -- (void)keyboardDidChangeFrame:(NSNotification *)notification -{ - Q_UNUSED(notification); - - [self updateKeyboardRect]; - - // If the keyboard was visible and docked from before, this is just a geometry - // change (normally caused by an orientation change). In that case, update scroll: - if (self.keyboardVisibleAndDocked) - m_context->scrollToCursor(); -} +// ------------------------------------------------------------------------- - (void)keyboardWillShow:(NSNotification *)notification { - // Note that UIKeyboardWillShowNotification is only sendt when the keyboard is docked. - self.keyboardVisibleAndDocked = YES; - self.keyboardEndRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - - if (!self.animationDuration) { - self.animationDuration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - self.animationCurve = UIViewAnimationCurve([[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]); - } + [self keyboardWillOrDidChange:notification]; UIResponder *firstResponder = [UIResponder currentFirstResponder]; if (![firstResponder isKindOfClass:[QIOSTextInputResponder class]]) @@ -175,9 +129,8 @@ static QUIView *focusView() - (void)keyboardWillHide:(NSNotification *)notification { - // Note that UIKeyboardWillHideNotification is also sendt when the keyboard is undocked. - self.keyboardVisibleAndDocked = NO; - self.keyboardEndRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + [self keyboardWillOrDidChange:notification]; + if (self.state != UIGestureRecognizerStateBegan) { // Only disable the gesture if the hiding of the keyboard was not caused by it. // Otherwise we need to await the final touchEnd callback for doing some clean-up. @@ -186,29 +139,19 @@ static QUIView *focusView() m_context->scroll(0); } -- (void)updateKeyboardRect +- (void)keyboardDidChangeFrame:(NSNotification *)notification { - // QInputmethod::keyboardRectangle() is documented to be in window coordinates. - // If there is no focus window, we return an empty rectangle - UIView *view = focusView(); - QRectF convertedRect = fromCGRect([view convertRect:self.keyboardEndRect fromView:nil]); + [self keyboardWillOrDidChange:notification]; - // Set height to zero if keyboard is hidden. Otherwise the rect will not change - // when the keyboard hides on a scrolled screen (since the keyboard will already - // be at the bottom of the 'screen' in that case) - if (!self.keyboardVisibleAndDocked) - convertedRect.setHeight(0); - - if (convertedRect != self.keyboardRect) { - self.keyboardRect = convertedRect; - m_context->emitKeyboardRectChanged(); - } + // If the keyboard was visible and docked from before, this is just a geometry + // change (normally caused by an orientation change). In that case, update scroll: + if (m_context->isInputPanelVisible()) + m_context->scrollToCursor(); +} - BOOL visible = CGRectIntersectsRect(self.keyboardEndRect, [UIScreen mainScreen].bounds); - if (self.keyboardVisible != visible) { - self.keyboardVisible = visible; - m_context->emitInputPanelVisibleChanged(); - } +- (void)keyboardWillOrDidChange:(NSNotification *)notification +{ + m_context->updateKeyboardState(notification); } // ------------------------------------------------------------------------- @@ -217,7 +160,7 @@ static QUIView *focusView() { [super touchesBegan:touches withEvent:event]; - Q_ASSERT(self.keyboardVisibleAndDocked); + Q_ASSERT(m_context->isInputPanelVisible()); if ([touches count] != 1) self.state = UIGestureRecognizerStateFailed; @@ -231,7 +174,7 @@ static QUIView *focusView() return; CGPoint touchPoint = [[touches anyObject] locationInView:self.view]; - if (CGRectContainsPoint(self.keyboardEndRect, touchPoint)) + if (CGRectContainsPoint(m_context->keyboardState().keyboardEndRect, touchPoint)) self.state = UIGestureRecognizerStateBegan; } @@ -281,7 +224,7 @@ static QUIView *focusView() { [super reset]; - if (!self.keyboardVisibleAndDocked) { + if (!m_context->isInputPanelVisible()) { qImDebug() << "keyboard was hidden, disabling hide-keyboard gesture"; self.enabled = NO; } else { @@ -378,11 +321,75 @@ void QIOSInputContext::clearCurrentFocusObject() static_cast<QWindowPrivate *>(QObjectPrivate::get(focusWindow))->clearFocusObject(); } +// ------------------------------------------------------------------------- + +void QIOSInputContext::updateKeyboardState(NSNotification *notification) +{ + static CGRect currentKeyboardRect = CGRectZero; + + KeyboardState previousState = m_keyboardState; + + if (notification) { + NSDictionary *userInfo = [notification userInfo]; + + CGRect frameBegin = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; + CGRect frameEnd = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + + bool atEndOfKeyboardTransition = [notification.name rangeOfString:@"Did"].location != NSNotFound; + + currentKeyboardRect = atEndOfKeyboardTransition ? frameEnd : frameBegin; + + // 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), + // 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); + + // Used for auto-scroller, and will be used for animation-signal in the future + m_keyboardState.keyboardEndRect = frameEnd; + + if (m_keyboardState.animationCurve < 0) { + // We only set the animation curve the first time it has a valid value, since iOS will sometimes report + // an invalid animation curve even if the keyboard is animating, and we don't want to overwrite the + // curve in that case. + m_keyboardState.animationCurve = UIViewAnimationCurve([[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]); + } + + m_keyboardState.animationDuration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + m_keyboardState.keyboardAnimating = m_keyboardState.animationDuration > 0 && !atEndOfKeyboardTransition; + + qImDebug() << qPrintable(QString::fromNSString(notification.name)) << "from" << fromCGRect(frameBegin) << "to" << fromCGRect(frameEnd) + << "(curve =" << m_keyboardState.animationCurve << "duration =" << m_keyboardState.animationDuration << "s)"; + } else { + qImDebug() << "No notification to update keyboard state based on, just updating keyboard rect"; + } + + if (!focusView() || CGRectIsEmpty(currentKeyboardRect)) + m_keyboardState.keyboardRect = QRectF(); + else // QInputmethod::keyboardRectangle() is documented to be in window coordinates. + m_keyboardState.keyboardRect = fromCGRect([focusView() convertRect:currentKeyboardRect fromView:nil]); + + // Emit for all changed properties + if (m_keyboardState.keyboardVisible != previousState.keyboardVisible) + emitInputPanelVisibleChanged(); + if (m_keyboardState.keyboardAnimating != previousState.keyboardAnimating) + emitAnimatingChanged(); + if (m_keyboardState.keyboardRect != previousState.keyboardRect) + emitKeyboardRectChanged(); +} + bool QIOSInputContext::isInputPanelVisible() const { return m_keyboardState.keyboardVisible; } +bool QIOSInputContext::isAnimating() const +{ + return m_keyboardState.keyboardAnimating; +} + QRectF QIOSInputContext::keyboardRect() const { return m_keyboardState.keyboardRect; @@ -392,7 +399,7 @@ QRectF QIOSInputContext::keyboardRect() const void QIOSInputContext::cursorRectangleChanged() { - if (!m_keyboardState.keyboardVisibleAndDocked || !qApp->focusObject()) + if (!isInputPanelVisible() || !qApp->focusObject()) return; // Check if the cursor has changed position inside the input item. Since @@ -438,6 +445,12 @@ void QIOSInputContext::scrollToCursor() if (rootView.window != focusView().window) return; + // We only support auto-scroll for docked keyboards for now, so make sure that's the case + if (CGRectGetMaxY(m_keyboardState.keyboardEndRect) != CGRectGetMaxY([UIScreen mainScreen].bounds)) { + qImDebug() << "Keyboard not docked, ignoring request to scroll to reveal cursor"; + return; + } + const int margin = 20; QRectF translatedCursorPos = qApp->inputMethod()->cursorRectangle(); translatedCursorPos.translate(focusView().qwindow->geometry().topLeft()); @@ -490,8 +503,12 @@ void QIOSInputContext::scroll(int y) [rootView.qtViewController updateProperties]; } completion:^(BOOL){ - if (self) - [m_keyboardState updateKeyboardRect]; + if (self) { + // Scrolling the root view results in the keyboard being moved + // relative to the focus window, so we need to re-evaluate the + // keyboard rectangle. + updateKeyboardState(); + } } ]; } @@ -516,7 +533,7 @@ void QIOSInputContext::setFocusObject(QObject *focusObject) reset(); - if (m_keyboardState.keyboardVisibleAndDocked) + if (isInputPanelVisible()) scrollToCursor(); } @@ -528,9 +545,11 @@ void QIOSInputContext::focusWindowChanged(QWindow *focusWindow) reset(); - [m_keyboardState updateKeyboardRect]; + // The keyboard rectangle depend on the focus window, so + // we need to re-evaluate the keyboard state. + updateKeyboardState(); - if (m_keyboardState.keyboardVisibleAndDocked) + if (isInputPanelVisible()) scrollToCursor(); } |