summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios
diff options
context:
space:
mode:
authorFrederik Gladhorn <frederik.gladhorn@theqtcompany.com>2014-11-24 13:37:06 +0100
committerFrederik Gladhorn <frederik.gladhorn@theqtcompany.com>2014-11-24 13:39:13 +0100
commit34aba4724f196e34ed02cf50073f41968f119bb6 (patch)
tree0ebdfcabda989ab76ee6de53c6461553c7a767a5 /src/plugins/platforms/ios
parentb86b2a742afae118bf974c82ba966ddb0cae4afb (diff)
parentb1cf07f495e10c93e53651ac03e46ebdaea0a97e (diff)
Merge remote-tracking branch 'origin/5.4' into dev
Conflicts: src/corelib/io/qiodevice.cpp src/plugins/bearer/linux_common/qofonoservice_linux.cpp src/plugins/bearer/linux_common/qofonoservice_linux_p.h src/plugins/platforms/android/qandroidplatformtheme.cpp src/tools/bootstrap/bootstrap.pro src/widgets/styles/qmacstyle_mac.mm Change-Id: Ia02aab6c4598ce74e9c30bb4666d5e2ef000f99b
Diffstat (limited to 'src/plugins/platforms/ios')
-rw-r--r--src/plugins/platforms/ios/qioseventdispatcher.mm15
-rw-r--r--src/plugins/platforms/ios/qiosglobal.h22
-rw-r--r--src/plugins/platforms/ios/qiosglobal.mm45
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.h10
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm223
-rw-r--r--src/plugins/platforms/ios/qiosmenu.mm23
-rw-r--r--src/plugins/platforms/ios/qiosplatformaccessibility.mm20
-rw-r--r--src/plugins/platforms/ios/qiostextresponder.h6
-rw-r--r--src/plugins/platforms/ios/qiostextresponder.mm80
-rw-r--r--src/plugins/platforms/ios/quiview.mm66
-rw-r--r--src/plugins/platforms/ios/quiview_accessibility.mm11
11 files changed, 384 insertions, 137 deletions
diff --git a/src/plugins/platforms/ios/qioseventdispatcher.mm b/src/plugins/platforms/ios/qioseventdispatcher.mm
index ce7dfe2606..ffffc4cbc4 100644
--- a/src/plugins/platforms/ios/qioseventdispatcher.mm
+++ b/src/plugins/platforms/ios/qioseventdispatcher.mm
@@ -241,11 +241,11 @@ enum SetJumpResult
kJumpedFromUserMainTrampoline,
};
-// We define qt_main so that user_main_trampoline() will not cause
+// We define qtmn so that user_main_trampoline() will not cause
// missing symbols in the case of hybrid applications that don't
-// user our main wrapper. Since the symbol is weak, it will not
+// use our main wrapper. Since the symbol is weak, it will not
// get used or cause a clash in the normal Qt application usecase,
-// where we rename main to qt_main.
+// where we rename main to qtmn before linking.
extern "C" int __attribute__((weak)) qtmn(int argc, char *argv[])
{
Q_UNUSED(argc);
@@ -317,11 +317,16 @@ static bool rootLevelRunLoopIntegration()
}
#if defined(Q_PROCESSOR_X86)
-# define SET_STACK_POINTER "mov %0, %%esp"
# define FUNCTION_CALL_ALIGNMENT 16
+# if defined(Q_PROCESSOR_X86_32)
+# define SET_STACK_POINTER "mov %0, %%esp"
+# elif defined(Q_PROCESSOR_X86_64)
+# define SET_STACK_POINTER "movq %0, %%rsp"
+# endif
#elif defined(Q_PROCESSOR_ARM)
-# define SET_STACK_POINTER "mov sp, %0"
+# // Valid for both 32 and 64-bit ARM
# define FUNCTION_CALL_ALIGNMENT 4
+# define SET_STACK_POINTER "mov sp, %0"
#else
# error "Unknown processor family"
#endif
diff --git a/src/plugins/platforms/ios/qiosglobal.h b/src/plugins/platforms/ios/qiosglobal.h
index c09987f9dc..f7b9cd7015 100644
--- a/src/plugins/platforms/ios/qiosglobal.h
+++ b/src/plugins/platforms/ios/qiosglobal.h
@@ -41,6 +41,16 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods);
+
+#if !defined(QT_NO_DEBUG)
+#define qImDebug(...) \
+ for (bool qt_category_enabled = lcQpaInputMethods().isDebugEnabled(); qt_category_enabled; qt_category_enabled = false) \
+ QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, lcQpaInputMethods().categoryName()).debug(__VA_ARGS__)
+#else
+#define qImDebug() QT_NO_QDEBUG_MACRO()
+#endif
+
class QPlatformScreen;
bool isQtApplication();
@@ -52,7 +62,7 @@ QPointF fromCGPoint(const CGPoint &point);
Qt::ScreenOrientation toQtScreenOrientation(UIDeviceOrientation uiDeviceOrientation);
UIDeviceOrientation fromQtScreenOrientation(Qt::ScreenOrientation qtOrientation);
-QRect fromPortraitToPrimary(const QRect &rect, QPlatformScreen *screen);
+
int infoPlistValue(NSString* key, int defaultValue);
QT_END_NAMESPACE
@@ -61,4 +71,14 @@ QT_END_NAMESPACE
+(id)currentFirstResponder;
@end
+class FirstResponderCandidate : public QScopedValueRollback<UIResponder *>
+{
+public:
+ FirstResponderCandidate(UIResponder *);
+ static UIResponder *currentCandidate() { return s_firstResponderCandidate; }
+
+private:
+ static UIResponder *s_firstResponderCandidate;
+};
+
#endif // QIOSGLOBAL_H
diff --git a/src/plugins/platforms/ios/qiosglobal.mm b/src/plugins/platforms/ios/qiosglobal.mm
index 7ff4950599..3ecd0ca61f 100644
--- a/src/plugins/platforms/ios/qiosglobal.mm
+++ b/src/plugins/platforms/ios/qiosglobal.mm
@@ -46,6 +46,8 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
+
bool isQtApplication()
{
// Returns \c true if the plugin is in full control of the whole application. This means
@@ -125,15 +127,6 @@ UIDeviceOrientation fromQtScreenOrientation(Qt::ScreenOrientation qtOrientation)
return uiOrientation;
}
-QRect fromPortraitToPrimary(const QRect &rect, QPlatformScreen *screen)
-{
- // UIScreen is always in portrait. Use this function to convert CGRects
- // aligned with UIScreen into whatever is the current orientation of QScreen.
- QRect geometry = screen->geometry();
- return geometry.width() < geometry.height() ? rect
- : QRect(rect.y(), geometry.height() - rect.width() - rect.x(), rect.height(), rect.width());
-}
-
int infoPlistValue(NSString* key, int defaultValue)
{
static NSBundle *bundle = [NSBundle mainBundle];
@@ -148,6 +141,27 @@ int infoPlistValue(NSString* key, int defaultValue)
@end
@implementation QtFirstResponderEvent
+- (void) dealloc
+{
+ self.firstResponder = 0;
+ [super dealloc];
+}
+@end
+
+
+@implementation UIView (QtFirstResponder)
+- (UIView*)qt_findFirstResponder
+{
+ if ([self isFirstResponder])
+ return self;
+
+ for (UIView *subview in self.subviews) {
+ if (UIView *firstResponder = [subview qt_findFirstResponder])
+ return firstResponder;
+ }
+
+ return nil;
+}
@end
@implementation UIResponder (QtFirstResponder)
@@ -162,9 +176,20 @@ int infoPlistValue(NSString* key, int defaultValue)
- (void)qt_findFirstResponder:(id)sender event:(QtFirstResponderEvent *)event
{
Q_UNUSED(sender);
- event.firstResponder = self;
+
+ if ([self isKindOfClass:[UIView class]])
+ event.firstResponder = [static_cast<UIView *>(self) qt_findFirstResponder];
+ else
+ event.firstResponder = [self isFirstResponder] ? self : nil;
}
@end
+FirstResponderCandidate::FirstResponderCandidate(UIResponder *responder)
+ : QScopedValueRollback<UIResponder *>(s_firstResponderCandidate, responder)
+{
+}
+
+UIResponder *FirstResponderCandidate::s_firstResponderCandidate = 0;
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h
index 8850bbf80e..d2a9c261ba 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.h
+++ b/src/plugins/platforms/ios/qiosinputcontext.h
@@ -42,6 +42,7 @@
const char kImePlatformDataInputView[] = "inputView";
const char kImePlatformDataInputAccessoryView[] = "inputAccessoryView";
+const char kImePlatformDataReturnKeyType[] = "returnKeyType";
QT_BEGIN_NAMESPACE
@@ -50,9 +51,10 @@ QT_BEGIN_NAMESPACE
struct ImeState
{
- ImeState() : currentState(0) {}
+ ImeState() : currentState(0), focusObject(0) {}
Qt::InputMethodQueries update(Qt::InputMethodQueries properties);
QInputMethodQueryEvent currentState;
+ QObject *focusObject;
};
class QIOSInputContext : public QPlatformInputContext
@@ -65,7 +67,8 @@ public:
void showInputPanel();
void hideInputPanel();
- void hideVirtualKeyboard();
+
+ void clearCurrentFocusObject();
bool isInputPanelVisible() const;
void setFocusObject(QObject *object);
@@ -80,6 +83,9 @@ public:
void commit();
const ImeState &imeState() { return m_imeState; };
+ bool inputMethodAccepted() const;
+
+ static QIOSInputContext *instance();
private:
QIOSKeyboardListener *m_keyboardListener;
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index b403154321..e417e9a1fb 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.mm
+++ b/src/plugins/platforms/ios/qiosinputcontext.mm
@@ -44,26 +44,30 @@
#import <UIKit/UIGestureRecognizerSubclass.h>
#include "qiosglobal.h"
+#include "qiosintegration.h"
#include "qiostextresponder.h"
+#include "qiosviewcontroller.h"
#include "qioswindow.h"
#include "quiview.h"
#include <QGuiApplication>
#include <QtGui/private/qwindow_p.h>
+// -------------------------------------------------------------------------
+
static QUIView *focusView()
{
return qApp->focusWindow() ?
reinterpret_cast<QUIView *>(qApp->focusWindow()->winId()) : 0;
}
-@interface QIOSKeyboardListener : UIGestureRecognizer {
+// -------------------------------------------------------------------------
+
+@interface QIOSKeyboardListener : UIGestureRecognizer <UIGestureRecognizerDelegate> {
@public
QIOSInputContext *m_context;
BOOL m_keyboardVisible;
BOOL m_keyboardVisibleAndDocked;
- BOOL m_touchPressWhileKeyboardVisible;
- BOOL m_keyboardHiddenByGesture;
QRectF m_keyboardRect;
CGRect m_keyboardEndRect;
NSTimeInterval m_duration;
@@ -76,13 +80,11 @@ static QUIView *focusView()
- (id)initWithQIOSInputContext:(QIOSInputContext *)context
{
- self = [super initWithTarget:self action:@selector(gestureTriggered)];
+ self = [super initWithTarget:self action:@selector(gestureStateChanged:)];
if (self) {
m_context = context;
m_keyboardVisible = NO;
m_keyboardVisibleAndDocked = NO;
- m_touchPressWhileKeyboardVisible = NO;
- m_keyboardHiddenByGesture = NO;
m_duration = 0;
m_curve = UIViewAnimationCurveEaseOut;
m_viewController = 0;
@@ -98,10 +100,9 @@ static QUIView *focusView()
Q_ASSERT(m_viewController);
// Attach 'hide keyboard' gesture to the window, but keep it disabled when the
- // keyboard is not visible. Note that we never trigger the gesture the way it is intended
- // since we don't want to cancel touch events and interrupt flicking etc. Instead we use
- // the gesture framework more as an event filter and hide the keyboard silently.
+ // keyboard is not visible.
self.enabled = NO;
+ self.cancelsTouchesInView = NO;
self.delaysTouchesEnded = NO;
[m_viewController.view.window addGestureRecognizer:self];
}
@@ -155,11 +156,19 @@ static QUIView *focusView()
// Note that UIKeyboardWillShowNotification is only sendt when the keyboard is docked.
m_keyboardVisibleAndDocked = YES;
m_keyboardEndRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
- self.enabled = YES;
+
if (!m_duration) {
m_duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
m_curve = UIViewAnimationCurve([[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]);
}
+
+ UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ if (![firstResponder isKindOfClass:[QIOSTextInputResponder class]])
+ return;
+
+ // Enable hide-keyboard gesture
+ self.enabled = YES;
+
m_context->scrollToCursor();
}
@@ -168,7 +177,7 @@ static QUIView *focusView()
// Note that UIKeyboardWillHideNotification is also sendt when the keyboard is undocked.
m_keyboardVisibleAndDocked = NO;
m_keyboardEndRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
- if (!m_keyboardHiddenByGesture) {
+ 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.
self.enabled = NO;
@@ -201,51 +210,81 @@ static QUIView *focusView()
}
}
-- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
+// -------------------------------------------------------------------------
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
- CGPoint p = [[touches anyObject] locationInView:m_viewController.view.window];
- if (CGRectContainsPoint(m_keyboardEndRect, p)) {
- m_keyboardHiddenByGesture = YES;
- m_context->hideVirtualKeyboard();
- }
+ [super touchesBegan:touches withEvent:event];
- [super touchesMoved:touches withEvent:event];
+ Q_ASSERT(m_keyboardVisibleAndDocked);
+
+ if ([touches count] != 1)
+ self.state = UIGestureRecognizerStateFailed;
}
-- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
- Q_ASSERT(m_keyboardVisibleAndDocked);
- m_touchPressWhileKeyboardVisible = YES;
- [super touchesBegan:touches withEvent:event];
+ [super touchesMoved:touches withEvent:event];
+
+ if (self.state != UIGestureRecognizerStatePossible)
+ return;
+
+ CGPoint touchPoint = [[touches anyObject] locationInView:m_viewController.view.window];
+ if (CGRectContainsPoint(m_keyboardEndRect, touchPoint))
+ self.state = UIGestureRecognizerStateBegan;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
- m_touchPressWhileKeyboardVisible = NO;
- [self performSelectorOnMainThread:@selector(touchesEndedPostDelivery) withObject:nil waitUntilDone:NO];
[super touchesEnded:touches withEvent:event];
+
+ [self touchesEndedOrCancelled];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
- m_touchPressWhileKeyboardVisible = NO;
- [self performSelectorOnMainThread:@selector(touchesEndedPostDelivery) withObject:nil waitUntilDone:NO];
[super touchesCancelled:touches withEvent:event];
+
+ [self touchesEndedOrCancelled];
+}
+
+- (void)touchesEndedOrCancelled
+{
+ // Defer final state change until next runloop iteration, so that Qt
+ // has a chance to process the final touch events first, before we eg.
+ // scroll the view.
+ dispatch_async(dispatch_get_main_queue (), ^{
+ // iOS will transition from began to changed by itself
+ Q_ASSERT(self.state != UIGestureRecognizerStateBegan);
+
+ if (self.state == UIGestureRecognizerStateChanged)
+ self.state = UIGestureRecognizerStateEnded;
+ else
+ self.state = UIGestureRecognizerStateFailed;
+ });
+}
+
+- (void)gestureStateChanged:(id)sender
+{
+ Q_UNUSED(sender);
+
+ if (self.state == UIGestureRecognizerStateBegan) {
+ qImDebug() << "hide keyboard gesture was triggered";
+ UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]);
+ [firstResponder resignFirstResponder];
+ }
}
-- (void)touchesEndedPostDelivery
+- (void)reset
{
- // Do some clean-up _after_ touchEnd has been delivered to QUIView
- m_keyboardHiddenByGesture = NO;
+ [super reset];
+
if (!m_keyboardVisibleAndDocked) {
+ qImDebug() << "keyboard was hidden, disabling hide-keyboard gesture";
self.enabled = NO;
- if (qApp->focusObject()) {
- // UI Controls are told to gain focus on touch release. So when the 'hide keyboard' gesture
- // finishes, the final touch end can trigger a control to gain focus. This is in conflict with
- // the gesture, so we clear focus once more as a work-around.
- static_cast<QWindowPrivate *>(QObjectPrivate::get(qApp->focusWindow()))->clearFocusObject();
- }
} else {
+ qImDebug() << "gesture completed without triggering, scrolling view to cursor";
m_context->scrollToCursor();
}
}
@@ -261,8 +300,11 @@ Qt::InputMethodQueries ImeState::update(Qt::InputMethodQueries properties)
QInputMethodQueryEvent newState(properties);
- if (qApp && qApp->focusObject())
- QCoreApplication::sendEvent(qApp->focusObject(), &newState);
+ // Update the focus object that the new state is based on
+ focusObject = qApp ? qApp->focusObject() : 0;
+
+ if (focusObject)
+ QCoreApplication::sendEvent(focusObject, &newState);
Qt::InputMethodQueries updatedProperties;
for (uint i = 0; i < (sizeof(Qt::ImQueryAll) * CHAR_BIT); ++i) {
@@ -279,6 +321,11 @@ Qt::InputMethodQueries ImeState::update(Qt::InputMethodQueries properties)
// -------------------------------------------------------------------------
+QIOSInputContext *QIOSInputContext::instance()
+{
+ return static_cast<QIOSInputContext *>(QIOSIntegration::instance()->inputContext());
+}
+
QIOSInputContext::QIOSInputContext()
: QPlatformInputContext()
, m_keyboardListener([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this])
@@ -303,16 +350,29 @@ QRectF QIOSInputContext::keyboardRect() const
void QIOSInputContext::showInputPanel()
{
// No-op, keyboard controlled fully by platform based on focus
+ qImDebug() << "can't show virtual keyboard without a focus object, ignoring";
}
void QIOSInputContext::hideInputPanel()
{
- // No-op, keyboard controlled fully by platform based on focus
+ if (![m_textResponder isFirstResponder]) {
+ qImDebug() << "QIOSTextInputResponder is not first responder, ignoring";
+ return;
+ }
+
+ if (qGuiApp->focusObject() != m_imeState.focusObject) {
+ qImDebug() << "current focus object does not match IM state, likely hiding from focusOut event, so ignoring";
+ return;
+ }
+
+ qImDebug() << "hiding VKB as requested by QInputMethod::hide()";
+ [m_textResponder resignFirstResponder];
}
-void QIOSInputContext::hideVirtualKeyboard()
+void QIOSInputContext::clearCurrentFocusObject()
{
- static_cast<QWindowPrivate *>(QObjectPrivate::get(qApp->focusWindow()))->clearFocusObject();
+ if (QWindow *focusWindow = qApp->focusWindow())
+ static_cast<QWindowPrivate *>(QObjectPrivate::get(focusWindow))->clearFocusObject();
}
bool QIOSInputContext::isInputPanelVisible() const
@@ -342,10 +402,10 @@ void QIOSInputContext::scrollToCursor()
if (!isQtApplication())
return;
- if (m_keyboardListener->m_touchPressWhileKeyboardVisible) {
- // Don't scroll to the cursor if the user is touching the screen. This
- // interferes with selection and the 'hide keyboard' gesture. Instead
- // we update scrolling upon touchEnd.
+ if (m_keyboardListener.state == UIGestureRecognizerStatePossible && m_keyboardListener.numberOfTouches == 1) {
+ // Don't scroll to the cursor if the user is touching the screen and possibly
+ // trying to trigger the hide-keyboard gesture.
+ qImDebug() << "preventing scrolling to cursor as we're still waiting for a possible gesture";
return;
}
@@ -415,6 +475,18 @@ void QIOSInputContext::setFocusObject(QObject *focusObject)
{
Q_UNUSED(focusObject);
+ qImDebug() << "new focus object =" << focusObject;
+
+ if (m_keyboardListener.state == UIGestureRecognizerStateChanged) {
+ // A new focus object may be set as part of delivering touch events to
+ // application during the hide-keyboard gesture, but we don't want that
+ // to result in a new object getting focus and bringing the keyboard up
+ // again.
+ qImDebug() << "clearing focus object" << focusObject << "as hide-keyboard gesture is active";
+ clearCurrentFocusObject();
+ return;
+ }
+
reset();
if (m_keyboardListener->m_keyboardVisibleAndDocked)
@@ -425,6 +497,8 @@ void QIOSInputContext::focusWindowChanged(QWindow *focusWindow)
{
Q_UNUSED(focusWindow);
+ qImDebug() << "new focus window =" << focusWindow;
+
reset();
[m_keyboardListener handleKeyboardRectChanged];
@@ -443,17 +517,59 @@ void QIOSInputContext::update(Qt::InputMethodQueries updatedProperties)
// Mask for properties that we are interested in and see if any of them changed
updatedProperties &= (Qt::ImEnabled | Qt::ImHints | Qt::ImQueryInput | Qt::ImPlatformData);
+ if (updatedProperties & Qt::ImEnabled) {
+ // Switching on and off input-methods needs a re-fresh of hints and platform
+ // data when we turn them on again, as the IM state we have may have been
+ // invalidated when IM was switched off. We could defer this until we know
+ // if IM was turned on, to limit the extra query parameters, but for simplicity
+ // we always do the update.
+ 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 if ([UIResponder currentFirstResponder] == m_textResponder) {
+ qImDebug() << "IM not enabled, resigning text responder as first responder";
+ [m_textResponder resignFirstResponder];
+ } else {
+ qImDebug() << "IM not enabled. Text responder not first responder. Nothing to do";
+ }
} else {
[m_textResponder notifyInputDelegate:changedProperties];
}
}
+bool QIOSInputContext::inputMethodAccepted() const
+{
+ // The IM enablement state is based on the last call to update()
+ bool lastKnownImEnablementState = m_imeState.currentState.value(Qt::ImEnabled).toBool();
+
+#if !defined(QT_NO_DEBUG)
+ // QPlatformInputContext keeps a cached value of the current IM enablement state that is
+ // updated by QGuiApplication when the current focus object changes, or by QInputMethod's
+ // update() function. If the focus object changes, but the change is not propagated as
+ // a signal to QGuiApplication due to bugs in the widget/graphicsview/qml stack, we'll
+ // end up with a stale value for QPlatformInputContext::inputMethodAccepted(). To be on
+ // the safe side we always use our own cached value to decide if IM is enabled, and try
+ // to detect the case where the two values are out of sync.
+ if (lastKnownImEnablementState != QPlatformInputContext::inputMethodAccepted())
+ qWarning("QPlatformInputContext::inputMethodAccepted() does not match actual focus object IM enablement!");
+#endif
+
+ return lastKnownImEnablementState;
+}
+
/*!
Called by the input item to reset the input method state.
*/
@@ -478,16 +594,3 @@ void QIOSInputContext::commit()
[m_textResponder unmarkText];
[m_textResponder notifyInputDelegate:Qt::ImSurroundingText];
}
-
-// -------------------------------------------------------------------------
-
-@interface QUIView (InputMethods)
-- (void)reloadInputViews;
-@end
-
-@implementation QUIView (InputMethods)
-- (void)reloadInputViews
-{
- qApp->inputMethod()->reset();
-}
-@end
diff --git a/src/plugins/platforms/ios/qiosmenu.mm b/src/plugins/platforms/ios/qiosmenu.mm
index 005b06547e..0f351f785b 100644
--- a/src/plugins/platforms/ios/qiosmenu.mm
+++ b/src/plugins/platforms/ios/qiosmenu.mm
@@ -153,13 +153,29 @@ static NSString *const kSelectorPrefix = @"_qtMenuItem_";
[self setDelegate:self];
[self setDataSource:self];
[self selectRow:m_selectedRow inComponent:0 animated:false];
+ [self listenForKeyboardWillHideNotification:YES];
}
return self;
}
+-(void)listenForKeyboardWillHideNotification:(BOOL)listen
+{
+ if (listen) {
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(cancelMenu)
+ name:@"UIKeyboardWillHideNotification" object:nil];
+ } else {
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:@"UIKeyboardWillHideNotification" object:nil];
+ }
+}
+
-(void)dealloc
{
+ [self listenForKeyboardWillHideNotification:NO];
self.toolbar = 0;
[super dealloc];
}
@@ -193,6 +209,7 @@ static NSString *const kSelectorPrefix = @"_qtMenuItem_";
- (void)closeMenu
{
+ [self listenForKeyboardWillHideNotification:NO];
if (!m_visibleMenuItems.isEmpty())
QIOSMenu::currentMenu()->handleItemSelected(m_visibleMenuItems.at(m_selectedRow));
else
@@ -201,6 +218,7 @@ static NSString *const kSelectorPrefix = @"_qtMenuItem_";
- (void)cancelMenu
{
+ [self listenForKeyboardWillHideNotification:NO];
QIOSMenu::currentMenu()->dismiss();
}
@@ -452,11 +470,11 @@ void QIOSMenu::toggleShowUsingUIPickerView(bool show)
Q_ASSERT(!focusObjectWithPickerView);
focusObjectWithPickerView = qApp->focusWindow()->focusObject();
focusObjectWithPickerView->installEventFilter(this);
- qApp->inputMethod()->update(Qt::ImPlatformData);
+ qApp->inputMethod()->update(Qt::ImEnabled | Qt::ImPlatformData);
} else {
Q_ASSERT(focusObjectWithPickerView);
focusObjectWithPickerView->removeEventFilter(this);
- qApp->inputMethod()->update(Qt::ImPlatformData);
+ qApp->inputMethod()->update(Qt::ImEnabled | Qt::ImPlatformData);
focusObjectWithPickerView = 0;
Q_ASSERT(m_pickerView);
@@ -477,6 +495,7 @@ bool QIOSMenu::eventFilter(QObject *obj, QEvent *event)
imPlatformData.insert(kImePlatformDataInputView, QVariant::fromValue(static_cast<void *>(m_pickerView)));
imPlatformData.insert(kImePlatformDataInputAccessoryView, QVariant::fromValue(static_cast<void *>(m_pickerView.toolbar)));
queryEvent->setValue(Qt::ImPlatformData, imPlatformData);
+ queryEvent->setValue(Qt::ImEnabled, true);
return true;
}
diff --git a/src/plugins/platforms/ios/qiosplatformaccessibility.mm b/src/plugins/platforms/ios/qiosplatformaccessibility.mm
index ad8bd9bdf4..705c626272 100644
--- a/src/plugins/platforms/ios/qiosplatformaccessibility.mm
+++ b/src/plugins/platforms/ios/qiosplatformaccessibility.mm
@@ -58,16 +58,16 @@ void invalidateCache(QAccessibleInterface *iface)
return;
}
- QWindow *win = 0;
- QAccessibleInterface *parent = iface;
- do {
- win = parent->window();
- parent = parent->parent();
- } while (!win && parent);
-
- if (win && win->handle()) {
- QIOSWindow *window = static_cast<QIOSWindow*>(win->handle());
- window->clearAccessibleCache();
+ // This will invalidate everything regardless of what window the
+ // interface belonged to. We might want to revisit this strategy later.
+ // (Therefore this function still takes the interface as argument)
+ // It is also responsible for the bug that focus gets temporary lost
+ // when items get added or removed from the screen
+ foreach (QWindow *win, QGuiApplication::topLevelWindows()) {
+ if (win && win->handle()) {
+ QIOSWindow *window = static_cast<QIOSWindow*>(win->handle());
+ window->clearAccessibleCache();
+ }
}
}
diff --git a/src/plugins/platforms/ios/qiostextresponder.h b/src/plugins/platforms/ios/qiostextresponder.h
index 2923feba3b..118ab8958a 100644
--- a/src/plugins/platforms/ios/qiostextresponder.h
+++ b/src/plugins/platforms/ios/qiostextresponder.h
@@ -47,12 +47,10 @@ class QIOSInputContext;
@interface QIOSTextInputResponder : UIResponder <UITextInputTraits, UIKeyInput, UITextInput>
{
- @public
- QString m_markedText;
- BOOL m_inSendEventToFocusObject;
-
@private
QIOSInputContext *m_inputContext;
+ QString m_markedText;
+ BOOL m_inSendEventToFocusObject;
}
- (id)initWithInputContext:(QIOSInputContext *)context;
diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm
index 54362cde7a..2fcc7258f7 100644
--- a/src/plugins/platforms/ios/qiostextresponder.mm
+++ b/src/plugins/platforms/ios/qiostextresponder.mm
@@ -173,9 +173,13 @@
m_inSendEventToFocusObject = NO;
m_inputContext = inputContext;
+ QVariantMap platformData = [self imValue:Qt::ImPlatformData].toMap();
Qt::InputMethodHints hints = Qt::InputMethodHints([self imValue:Qt::ImHints].toUInt());
- self.returnKeyType = (hints & Qt::ImhMultiLine) ? UIReturnKeyDefault : UIReturnKeyDone;
+ self.returnKeyType = platformData.value(kImePlatformDataReturnKeyType).isValid() ?
+ UIReturnKeyType(platformData.value(kImePlatformDataReturnKeyType).toInt()) :
+ (hints & Qt::ImhMultiLine) ? UIReturnKeyDefault : UIReturnKeyDone;
+
self.secureTextEntry = BOOL(hints & Qt::ImhHiddenText);
self.autocorrectionType = (hints & Qt::ImhNoPredictiveText) ?
UITextAutocorrectionTypeNo : UITextAutocorrectionTypeDefault;
@@ -202,7 +206,6 @@
else
self.keyboardType = UIKeyboardTypeDefault;
- QVariantMap platformData = [self imValue:Qt::ImPlatformData].toMap();
if (UIView *inputView = static_cast<UIView *>(platformData.value(kImePlatformDataInputView).value<void *>()))
self.inputView = [[[WrapperView alloc] initWithView:inputView] autorelease];
if (UIView *accessoryView = static_cast<UIView *>(platformData.value(kImePlatformDataInputAccessoryView).value<void *>()))
@@ -218,30 +221,68 @@
[super dealloc];
}
-- (BOOL)isFirstResponder
+- (BOOL)canBecomeFirstResponder
{
return YES;
}
-- (UIResponder*)nextResponder
+- (BOOL)becomeFirstResponder
{
- return qApp->focusWindow() ?
- reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
+ 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;
}
-/*!
- iOS uses [UIResponder(Internal) _requiresKeyboardWhenFirstResponder] to check if the
- current responder should bring up the keyboard, which in turn checks if the responder
- supports the UIKeyInput protocol. By dynamically reporting our protocol conformance
- we can control the keyboard visibility depending on whether or not we have a focus
- object with IME enabled.
-*/
-- (BOOL)conformsToProtocol:(Protocol *)protocol
+- (BOOL)resignFirstResponder
{
- if (protocol == @protocol(UIKeyInput))
- return m_inputContext->inputMethodAccepted();
+ qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
- return [super conformsToProtocol:protocol];
+ // 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 Qt window was made active,
+ // another QIOSTextResponder was made first-responder, another UIView was
+ // made first-responder, or the first-responder was cleared globally. In
+ // either of these cases we don't have to do anything.
+ qImDebug() << "lost first responder, but not clearing focus object";
+ }
+
+ return YES;
+}
+
+
+- (UIResponder*)nextResponder
+{
+ return qApp->focusWindow() ?
+ reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
}
// -------------------------------------------------------------------------
@@ -575,9 +616,8 @@
[self sendEventToFocusObject:press];
[self sendEventToFocusObject:release];
- Qt::InputMethodHints imeHints = static_cast<Qt::InputMethodHints>([self imValue:Qt::ImHints].toUInt());
- if (!(imeHints & Qt::ImhMultiLine))
- m_inputContext->hideVirtualKeyboard();
+ if (self.returnKeyType == UIReturnKeyDone)
+ [self resignFirstResponder];
return;
}
diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm
index c46ed4c0b1..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"
@@ -191,33 +192,66 @@
- (BOOL)becomeFirstResponder
{
- if ([super becomeFirstResponder]) {
+ FirstResponderCandidate firstResponderCandidate(self);
+
+ qImDebug() << "win:" << m_qioswindow->window() << "self:" << self
+ << "first:" << [UIResponder currentFirstResponder];
+
+ if (![super becomeFirstResponder]) {
+ qImDebug() << m_qioswindow->window()
+ << "was not allowed to become first responder";
+ return NO;
+ }
+
+ qImDebug() << m_qioswindow->window() << "became first responder";
+
+ if (qGuiApp->focusWindow() != m_qioswindow->window()) {
QWindowSystemInterface::handleWindowActivated(m_qioswindow->window());
QWindowSystemInterface::flushWindowSystemEvents();
+ } else {
+ qImDebug() << m_qioswindow->window()
+ << "already active, not sending window activation";
+ }
+
+ return YES;
+}
- return YES;
+- (BOOL)responderShouldTriggerWindowDeactivation:(UIResponder *)responder
+{
+ // We don't want to send window deactivation in case the resign
+ // was a result of another Qt window becoming first responder.
+ 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 NO;
+ return YES;
}
- (BOOL)resignFirstResponder
{
- if ([super resignFirstResponder]) {
- // We don't want to send window deactivation in case we're in the process
- // of activating another window. The handleWindowActivated of the activation
- // will take care of both.
- dispatch_async(dispatch_get_main_queue (), ^{
- if (![[UIResponder currentFirstResponder] isKindOfClass:[QUIView class]]) {
- QWindowSystemInterface::handleWindowActivated(0);
- QWindowSystemInterface::flushWindowSystemEvents();
- }
- });
-
- return YES;
+ qImDebug() << "win:" << m_qioswindow->window() << "self:" << self
+ << "first:" << [UIResponder currentFirstResponder];
+
+ if (![super resignFirstResponder])
+ return NO;
+
+ qImDebug() << m_qioswindow->window() << "resigned first responder";
+
+ UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
+ if ([self responderShouldTriggerWindowDeactivation:newResponder]) {
+ QWindowSystemInterface::handleWindowActivated(0);
+ QWindowSystemInterface::flushWindowSystemEvents();
}
- return NO;
+ return YES;
}
// -------------------------------------------------------------------------
diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm
index 6565e08302..7e1cb9a4fd 100644
--- a/src/plugins/platforms/ios/quiview_accessibility.mm
+++ b/src/plugins/platforms/ios/quiview_accessibility.mm
@@ -48,7 +48,7 @@
- (void)createAccessibleElement:(QAccessibleInterface *)iface
{
- if (!iface || iface->state().invisible)
+ if (!iface || iface->state().invisible || (iface->text(QAccessible::Name).isEmpty() && iface->text(QAccessible::Value).isEmpty() && iface->text(QAccessible::Description).isEmpty()))
return;
QAccessible::Id accessibleId = QAccessible::uniqueId(iface);
UIAccessibilityElement *elem = [[QMacAccessibilityElement alloc] initWithId: accessibleId withAccessibilityContainer: self];
@@ -60,12 +60,9 @@
if (!iface)
return;
- if (iface->childCount() == 0) {
- [self createAccessibleElement: iface];
- } else {
- for (int i = 0; i < iface->childCount(); ++i)
- [self createAccessibleContainer: iface->child(i)];
- }
+ [self createAccessibleElement: iface];
+ for (int i = 0; i < iface->childCount(); ++i)
+ [self createAccessibleContainer: iface->child(i)];
}
- (void)initAccessibility