diff options
Diffstat (limited to 'src/plugins/platforms/ios')
26 files changed, 1558 insertions, 650 deletions
diff --git a/src/plugins/platforms/ios/ios.pro b/src/plugins/platforms/ios/ios.pro index ffc4ff9b12..82f0bd91c4 100644 --- a/src/plugins/platforms/ios/ios.pro +++ b/src/plugins/platforms/ios/ios.pro @@ -23,7 +23,11 @@ OBJECTIVE_SOURCES = \ qiostheme.mm \ qiosglobal.mm \ qiosservices.mm \ - qiosclipboard.mm + quiview.mm \ + qiosclipboard.mm \ + quiaccessibilityelement.mm \ + qiosplatformaccessibility.mm \ + qiostextresponder.mm \ HEADERS = \ qiosintegration.h \ @@ -40,7 +44,11 @@ HEADERS = \ qiosglobal.h \ qiosservices.h \ quiview.h \ - qiosclipboard.h + qiosclipboard.h \ + quiaccessibilityelement.h \ + qiosplatformaccessibility.h \ + qiostextresponder.h \ OTHER_FILES = \ - quiview_textinput.mm + quiview_textinput.mm \ + quiview_accessibility.mm diff --git a/src/plugins/platforms/ios/qiosapplicationdelegate.h b/src/plugins/platforms/ios/qiosapplicationdelegate.h index 617b740d6e..f3c5c14502 100644 --- a/src/plugins/platforms/ios/qiosapplicationdelegate.h +++ b/src/plugins/platforms/ios/qiosapplicationdelegate.h @@ -45,7 +45,4 @@ #import "qiosviewcontroller.h" @interface QIOSApplicationDelegate : UIResponder <UIApplicationDelegate> - -@property (strong, nonatomic) UIWindow *window; - @end diff --git a/src/plugins/platforms/ios/qiosapplicationdelegate.mm b/src/plugins/platforms/ios/qiosapplicationdelegate.mm index 9cf1047a6b..ef9f924384 100644 --- a/src/plugins/platforms/ios/qiosapplicationdelegate.mm +++ b/src/plugins/platforms/ios/qiosapplicationdelegate.mm @@ -46,47 +46,12 @@ #include "qiosviewcontroller.h" #include "qioswindow.h" -#include <QtGui/private/qguiapplication_p.h> #include <qpa/qplatformintegration.h> #include <QtCore/QtCore> @implementation QIOSApplicationDelegate -@synthesize window; - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - Q_UNUSED(application); - Q_UNUSED(launchOptions); - - self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; - self.window.rootViewController = [[[QIOSViewController alloc] init] autorelease]; - -#if QT_IOS_DEPLOYMENT_TARGET_BELOW(__IPHONE_7_0) - QSysInfo::MacVersion iosVersion = QSysInfo::MacintoshVersion; - - // We prefer to keep the root viewcontroller in fullscreen layout, so that - // we don't have to compensate for the viewcontroller position. This also - // gives us the same behavior on iOS 5/6 as on iOS 7, where full screen layout - // is the only way. - if (iosVersion < QSysInfo::MV_IOS_7_0) - self.window.rootViewController.wantsFullScreenLayout = YES; - - // Use translucent statusbar by default on iOS6 iPhones (unless the user changed - // the default in the Info.plist), so that windows placed under the stausbar are - // still visible, just like on iOS7. - if (iosVersion >= QSysInfo::MV_IOS_6_0 && iosVersion < QSysInfo::MV_IOS_7_0 - && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone - && [UIApplication sharedApplication].statusBarStyle == UIStatusBarStyleDefault) - [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackTranslucent]; -#endif - - self.window.hidden = NO; - - return YES; -} - - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { Q_UNUSED(application); @@ -96,17 +61,13 @@ if (!QGuiApplication::instance()) return NO; - QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); + QIOSIntegration *iosIntegration = QIOSIntegration::instance(); + Q_ASSERT(iosIntegration); + QIOSServices *iosServices = static_cast<QIOSServices *>(iosIntegration->services()); return iosServices->handleUrl(QUrl::fromNSURL(url)); } -- (void)dealloc -{ - [window release]; - [super dealloc]; -} - @end diff --git a/src/plugins/platforms/ios/qiosclipboard.mm b/src/plugins/platforms/ios/qiosclipboard.mm index 0a7b34a216..e18ad53b2c 100644 --- a/src/plugins/platforms/ios/qiosclipboard.mm +++ b/src/plugins/platforms/ios/qiosclipboard.mm @@ -205,6 +205,10 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode) Q_ASSERT(supportsMode(mode)); UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:mode]; + if (!mimeData) { + pb.items = [NSArray array]; + return; + } NSMutableDictionary *pbItem = [NSMutableDictionary dictionaryWithCapacity:mimeData->formats().size()]; foreach (const QString &mimeType, mimeData->formats()) { diff --git a/src/plugins/platforms/ios/qioscontext.mm b/src/plugins/platforms/ios/qioscontext.mm index ddee52196a..0143b75828 100644 --- a/src/plugins/platforms/ios/qioscontext.mm +++ b/src/plugins/platforms/ios/qioscontext.mm @@ -47,19 +47,33 @@ #include <QtGui/QOpenGLContext> #import <OpenGLES/EAGL.h> +#import <OpenGLES/ES2/glext.h> #import <QuartzCore/CAEAGLLayer.h> QIOSContext::QIOSContext(QOpenGLContext *context) : QPlatformOpenGLContext() , m_sharedContext(static_cast<QIOSContext *>(context->shareHandle())) - , m_eaglContext([[EAGLContext alloc] - initWithAPI:kEAGLRenderingAPIOpenGLES2 - sharegroup:m_sharedContext ? [m_sharedContext->m_eaglContext sharegroup] : nil]) , m_format(context->format()) { m_format.setRenderableType(QSurfaceFormat::OpenGLES); - m_format.setMajorVersion(2); - m_format.setMinorVersion(0); + m_eaglContext = [[EAGLContext alloc] + initWithAPI:EAGLRenderingAPI(m_format.majorVersion()) + sharegroup:m_sharedContext ? [m_sharedContext->m_eaglContext sharegroup] : nil]; + + if (m_eaglContext != nil) { + EAGLContext *originalContext = [EAGLContext currentContext]; + [EAGLContext setCurrentContext:m_eaglContext]; + const GLubyte *s = glGetString(GL_VERSION); + if (s) { + QByteArray version = QByteArray(reinterpret_cast<const char *>(s)); + int major, minor; + if (QPlatformOpenGLContext::parseOpenGLVersion(version, major, minor)) { + m_format.setMajorVersion(major); + m_format.setMinorVersion(minor); + } + } + [EAGLContext setCurrentContext:originalContext]; + } // iOS internally double-buffers its rendering using copy instead of flipping, // so technically we could report that we are single-buffered so that clients diff --git a/src/plugins/platforms/ios/qiosglobal.h b/src/plugins/platforms/ios/qiosglobal.h index 17184dc21d..20bebb1f3b 100644 --- a/src/plugins/platforms/ios/qiosglobal.h +++ b/src/plugins/platforms/ios/qiosglobal.h @@ -65,4 +65,8 @@ int infoPlistValue(NSString* key, int defaultValue); QT_END_NAMESPACE +@interface UIResponder (QtFirstResponder) ++(id)currentFirstResponder; +@end + #endif // QIOSGLOBAL_H diff --git a/src/plugins/platforms/ios/qiosglobal.mm b/src/plugins/platforms/ios/qiosglobal.mm index 2ce064582e..7ff4950599 100644 --- a/src/plugins/platforms/ios/qiosglobal.mm +++ b/src/plugins/platforms/ios/qiosglobal.mm @@ -141,5 +141,30 @@ int infoPlistValue(NSString* key, int defaultValue) return value ? [value intValue] : defaultValue; } +// ------------------------------------------------------------------------- + +@interface QtFirstResponderEvent : UIEvent +@property (nonatomic, strong) id firstResponder; +@end + +@implementation QtFirstResponderEvent +@end + +@implementation UIResponder (QtFirstResponder) + ++(id)currentFirstResponder +{ + QtFirstResponderEvent *event = [[[QtFirstResponderEvent alloc] init] autorelease]; + [[UIApplication sharedApplication] sendAction:@selector(qt_findFirstResponder:event:) to:nil from:nil forEvent:event]; + return event.firstResponder; +} + +- (void)qt_findFirstResponder:(id)sender event:(QtFirstResponderEvent *)event +{ + Q_UNUSED(sender); + event.firstResponder = self; +} +@end + QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h index 13255ada56..8d7f45d2bd 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.h +++ b/src/plugins/platforms/ios/qiosinputcontext.h @@ -44,13 +44,24 @@ #include <UIKit/UIKit.h> +#include <QtGui/qevent.h> #include <QtGui/qtransform.h> #include <qpa/qplatforminputcontext.h> +const char kImePlatformDataInputView[] = "inputView"; +const char kImePlatformDataInputAccessoryView[] = "inputAccessoryView"; + QT_BEGIN_NAMESPACE @class QIOSKeyboardListener; -@class QUIView; +@class QIOSTextInputResponder; + +struct ImeState +{ + ImeState() : currentState(0) {} + Qt::InputMethodQueries update(Qt::InputMethodQueries properties); + QInputMethodQueryEvent currentState; +}; class QIOSInputContext : public QPlatformInputContext { @@ -59,8 +70,11 @@ public: ~QIOSInputContext(); QRectF keyboardRect() const; + void showInputPanel(); void hideInputPanel(); + void hideVirtualKeyboard(); + bool isInputPanelVisible() const; void setFocusObject(QObject *object); @@ -73,10 +87,12 @@ public: void reset(); void commit(); + const ImeState &imeState() { return m_imeState; }; + private: QIOSKeyboardListener *m_keyboardListener; - QUIView *m_focusView; - bool m_hasPendingHideRequest; + QIOSTextInputResponder *m_textResponder; + ImeState m_imeState; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index d109d53168..aeef24b0b3 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -44,8 +44,10 @@ #import <UIKit/UIGestureRecognizerSubclass.h> #include "qiosglobal.h" +#include "qiostextresponder.h" #include "qioswindow.h" #include "quiview.h" + #include <QGuiApplication> #include <QtGui/private/qwindow_p.h> @@ -158,23 +160,19 @@ - (void) keyboardWillShow:(NSNotification *)notification { - if ([QUIView inUpdateKeyboardLayout]) - return; // Note that UIKeyboardWillShowNotification is only sendt when the keyboard is docked. m_keyboardVisibleAndDocked = YES; m_keyboardEndRect = [self getKeyboardRect:notification]; self.enabled = YES; if (!m_duration) { m_duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - m_curve = UIViewAnimationCurve([[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16); + m_curve = UIViewAnimationCurve([[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]); } m_context->scrollToCursor(); } - (void) keyboardWillHide:(NSNotification *)notification { - if ([QUIView inUpdateKeyboardLayout]) - return; // Note that UIKeyboardWillHideNotification is also sendt when the keyboard is undocked. m_keyboardVisibleAndDocked = NO; m_keyboardEndRect = [self getKeyboardRect:notification]; @@ -207,7 +205,7 @@ QPointF p = fromCGPoint([[touches anyObject] locationInView:m_viewController.view]); if (m_keyboardRect.contains(p)) { m_keyboardHiddenByGesture = YES; - m_context->hideInputPanel(); + m_context->hideVirtualKeyboard(); } [super touchesMoved:touches withEvent:event]; @@ -253,11 +251,43 @@ @end +// ------------------------------------------------------------------------- + +Qt::InputMethodQueries ImeState::update(Qt::InputMethodQueries properties) +{ + if (!properties) + return 0; + + QInputMethodQueryEvent newState(properties); + + if (qApp && qApp->focusObject()) + QCoreApplication::sendEvent(qApp->focusObject(), &newState); + + Qt::InputMethodQueries updatedProperties; + for (uint i = 0; i < (sizeof(Qt::ImQueryAll) * CHAR_BIT); ++i) { + if (Qt::InputMethodQuery property = Qt::InputMethodQuery(int(properties & (1 << i)))) { + if (newState.value(property) != currentState.value(property)) { + updatedProperties |= property; + currentState.setValue(property, newState.value(property)); + } + } + } + + return updatedProperties; +} + +// ------------------------------------------------------------------------- + +static QUIView *focusView() +{ + return qApp->focusWindow() ? + reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0; +} + QIOSInputContext::QIOSInputContext() : QPlatformInputContext() , m_keyboardListener([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this]) - , m_focusView(0) - , m_hasPendingHideRequest(false) + , m_textResponder(0) { if (isQtApplication()) connect(qGuiApp->inputMethod(), &QInputMethod::cursorRectangleChanged, this, &QIOSInputContext::cursorRectangleChanged); @@ -267,7 +297,7 @@ QIOSInputContext::QIOSInputContext() QIOSInputContext::~QIOSInputContext() { [m_keyboardListener release]; - [m_focusView release]; + [m_textResponder release]; } QRectF QIOSInputContext::keyboardRect() const @@ -277,61 +307,22 @@ QRectF QIOSInputContext::keyboardRect() const void QIOSInputContext::showInputPanel() { - if (m_keyboardListener->m_keyboardHiddenByGesture) { - // We refuse to re-show the keyboard until the touch - // sequence that triggered the gesture has ended. - return; - } - - // Documentation tells that one should call (and recall, if necessary) becomeFirstResponder/resignFirstResponder - // to show/hide the keyboard. This is slightly inconvenient, since there exist no API to get the current first - // responder. Rather than searching for it from the top, we let the active QIOSWindow tell us which view to use. - // Note that Qt will forward keyevents to whichever QObject that needs it, regardless of which UIView the input - // actually came from. So in this respect, we're undermining iOS' responder chain. - m_hasPendingHideRequest = false; - [m_focusView becomeFirstResponder]; + // No-op, keyboard controlled fully by platform based on focus } void QIOSInputContext::hideInputPanel() { - // Delay hiding the keyboard for cases where the user is transferring focus between - // 'line edits'. In that case the 'line edit' that lost focus will close the input - // panel, just to see that the new 'line edit' will open it again: - m_hasPendingHideRequest = true; - dispatch_async(dispatch_get_main_queue(), ^{ - if (m_hasPendingHideRequest) - [m_focusView resignFirstResponder]; - }); + // No-op, keyboard controlled fully by platform based on focus } -bool QIOSInputContext::isInputPanelVisible() const +void QIOSInputContext::hideVirtualKeyboard() { - return m_keyboardListener->m_keyboardVisible; + static_cast<QWindowPrivate *>(QObjectPrivate::get(qApp->focusWindow()))->clearFocusObject(); } -void QIOSInputContext::setFocusObject(QObject *focusObject) -{ - if (!focusObject || !m_focusView || !m_focusView.isFirstResponder) { - scroll(0); - return; - } - - reset(); - - if (m_keyboardListener->m_keyboardVisibleAndDocked) - scrollToCursor(); -} - -void QIOSInputContext::focusWindowChanged(QWindow *focusWindow) +bool QIOSInputContext::isInputPanelVisible() const { - QUIView *view = focusWindow ? reinterpret_cast<QUIView *>(focusWindow->handle()->winId()) : 0; - if ([m_focusView isFirstResponder]) - [view becomeFirstResponder]; - [m_focusView release]; - m_focusView = [view retain]; - - if (view.window != m_keyboardListener->m_viewController.view) - scroll(0); + return m_keyboardListener->m_keyboardVisible; } void QIOSInputContext::cursorRectangleChanged() @@ -353,7 +344,7 @@ void QIOSInputContext::cursorRectangleChanged() void QIOSInputContext::scrollToCursor() { - if (!isQtApplication() || !m_focusView) + if (!isQtApplication()) return; if (m_keyboardListener->m_touchPressWhileKeyboardVisible) { @@ -364,12 +355,12 @@ void QIOSInputContext::scrollToCursor() } UIView *view = m_keyboardListener->m_viewController.view; - if (view.window != m_focusView.window) + if (view.window != focusView().window) return; const int margin = 20; QRectF translatedCursorPos = qApp->inputMethod()->cursorRectangle(); - translatedCursorPos.translate(m_focusView.qwindow->geometry().topLeft()); + translatedCursorPos.translate(focusView().qwindow->geometry().topLeft()); qreal keyboardY = m_keyboardListener->m_keyboardEndRect.y(); int statusBarY = qGuiApp->primaryScreen()->availableGeometry().y(); @@ -389,7 +380,7 @@ void QIOSInputContext::scroll(int y) newBounds.origin.y = y; QPointer<QIOSInputContext> self = this; [UIView animateWithDuration:m_keyboardListener->m_duration delay:0 - options:m_keyboardListener->m_curve | UIViewAnimationOptionBeginFromCurrentState + options:(m_keyboardListener->m_curve << 16) | UIViewAnimationOptionBeginFromCurrentState animations:^{ view.bounds = newBounds; } completion:^(BOOL){ if (self) @@ -398,18 +389,84 @@ void QIOSInputContext::scroll(int y) ]; } -void QIOSInputContext::update(Qt::InputMethodQueries query) +// ------------------------------------------------------------------------- + +void QIOSInputContext::setFocusObject(QObject *focusObject) +{ + Q_UNUSED(focusObject); + + reset(); + + if (m_keyboardListener->m_keyboardVisibleAndDocked) + scrollToCursor(); +} + +void QIOSInputContext::focusWindowChanged(QWindow *focusWindow) +{ + Q_UNUSED(focusWindow); + + reset(); + + if (m_keyboardListener->m_keyboardVisibleAndDocked) + scrollToCursor(); +} + +/*! + Called by the input item to inform the platform input methods when there has been + state changes in editor's input method query attributes. When calling the function + \a queries parameter has to be used to tell what has changes, which input method + can use to make queries for attributes it's interested with QInputMethodQueryEvent. +*/ +void QIOSInputContext::update(Qt::InputMethodQueries updatedProperties) { - [m_focusView updateInputMethodWithQuery:query]; + // Mask for properties that we are interested in and see if any of them changed + updatedProperties &= (Qt::ImEnabled | Qt::ImHints | Qt::ImQueryInput | Qt::ImPlatformData); + + 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]; + } else { + [m_textResponder notifyInputDelegate:changedProperties]; + } } +/*! + Called by the input item to reset the input method state. +*/ void QIOSInputContext::reset() { - [m_focusView reset]; + update(Qt::ImQueryAll); + + [m_textResponder setMarkedText:@"" selectedRange:NSMakeRange(0, 0)]; + [m_textResponder notifyInputDelegate:Qt::ImQueryInput]; } +/*! + Commits the word user is currently composing to the editor. The function is + mostly needed by the input methods with text prediction features and by the + methods where the script used for typing characters is different from the + script that actually gets appended to the editor. Any kind of action that + interrupts the text composing needs to flush the composing state by calling the + commit() function, for example when the cursor is moved elsewhere. +*/ void QIOSInputContext::commit() { - [m_focusView 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/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h index 956c112399..89ca0349d9 100644 --- a/src/plugins/platforms/ios/qiosintegration.h +++ b/src/plugins/platforms/ios/qiosintegration.h @@ -82,14 +82,20 @@ public: void *nativeResourceForWindow(const QByteArray &resource, QWindow *window); QTouchDevice *touchDevice(); + QPlatformAccessibility *accessibility() const Q_DECL_OVERRIDE; + + void addScreen(QPlatformScreen *screen) { screenAdded(screen); } + + static QIOSIntegration *instance(); + private: QPlatformFontDatabase *m_fontDatabase; QPlatformClipboard *m_clipboard; QPlatformInputContext *m_inputContext; - QPlatformScreen *m_screen; QTouchDevice *m_touchDevice; QIOSApplicationState m_applicationState; QIOSServices *m_platformServices; + mutable QPlatformAccessibility *m_accessibility; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm index 0fe7adff9f..9a722ead37 100644 --- a/src/plugins/platforms/ios/qiosintegration.mm +++ b/src/plugins/platforms/ios/qiosintegration.mm @@ -45,12 +45,15 @@ #include "qioswindow.h" #include "qiosbackingstore.h" #include "qiosscreen.h" +#include "qiosplatformaccessibility.h" #include "qioscontext.h" #include "qiosclipboard.h" #include "qiosinputcontext.h" #include "qiostheme.h" #include "qiosservices.h" +#include <QtGui/private/qguiapplication_p.h> + #include <qpa/qplatformoffscreensurface.h> #include <QtPlatformSupport/private/qcoretextfontdatabase_p.h> @@ -61,12 +64,17 @@ QT_BEGIN_NAMESPACE +QIOSIntegration *QIOSIntegration::instance() +{ + return static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); +} + QIOSIntegration::QIOSIntegration() : m_fontDatabase(new QCoreTextFontDatabase) , m_clipboard(new QIOSClipboard) - , m_inputContext(new QIOSInputContext) - , m_screen(new QIOSScreen(QIOSScreen::MainScreen)) + , m_inputContext(0) , m_platformServices(new QIOSServices) + , m_accessibility(0) { if (![UIApplication sharedApplication]) { qWarning() @@ -80,7 +88,11 @@ QIOSIntegration::QIOSIntegration() // Set current directory to app bundle folder QDir::setCurrent(QString::fromUtf8([[[NSBundle mainBundle] bundlePath] UTF8String])); - screenAdded(m_screen); + for (UIScreen *screen in [UIScreen screens]) + addScreen(new QIOSScreen(screen)); + + // Depends on a primary screen being present + m_inputContext = new QIOSInputContext; m_touchDevice = new QTouchDevice; m_touchDevice->setType(QTouchDevice::TouchScreen); @@ -101,11 +113,14 @@ QIOSIntegration::~QIOSIntegration() delete m_inputContext; m_inputContext = 0; - delete m_screen; - m_screen = 0; + foreach (QScreen *screen, QGuiApplication::screens()) + delete screen->handle(); delete m_platformServices; m_platformServices = 0; + + delete m_accessibility; + m_accessibility = 0; } bool QIOSIntegration::hasCapability(Capability cap) const @@ -229,4 +244,11 @@ QTouchDevice *QIOSIntegration::touchDevice() return m_touchDevice; } +QPlatformAccessibility *QIOSIntegration::accessibility() const +{ + if (!m_accessibility) + m_accessibility = new QIOSPlatformAccessibility; + return m_accessibility; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosplatformaccessibility.h b/src/plugins/platforms/ios/qiosplatformaccessibility.h new file mode 100644 index 0000000000..beb0d006e1 --- /dev/null +++ b/src/plugins/platforms/ios/qiosplatformaccessibility.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIOSPLATFORMACCESSIBILITY_H +#define QIOSPLATFORMACCESSIBILITY_H + +#include <qpa/qplatformaccessibility.h> + +QT_BEGIN_NAMESPACE + +class QIOSPlatformAccessibility: public QPlatformAccessibility +{ +public: + QIOSPlatformAccessibility(); + ~QIOSPlatformAccessibility(); + + virtual void notifyAccessibilityUpdate(QAccessibleEvent *event); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/ios/qiosplatformaccessibility.mm b/src/plugins/platforms/ios/qiosplatformaccessibility.mm new file mode 100644 index 0000000000..ad8bd9bdf4 --- /dev/null +++ b/src/plugins/platforms/ios/qiosplatformaccessibility.mm @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qiosplatformaccessibility.h" + +#include <QtGui/QtGui> +#include "qioswindow.h" + +QIOSPlatformAccessibility::QIOSPlatformAccessibility() +{} + +QIOSPlatformAccessibility::~QIOSPlatformAccessibility() +{} + + +void invalidateCache(QAccessibleInterface *iface) +{ + if (!iface || !iface->isValid()) { + qWarning() << "invalid accessible interface: " << 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(); + } +} + + +void QIOSPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) +{ + switch (event->type()) { + case QAccessible::ObjectCreated: + case QAccessible::ObjectShow: + case QAccessible::ObjectHide: + case QAccessible::ObjectDestroyed: + invalidateCache(event->accessibleInterface()); + break; + default: + break; + } +} diff --git a/src/plugins/platforms/ios/qiosscreen.h b/src/plugins/platforms/ios/qiosscreen.h index 173bd11719..9c7d53dcd6 100644 --- a/src/plugins/platforms/ios/qiosscreen.h +++ b/src/plugins/platforms/ios/qiosscreen.h @@ -55,11 +55,9 @@ class QIOSScreen : public QObject, public QPlatformScreen Q_OBJECT public: - QIOSScreen(unsigned int screenIndex); + QIOSScreen(UIScreen *screen); ~QIOSScreen(); - enum ScreenIndex { MainScreen = 0 }; - QRect geometry() const; QRect availableGeometry() const; int depth() const; @@ -82,6 +80,7 @@ public slots: private: UIScreen *m_uiScreen; + UIWindow *m_uiWindow; QRect m_geometry; QRect m_availableGeometry; int m_depth; diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm index 5331d05ae9..eb3b7e3c44 100644 --- a/src/plugins/platforms/ios/qiosscreen.mm +++ b/src/plugins/platforms/ios/qiosscreen.mm @@ -40,6 +40,7 @@ ****************************************************************************/ #include "qiosglobal.h" +#include "qiosintegration.h" #include "qiosscreen.h" #include "qioswindow.h" #include <qpa/qwindowsysteminterface.h> @@ -48,6 +49,63 @@ #include <sys/sysctl.h> +// ------------------------------------------------------------------------- + +static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen) +{ + foreach (QScreen *screen, QGuiApplication::screens()) { + QIOSScreen *platformScreen = static_cast<QIOSScreen *>(screen->handle()); + if (platformScreen->uiScreen() == uiScreen) + return platformScreen; + } + + return 0; +} + +@interface QIOSScreenTracker : NSObject +@end + +@implementation QIOSScreenTracker + ++ (void)load +{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(screenConnected:) + name:UIScreenDidConnectNotification object:nil]; + [center addObserver:self selector:@selector(screenDisconnected:) + name:UIScreenDidDisconnectNotification object:nil]; + [center addObserver:self selector:@selector(screenModeChanged:) + name:UIScreenModeDidChangeNotification object:nil]; +} + ++ (void)screenConnected:(NSNotification*)notification +{ + QIOSIntegration *integration = QIOSIntegration::instance(); + Q_ASSERT_X(integration, Q_FUNC_INFO, "Screen connected before QIOSIntegration creation"); + + integration->addScreen(new QIOSScreen([notification object])); +} + ++ (void)screenDisconnected:(NSNotification*)notification +{ + QIOSScreen *screen = qtPlatformScreenFor([notification object]); + Q_ASSERT_X(screen, Q_FUNC_INFO, "Screen disconnected that we didn't know about"); + + delete screen; +} + ++ (void)screenModeChanged:(NSNotification*)notification +{ + QIOSScreen *screen = qtPlatformScreenFor([notification object]); + Q_ASSERT_X(screen, Q_FUNC_INFO, "Screen changed that we didn't know about"); + + screen->updateProperties(); +} + +@end + +// ------------------------------------------------------------------------- + @interface QIOSOrientationListener : NSObject { @public QIOSScreen *m_screen; @@ -99,6 +157,8 @@ @end +// ------------------------------------------------------------------------- + /*! Returns the model identifier of the device. @@ -118,27 +178,51 @@ static QString deviceModelIdentifier() return QString::fromLatin1(value); } -QIOSScreen::QIOSScreen(unsigned int screenIndex) +QIOSScreen::QIOSScreen(UIScreen *screen) : QPlatformScreen() - , m_uiScreen([[UIScreen screens] count] > screenIndex - ? [[UIScreen screens] objectAtIndex:screenIndex] - : [UIScreen mainScreen]) + , m_uiScreen(screen) + , m_uiWindow(0) , m_orientationListener(0) { - QString deviceIdentifier = deviceModelIdentifier(); - - if (deviceIdentifier == QStringLiteral("iPhone2,1") /* iPhone 3GS */ - || deviceIdentifier == QStringLiteral("iPod3,1") /* iPod touch 3G */) { - m_depth = 18; + if (screen == [UIScreen mainScreen]) { + QString deviceIdentifier = deviceModelIdentifier(); + + if (deviceIdentifier == QStringLiteral("iPhone2,1") /* iPhone 3GS */ + || deviceIdentifier == QStringLiteral("iPod3,1") /* iPod touch 3G */) { + m_depth = 18; + } else { + m_depth = 24; + } + + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad + && !deviceIdentifier.contains(QRegularExpression("^iPad2,[567]$")) /* excluding iPad Mini */) { + m_unscaledDpi = 132; + } else { + m_unscaledDpi = 163; // Regular iPhone DPI + } } else { + // External display, hard to say m_depth = 24; + m_unscaledDpi = 96; } - if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad - && !deviceIdentifier.contains(QRegularExpression("^iPad2,[567]$")) /* excluding iPad Mini */) { - m_unscaledDpi = 132; - } else { - m_unscaledDpi = 163; // Regular iPhone DPI + for (UIWindow *existingWindow in [[UIApplication sharedApplication] windows]) { + if (existingWindow.screen == m_uiScreen) { + m_uiWindow = [m_uiWindow retain]; + break; + } + } + + if (!m_uiWindow) { + // Create a window and associated view-controller that we can use + m_uiWindow = [[UIWindow alloc] initWithFrame:[m_uiScreen bounds]]; + m_uiWindow.rootViewController = [[[QIOSViewController alloc] initWithQIOSScreen:this] autorelease]; + + // FIXME: Only do once windows are added to the screen, and for any screen + if (screen == [UIScreen mainScreen]) { + m_uiWindow.screen = m_uiScreen; + m_uiWindow.hidden = NO; + } } connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &QIOSScreen::updateStatusBarVisibility); @@ -149,40 +233,31 @@ QIOSScreen::QIOSScreen(unsigned int screenIndex) QIOSScreen::~QIOSScreen() { [m_orientationListener release]; + [m_uiWindow release]; } void QIOSScreen::updateProperties() { - UIWindow *uiWindow = 0; - for (uiWindow in [[UIApplication sharedApplication] windows]) { - if (uiWindow.screen == m_uiScreen) - break; - } + QRect previousGeometry = m_geometry; + QRect previousAvailableGeometry = m_availableGeometry; - bool inPortrait = UIInterfaceOrientationIsPortrait(uiWindow.rootViewController.interfaceOrientation); - QRect geometry = inPortrait ? fromCGRect(m_uiScreen.bounds).toRect() + bool inPortrait = UIInterfaceOrientationIsPortrait(m_uiWindow.rootViewController.interfaceOrientation); + m_geometry = inPortrait ? fromCGRect(m_uiScreen.bounds).toRect() : QRect(m_uiScreen.bounds.origin.x, m_uiScreen.bounds.origin.y, m_uiScreen.bounds.size.height, m_uiScreen.bounds.size.width); - if (geometry != m_geometry) { - m_geometry = geometry; - - const qreal millimetersPerInch = 25.4; - m_physicalSize = QSizeF(m_geometry.size()) / m_unscaledDpi * millimetersPerInch; - - QWindowSystemInterface::handleScreenGeometryChange(screen(), m_geometry); - } - - QRect availableGeometry = geometry; + m_availableGeometry = m_geometry; CGSize applicationFrameSize = m_uiScreen.applicationFrame.size; - int statusBarHeight = geometry.height() - (inPortrait ? applicationFrameSize.height : applicationFrameSize.width); + int statusBarHeight = m_geometry.height() - (inPortrait ? applicationFrameSize.height : applicationFrameSize.width); - availableGeometry.adjust(0, statusBarHeight, 0, 0); + m_availableGeometry.adjust(0, statusBarHeight, 0, 0); - if (availableGeometry != m_availableGeometry) { - m_availableGeometry = availableGeometry; - QWindowSystemInterface::handleScreenAvailableGeometryChange(screen(), m_availableGeometry); + if (m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry) { + const qreal millimetersPerInch = 25.4; + m_physicalSize = QSizeF(m_geometry.size()) / m_unscaledDpi * millimetersPerInch; + + QWindowSystemInterface::handleScreenGeometryChange(screen(), m_geometry, m_availableGeometry); } if (screen()) @@ -294,8 +369,15 @@ qreal QIOSScreen::devicePixelRatio() const Qt::ScreenOrientation QIOSScreen::nativeOrientation() const { - // A UIScreen stays in the native orientation, regardless of rotation - return m_uiScreen.bounds.size.width >= m_uiScreen.bounds.size.height ? + CGRect nativeBounds = +#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_8_0) + QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_8_0 ? m_uiScreen.nativeBounds : +#endif + m_uiScreen.bounds; + + // All known iOS devices have a native orientation of portrait, but to + // be on the safe side we compare the width and height of the bounds. + return nativeBounds.size.width >= nativeBounds.size.height ? Qt::LandscapeOrientation : Qt::PortraitOrientation; } diff --git a/src/plugins/platforms/ios/qiostextresponder.h b/src/plugins/platforms/ios/qiostextresponder.h new file mode 100644 index 0000000000..2923feba3b --- /dev/null +++ b/src/plugins/platforms/ios/qiostextresponder.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#import <UIKit/UIKit.h> + +#include <QtCore/qstring.h> + +class QIOSInputContext; + +@interface QIOSTextInputResponder : UIResponder <UITextInputTraits, UIKeyInput, UITextInput> +{ + @public + QString m_markedText; + BOOL m_inSendEventToFocusObject; + + @private + QIOSInputContext *m_inputContext; +} + +- (id)initWithInputContext:(QIOSInputContext *)context; +- (void)notifyInputDelegate:(Qt::InputMethodQueries)updatedProperties; + +@property(readwrite, retain) UIView *inputView; +@property(readwrite, retain) UIView *inputAccessoryView; + +// UITextInputTraits +@property(nonatomic) UITextAutocapitalizationType autocapitalizationType; +@property(nonatomic) UITextAutocorrectionType autocorrectionType; +@property(nonatomic) UITextSpellCheckingType spellCheckingType; +@property(nonatomic) BOOL enablesReturnKeyAutomatically; +@property(nonatomic) UIKeyboardAppearance keyboardAppearance; +@property(nonatomic) UIKeyboardType keyboardType; +@property(nonatomic) UIReturnKeyType returnKeyType; +@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; + +// UITextInput +@property(nonatomic, assign) id<UITextInputDelegate> inputDelegate; + +@end diff --git a/src/plugins/platforms/ios/quiview_textinput.mm b/src/plugins/platforms/ios/qiostextresponder.mm index e65ac1cc46..54362cde7a 100644 --- a/src/plugins/platforms/ios/quiview_textinput.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -39,36 +39,22 @@ ** ****************************************************************************/ -#include <QtGui/qtextformat.h> +#include "qiostextresponder.h" -class StaticVariables -{ -public: - QInputMethodQueryEvent inputMethodQueryEvent; - bool inUpdateKeyboardLayout; - QTextCharFormat markedTextFormat; +#include "qiosglobal.h" +#include "qiosinputcontext.h" +#include "quiview.h" - StaticVariables() - : inputMethodQueryEvent(Qt::ImQueryInput) - , inUpdateKeyboardLayout(false) - { - // There seems to be no way to query how the preedit text - // should be drawn. So we need to hard-code the color. - QSysInfo::MacVersion iosVersion = QSysInfo::MacintoshVersion; - if (iosVersion < QSysInfo::MV_IOS_7_0) - markedTextFormat.setBackground(QColor(235, 239, 247)); - else - markedTextFormat.setBackground(QColor(206, 221, 238)); - } -}; +#include <QtCore/qscopedvaluerollback.h> -Q_GLOBAL_STATIC(StaticVariables, staticVariables); +#include <QtGui/qevent.h> +#include <QtGui/qtextformat.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformwindow.h> // ------------------------------------------------------------------------- @interface QUITextPosition : UITextPosition -{ -} @property (nonatomic) NSUInteger index; + (QUITextPosition *)positionWithIndex:(NSUInteger)index; @@ -89,8 +75,6 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); // ------------------------------------------------------------------------- @interface QUITextRange : UITextRange -{ -} @property (nonatomic) NSRange range; + (QUITextRange *)rangeWithNSRange:(NSRange)range; @@ -130,51 +114,139 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); // ------------------------------------------------------------------------- -@implementation QUIView (TextInput) +@interface WrapperView : UIView +@end + +@implementation WrapperView -- (BOOL)canBecomeFirstResponder +-(id)initWithView:(UIView *)view { - return YES; + if (self = [self init]) { + [self addSubview:view]; + + self.autoresizingMask = view.autoresizingMask; + + [self sizeToFit]; + } + + return self; } -- (BOOL)becomeFirstResponder +- (void)layoutSubviews { - // Note: QIOSInputContext controls our first responder status based on - // whether or not the keyboard should be open or closed. - [self updateTextInputTraits]; - return [super becomeFirstResponder]; + UIView* view = [self.subviews firstObject]; + view.frame = self.bounds; + + // FIXME: During orientation changes the size and position + // of the view is not respected by the host view, even if + // we call sizeToFit or setNeedsLayout on the superview. } -- (BOOL)resignFirstResponder +- (CGSize)sizeThatFits:(CGSize)size { - // Resigning first responed status means that the virtual keyboard was closed, or - // some other view became first responder. In either case we clear the focus object to - // avoid blinking cursors in line edits etc: - if (m_qioswindow) - static_cast<QWindowPrivate *>(QObjectPrivate::get(m_qioswindow->window()))->clearFocusObject(); - return [super resignFirstResponder]; + return [[self.subviews firstObject] sizeThatFits:size]; } -+ (bool)inUpdateKeyboardLayout +// By keeping the responder (QIOSTextInputResponder in this case) +// retained, we ensure that all messages sent to the view during +// its lifetime in a window hierarcy will be able to traverse the +// responder chain. +-(void)willMoveToWindow:(UIWindow *)window { - return staticVariables()->inUpdateKeyboardLayout; + if (window) + [[self nextResponder] retain]; + else + [[self nextResponder] autorelease]; } -- (void)updateKeyboardLayout +@end + +// ------------------------------------------------------------------------- + +@implementation QIOSTextInputResponder + +- (id)initWithInputContext:(QIOSInputContext *)inputContext { - if (![self isFirstResponder]) - return; + if (!(self = [self init])) + return self; + + m_inSendEventToFocusObject = NO; + m_inputContext = inputContext; + + Qt::InputMethodHints hints = Qt::InputMethodHints([self imValue:Qt::ImHints].toUInt()); + + self.returnKeyType = (hints & Qt::ImhMultiLine) ? UIReturnKeyDefault : UIReturnKeyDone; + self.secureTextEntry = BOOL(hints & Qt::ImhHiddenText); + self.autocorrectionType = (hints & Qt::ImhNoPredictiveText) ? + UITextAutocorrectionTypeNo : UITextAutocorrectionTypeDefault; + self.spellCheckingType = (hints & Qt::ImhNoPredictiveText) ? + UITextSpellCheckingTypeNo : UITextSpellCheckingTypeDefault; + + if (hints & Qt::ImhUppercaseOnly) + self.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters; + else if (hints & Qt::ImhNoAutoUppercase) + self.autocapitalizationType = UITextAutocapitalizationTypeNone; + else + self.autocapitalizationType = UITextAutocapitalizationTypeSentences; - // There seems to be no API to inform that the keyboard layout needs to update. - // As a work-around, we quickly resign first responder just to reassign it again. - QScopedValueRollback<bool> rollback(staticVariables()->inUpdateKeyboardLayout); - staticVariables()->inUpdateKeyboardLayout = true; - [super resignFirstResponder]; - [self updateTextInputTraits]; - [super becomeFirstResponder]; + if (hints & Qt::ImhUrlCharactersOnly) + self.keyboardType = UIKeyboardTypeURL; + else if (hints & Qt::ImhEmailCharactersOnly) + self.keyboardType = UIKeyboardTypeEmailAddress; + else if (hints & Qt::ImhDigitsOnly) + self.keyboardType = UIKeyboardTypeNumberPad; + else if (hints & Qt::ImhFormattedNumbersOnly) + self.keyboardType = UIKeyboardTypeDecimalPad; + else if (hints & Qt::ImhDialableCharactersOnly) + self.keyboardType = UIKeyboardTypeNumberPad; + 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 *>())) + self.inputAccessoryView = [[[WrapperView alloc] initWithView:accessoryView] autorelease]; + + return self; +} + +- (void)dealloc +{ + self.inputView = 0; + self.inputAccessoryView = 0; + [super dealloc]; +} + +- (BOOL)isFirstResponder +{ + return YES; +} + +- (UIResponder*)nextResponder +{ + return qApp->focusWindow() ? + reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0; } -- (void)updateUITextInputDelegate:(NSNumber *)intQuery +/*! + 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 +{ + if (protocol == @protocol(UIKeyInput)) + return m_inputContext->inputMethodAccepted(); + + return [super conformsToProtocol:protocol]; +} + +// ------------------------------------------------------------------------- + +- (void)notifyInputDelegate:(Qt::InputMethodQueries)updatedProperties { // As documented, we should not report textWillChange/textDidChange unless the text // was changed externally. That will cause spell checking etc to fail. But we don't @@ -184,35 +256,17 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); if (m_inSendEventToFocusObject) return; - Qt::InputMethodQueries query = Qt::InputMethodQueries([intQuery intValue]); - if (query & (Qt::ImCursorPosition | Qt::ImAnchorPosition)) { - [self.inputDelegate selectionWillChange:id<UITextInput>(self)]; - [self.inputDelegate selectionDidChange:id<UITextInput>(self)]; + if (updatedProperties & (Qt::ImCursorPosition | Qt::ImAnchorPosition)) { + [self.inputDelegate selectionWillChange:self]; + [self.inputDelegate selectionDidChange:self]; } - if (query & Qt::ImSurroundingText) { - [self.inputDelegate textWillChange:id<UITextInput>(self)]; - [self.inputDelegate textDidChange:id<UITextInput>(self)]; + if (updatedProperties & Qt::ImSurroundingText) { + [self.inputDelegate textWillChange:self]; + [self.inputDelegate textDidChange:self]; } } -- (void)updateInputMethodWithQuery:(Qt::InputMethodQueries)query -{ - Q_UNUSED(query); - - QObject *focusObject = QGuiApplication::focusObject(); - if (!focusObject) - return; - - // Note that we ignore \a query, and instead update using Qt::ImQueryInput. This enables us to just - // store the event without copying out the result from the event each time. Besides, we seem to be - // called with Qt::ImQueryInput when only changing selection, and always if typing text. So there would - // not be any performance gain by only updating \a query. - staticVariables()->inputMethodQueryEvent = QInputMethodQueryEvent(Qt::ImQueryInput); - QCoreApplication::sendEvent(focusObject, &staticVariables()->inputMethodQueryEvent); - [self updateUITextInputDelegate:[NSNumber numberWithInt:int(query)]]; -} - - (void)sendEventToFocusObject:(QEvent &)e { QObject *focusObject = QGuiApplication::focusObject(); @@ -227,29 +281,9 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); QCoreApplication::sendEvent(focusObject, &e); } -- (void)reset -{ - [self setMarkedText:@"" selectedRange:NSMakeRange(0, 0)]; - [self updateInputMethodWithQuery:Qt::ImQueryInput]; - // Guard agains recursive callbacks by posting calls to UITextInput - [self performSelectorOnMainThread:@selector(updateKeyboardLayout) withObject:nil waitUntilDone:NO]; - [self performSelectorOnMainThread:@selector(updateUITextInputDelegate:) - withObject:[NSNumber numberWithInt:int(Qt::ImQueryInput)] - waitUntilDone:NO]; -} - -- (void)commit -{ - [self unmarkText]; - // Guard agains recursive callbacks by posting calls to UITextInput - [self performSelectorOnMainThread:@selector(updateUITextInputDelegate:) - withObject:[NSNumber numberWithInt:int(Qt::ImSurroundingText)] - waitUntilDone:NO]; -} - - (QVariant)imValue:(Qt::InputMethodQuery)query { - return staticVariables()->inputMethodQueryEvent.value(query); + return m_inputContext->imeState().currentState.value(query); } -(id<UITextInputTokenizer>)tokenizer @@ -277,7 +311,8 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); [self sendEventToFocusObject:e]; } -- (UITextRange *)selectedTextRange { +- (UITextRange *)selectedTextRange +{ int cursorPos = [self imValue:Qt::ImCursorPosition].toInt(); int anchorPos = [self imValue:Qt::ImAnchorPosition].toInt(); return [QUITextRange rangeWithNSRange:NSMakeRange(qMin(cursorPos, anchorPos), qAbs(anchorPos - cursorPos))]; @@ -296,8 +331,19 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); m_markedText = markedText ? QString::fromNSString(markedText) : QString(); + static QTextCharFormat markedTextFormat; + if (markedTextFormat.isEmpty()) { + // There seems to be no way to query how the preedit text + // should be drawn. So we need to hard-code the color. + QSysInfo::MacVersion iosVersion = QSysInfo::MacintoshVersion; + if (iosVersion < QSysInfo::MV_IOS_7_0) + markedTextFormat.setBackground(QColor(235, 239, 247)); + else + markedTextFormat.setBackground(QColor(206, 221, 238)); + } + QList<QInputMethodEvent::Attribute> attrs; - attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, markedText.length, staticVariables()->markedTextFormat); + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, markedText.length, markedTextFormat); QInputMethodEvent e(m_markedText, attrs); [self sendEventToFocusObject:e]; } @@ -325,7 +371,8 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); return NSOrderedSame; } -- (UITextRange *)markedTextRange { +- (UITextRange *)markedTextRange +{ return m_markedText.isEmpty() ? nil : [QUITextRange rangeWithNSRange:NSMakeRange(0, m_markedText.length())]; } @@ -369,7 +416,8 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); // to be relative to the view this method returns. // Since QInputMethod returns rects relative to the top level // QWindow, that is also the view we need to return. - QPlatformWindow *topLevel = m_qioswindow; + Q_ASSERT(qApp->focusWindow()->handle()); + QPlatformWindow *topLevel = qApp->focusWindow()->handle(); while (QPlatformWindow *p = topLevel->parent()) topLevel = p; return reinterpret_cast<UIView *>(topLevel->winId()); @@ -522,14 +570,15 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); return; if ([text isEqualToString:@"\n"]) { - if (self.returnKeyType == UIReturnKeyDone) - qApp->inputMethod()->hide(); - QKeyEvent press(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); QKeyEvent release(QEvent::KeyRelease, Qt::Key_Return, Qt::NoModifier); [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(); + return; } @@ -549,47 +598,4 @@ Q_GLOBAL_STATIC(StaticVariables, staticVariables); [self sendEventToFocusObject:release]; } -- (void)updateTextInputTraits -{ - // Ask the current focus object what kind of input it - // expects, and configure the keyboard appropriately: - QObject *focusObject = QGuiApplication::focusObject(); - if (!focusObject) - return; - QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints); - if (!QCoreApplication::sendEvent(focusObject, &queryEvent)) - return; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return; - - Qt::InputMethodHints hints = static_cast<Qt::InputMethodHints>(queryEvent.value(Qt::ImHints).toUInt()); - - self.returnKeyType = (hints & Qt::ImhMultiLine) ? UIReturnKeyDefault : UIReturnKeyDone; - self.secureTextEntry = BOOL(hints & Qt::ImhHiddenText); - self.autocorrectionType = (hints & Qt::ImhNoPredictiveText) ? - UITextAutocorrectionTypeNo : UITextAutocorrectionTypeDefault; - self.spellCheckingType = (hints & Qt::ImhNoPredictiveText) ? - UITextSpellCheckingTypeNo : UITextSpellCheckingTypeDefault; - - if (hints & Qt::ImhUppercaseOnly) - self.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters; - else if (hints & Qt::ImhNoAutoUppercase) - self.autocapitalizationType = UITextAutocapitalizationTypeNone; - else - self.autocapitalizationType = UITextAutocapitalizationTypeSentences; - - if (hints & Qt::ImhUrlCharactersOnly) - self.keyboardType = UIKeyboardTypeURL; - else if (hints & Qt::ImhEmailCharactersOnly) - self.keyboardType = UIKeyboardTypeEmailAddress; - else if (hints & Qt::ImhDigitsOnly) - self.keyboardType = UIKeyboardTypeNumberPad; - else if (hints & Qt::ImhFormattedNumbersOnly) - self.keyboardType = UIKeyboardTypeDecimalPad; - else if (hints & Qt::ImhDialableCharactersOnly) - self.keyboardType = UIKeyboardTypeNumberPad; - else - self.keyboardType = UIKeyboardTypeDefault; -} - @end diff --git a/src/plugins/platforms/ios/qiosviewcontroller.h b/src/plugins/platforms/ios/qiosviewcontroller.h index a0017808d3..5e95cdd3ee 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.h +++ b/src/plugins/platforms/ios/qiosviewcontroller.h @@ -41,7 +41,13 @@ #import <UIKit/UIKit.h> -@interface QIOSViewController : UIViewController +class QIOSScreen; + +@interface QIOSViewController : UIViewController { + QIOSScreen *m_screen; +} + +- (id)initWithQIOSScreen:(QIOSScreen *)screen; - (BOOL)prefersStatusBarHidden; @end diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm index 2fe679fc20..73f1b51b83 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.mm +++ b/src/plugins/platforms/ios/qiosviewcontroller.mm @@ -53,6 +53,35 @@ @implementation QIOSViewController +- (id)initWithQIOSScreen:(QIOSScreen *)screen +{ + if (self = [self init]) { + m_screen = screen; + +#if QT_IOS_DEPLOYMENT_TARGET_BELOW(__IPHONE_7_0) + QSysInfo::MacVersion iosVersion = QSysInfo::MacintoshVersion; + + // We prefer to keep the root viewcontroller in fullscreen layout, so that + // we don't have to compensate for the viewcontroller position. This also + // gives us the same behavior on iOS 5/6 as on iOS 7, where full screen layout + // is the only way. + if (iosVersion < QSysInfo::MV_IOS_7_0) + self.wantsFullScreenLayout = YES; + + // Use translucent statusbar by default on iOS6 iPhones (unless the user changed + // the default in the Info.plist), so that windows placed under the stausbar are + // still visible, just like on iOS7. + if (screen->uiScreen() == [UIScreen mainScreen] + && iosVersion >= QSysInfo::MV_IOS_6_0 && iosVersion < QSysInfo::MV_IOS_7_0 + && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone + && [UIApplication sharedApplication].statusBarStyle == UIStatusBarStyleDefault) + [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackTranslucent]; +#endif + } + + return self; +} + -(BOOL)shouldAutorotate { // Until a proper orientation and rotation API is in place, we always auto rotate. @@ -85,8 +114,7 @@ if (!QCoreApplication::instance()) return; // FIXME: Store orientation for later (?) - QIOSScreen *qiosScreen = static_cast<QIOSScreen *>(QGuiApplication::primaryScreen()->handle()); - qiosScreen->updateProperties(); + m_screen->updateProperties(); } #if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0) diff --git a/src/plugins/platforms/ios/qioswindow.h b/src/plugins/platforms/ios/qioswindow.h index 6b6892e6e4..e55def1992 100644 --- a/src/plugins/platforms/ios/qioswindow.h +++ b/src/plugins/platforms/ios/qioswindow.h @@ -73,6 +73,7 @@ public: void setParent(const QPlatformWindow *window); void handleContentOrientationChange(Qt::ScreenOrientation orientation); void setVisible(bool visible); + void setOpacity(qreal level) Q_DECL_OVERRIDE; bool isExposed() const Q_DECL_OVERRIDE; @@ -87,6 +88,8 @@ public: WId winId() const { return WId(m_view); }; + void clearAccessibleCache(); + private: void applicationStateChanged(Qt::ApplicationState state); void applyGeometry(const QRect &rect); diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm index 6f5c96cfc1..2874d272fe 100644 --- a/src/plugins/platforms/ios/qioswindow.mm +++ b/src/plugins/platforms/ios/qioswindow.mm @@ -39,323 +39,23 @@ ** ****************************************************************************/ -#include "qiosglobal.h" #include "qioswindow.h" -#include "quiview.h" + +#include "qiosapplicationdelegate.h" #include "qioscontext.h" -#include "qiosinputcontext.h" +#include "qiosglobal.h" +#include "qiosintegration.h" #include "qiosscreen.h" -#include "qiosapplicationdelegate.h" #include "qiosviewcontroller.h" -#include "qiosintegration.h" -#include <QtGui/private/qguiapplication_p.h> +#include "quiview.h" + #include <QtGui/private/qwindow_p.h> #include <qpa/qplatformintegration.h> #import <QuartzCore/CAEAGLLayer.h> -#include <QtGui/QKeyEvent> -#include <qpa/qwindowsysteminterface.h> - #include <QtDebug> -// Include category as an alternative to using -ObjC (Apple QA1490) -#include "quiview_textinput.mm" - -@implementation QUIView - -@synthesize autocapitalizationType; -@synthesize autocorrectionType; -@synthesize enablesReturnKeyAutomatically; -@synthesize keyboardAppearance; -@synthesize keyboardType; -@synthesize returnKeyType; -@synthesize secureTextEntry; - -+ (Class)layerClass -{ - return [CAEAGLLayer class]; -} - --(id)initWithQIOSWindow:(QIOSWindow *)window -{ - if (self = [self initWithFrame:toCGRect(window->geometry())]) - m_qioswindow = window; - - return self; -} - -- (id)initWithFrame:(CGRect)frame -{ - if ((self = [super initWithFrame:frame])) { - // Set up EAGL layer - CAEAGLLayer *eaglLayer = static_cast<CAEAGLLayer *>(self.layer); - eaglLayer.opaque = TRUE; - eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, - kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]; - - if (isQtApplication()) - self.hidden = YES; - - self.multipleTouchEnabled = YES; - m_inSendEventToFocusObject = NO; - } - - return self; -} - -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - // UIKIt will normally set the scale factor of a view to match the corresponding - // screen scale factor, but views backed by CAEAGLLayers need to do this manually. - self.contentScaleFactor = newWindow && newWindow.screen ? - newWindow.screen.scale : [[UIScreen mainScreen] scale]; - - // FIXME: Allow the scale factor to be customized through QSurfaceFormat. -} - -- (void)didAddSubview:(UIView *)subview -{ - if ([subview isKindOfClass:[QUIView class]]) - self.clipsToBounds = YES; -} - -- (void)willRemoveSubview:(UIView *)subview -{ - for (UIView *view in self.subviews) { - if (view != subview && [view isKindOfClass:[QUIView class]]) - return; - } - - self.clipsToBounds = NO; -} - -- (void)setNeedsDisplay -{ - [super setNeedsDisplay]; - - // We didn't implement drawRect: so we have to manually - // mark the layer as needing display. - [self.layer setNeedsDisplay]; -} - -- (void)layoutSubviews -{ - // This method is the de facto way to know that view has been resized, - // or otherwise needs invalidation of its buffers. Note though that we - // do not get this callback when the view just changes its position, so - // the position of our QWindow (and platform window) will only get updated - // when the size is also changed. - - if (!CGAffineTransformIsIdentity(self.transform)) - qWarning() << m_qioswindow->window() - << "is backed by a UIView that has a transform set. This is not supported."; - - // The original geometry requested by setGeometry() might be different - // from what we end up with after applying window constraints. - QRect requestedGeometry = m_qioswindow->geometry(); - - QRect actualGeometry; - if (m_qioswindow->window()->isTopLevel()) { - UIWindow *uiWindow = self.window; - UIView *rootView = uiWindow.rootViewController.view; - CGRect rootViewPositionInRelationToRootViewController = - [rootView convertRect:uiWindow.bounds fromView:uiWindow]; - - actualGeometry = fromCGRect(CGRectOffset([self.superview convertRect:self.frame toView:rootView], - -rootViewPositionInRelationToRootViewController.origin.x, - -rootViewPositionInRelationToRootViewController.origin.y - + rootView.bounds.origin.y)).toRect(); - } else { - actualGeometry = fromCGRect(self.frame).toRect(); - } - - // Persist the actual/new geometry so that QWindow::geometry() can - // be queried on the resize event. - m_qioswindow->QPlatformWindow::setGeometry(actualGeometry); - - QRect previousGeometry = requestedGeometry != actualGeometry ? - requestedGeometry : qt_window_private(m_qioswindow->window())->geometry; - - QWindowSystemInterface::handleGeometryChange(m_qioswindow->window(), actualGeometry, previousGeometry); - QWindowSystemInterface::flushWindowSystemEvents(); - - if (actualGeometry.size() != previousGeometry.size()) { - // Trigger expose event on resize - [self setNeedsDisplay]; - - // A new size means we also need to resize the FBO's corresponding buffers, - // but we defer that to when the application calls makeCurrent. - } -} - -- (void)displayLayer:(CALayer *)layer -{ - Q_UNUSED(layer); - Q_ASSERT(layer == self.layer); - - [self sendUpdatedExposeEvent]; -} - -- (void)sendUpdatedExposeEvent -{ - QRegion region; - - if (m_qioswindow->isExposed()) { - QSize bounds = fromCGRect(self.layer.bounds).toRect().size(); - - Q_ASSERT(m_qioswindow->geometry().size() == bounds); - Q_ASSERT(self.hidden == !m_qioswindow->window()->isVisible()); - - region = QRect(QPoint(), bounds); - } - - QWindowSystemInterface::handleExposeEvent(m_qioswindow->window(), region); - QWindowSystemInterface::flushWindowSystemEvents(); -} - -- (void)updateTouchList:(NSSet *)touches withState:(Qt::TouchPointState)state -{ - // We deliver touch events in global coordinates. But global in this respect - // means the same coordinate system that we use for describing the geometry - // of the top level QWindow we're inside. And that would be the coordinate - // system of the superview of the UIView that backs that window: - QPlatformWindow *topLevel = m_qioswindow; - while (QPlatformWindow *topLevelParent = topLevel->parent()) - topLevel = topLevelParent; - UIView *rootView = reinterpret_cast<UIView *>(topLevel->winId()).superview; - CGSize rootViewSize = rootView.frame.size; - - foreach (UITouch *uiTouch, m_activeTouches.keys()) { - QWindowSystemInterface::TouchPoint &touchPoint = m_activeTouches[uiTouch]; - if (![touches containsObject:uiTouch]) { - touchPoint.state = Qt::TouchPointStationary; - } else { - touchPoint.state = state; - touchPoint.pressure = (state == Qt::TouchPointReleased) ? 0.0 : 1.0; - QPoint touchPos = fromCGPoint([uiTouch locationInView:rootView]).toPoint(); - touchPoint.area = QRectF(touchPos, QSize(0, 0)); - touchPoint.normalPosition = QPointF(touchPos.x() / rootViewSize.width, touchPos.y() / rootViewSize.height); - } - } -} - -- (void) sendTouchEventWithTimestamp:(ulong)timeStamp -{ - // Send touch event synchronously - QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); - QWindowSystemInterface::handleTouchEvent(m_qioswindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values()); - QWindowSystemInterface::flushWindowSystemEvents(); -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - // UIKit generates [Began -> Moved -> Ended] event sequences for - // each touch point. Internally we keep a hashmap of active UITouch - // points to QWindowSystemInterface::TouchPoints, and assigns each TouchPoint - // an id for use by Qt. - for (UITouch *touch in touches) { - Q_ASSERT(!m_activeTouches.contains(touch)); - m_activeTouches[touch].id = m_nextTouchId++; - } - - if (m_activeTouches.size() == 1) { - QPlatformWindow *topLevel = m_qioswindow; - while (QPlatformWindow *p = topLevel->parent()) - topLevel = p; - if (topLevel->window() != QGuiApplication::focusWindow()) - topLevel->requestActivateWindow(); - } - - [self updateTouchList:touches withState:Qt::TouchPointPressed]; - [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self updateTouchList:touches withState:Qt::TouchPointMoved]; - [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self updateTouchList:touches withState:Qt::TouchPointReleased]; - [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; - - // Remove ended touch points from the active set: - for (UITouch *touch in touches) - m_activeTouches.remove(touch); - if (m_activeTouches.isEmpty()) - m_nextTouchId = 0; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - if (m_activeTouches.isEmpty()) - return; - - // When four-finger swiping, we get a touchesCancelled callback - // which includes all four touch points. The swipe gesture is - // then active until all four touches have been released, and - // we start getting touchesBegan events again. - - // When five-finger pinching, we also get a touchesCancelled - // callback with all five touch points, but the pinch gesture - // ends when the second to last finger is released from the - // screen. The last finger will not emit any more touch - // events, _but_, will contribute to starting another pinch - // gesture. That second pinch gesture will _not_ trigger a - // touchesCancelled event when starting, but as each finger - // is released, and we may get touchesMoved events for the - // remaining fingers. [event allTouches] also contains one - // less touch point than it should, so this behavior is - // likely a bug in the iOS system gesture recognizer, but we - // have to take it into account when maintaining the Qt state. - // We do this by assuming that there are no cases where a - // sub-set of the active touch events are intentionally cancelled. - - if (touches && (static_cast<NSInteger>([touches count]) != m_activeTouches.count())) - qWarning("Subset of active touches cancelled by UIKit"); - - m_activeTouches.clear(); - m_nextTouchId = 0; - - NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime]; - - // Send cancel touch event synchronously - QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); - QWindowSystemInterface::handleTouchCancelEvent(m_qioswindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice()); - QWindowSystemInterface::flushWindowSystemEvents(); -} - -@end - -@implementation UIView (QIOS) - -- (QWindow *)qwindow -{ - if ([self isKindOfClass:[QUIView class]]) { - if (QIOSWindow *w = static_cast<QUIView *>(self)->m_qioswindow) - return w->window(); - } - return nil; -} - -- (UIViewController *)viewController -{ - id responder = self; - while ((responder = [responder nextResponder])) { - if ([responder isKindOfClass:UIViewController.class]) - return responder; - } - return nil; -} - -@end - -QT_BEGIN_NAMESPACE - QIOSWindow::QIOSWindow(QWindow *window) : QPlatformWindow(window) , m_view([[QUIView alloc] initWithQIOSWindow:this]) @@ -374,6 +74,7 @@ QIOSWindow::QIOSWindow(QWindow *window) screen()->availableGeometry().width(), screen()->availableGeometry().height()); setWindowState(window->windowState()); + setOpacity(window->opacity()); } QIOSWindow::~QIOSWindow() @@ -385,6 +86,7 @@ QIOSWindow::~QIOSWindow() // cancellation of all touch events. [m_view touchesCancelled:0 withEvent:0]; + clearAccessibleCache(); m_view->m_qioswindow = 0; [m_view removeFromSuperview]; [m_view release]; @@ -434,6 +136,11 @@ void QIOSWindow::setVisible(bool visible) } } +void QIOSWindow::setOpacity(qreal level) +{ + m_view.alpha = qBound(0.0, level, 1.0); +} + void QIOSWindow::setGeometry(const QRect &rect) { m_normalGeometry = rect; @@ -557,12 +264,12 @@ void QIOSWindow::requestActivateWindow() if (blockedByModal()) return; + Q_ASSERT(m_view.window); [m_view.window makeKeyWindow]; + [m_view becomeFirstResponder]; if (window()->isTopLevel()) raise(); - - QWindowSystemInterface::handleWindowActivated(window()); } void QIOSWindow::raiseOrLower(bool raise) @@ -634,6 +341,11 @@ qreal QIOSWindow::devicePixelRatio() const return m_view.contentScaleFactor; } +void QIOSWindow::clearAccessibleCache() +{ + [m_view clearAccessibleCache]; +} + #include "moc_qioswindow.cpp" QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.h b/src/plugins/platforms/ios/quiaccessibilityelement.h new file mode 100644 index 0000000000..7c5a930e86 --- /dev/null +++ b/src/plugins/platforms/ios/quiaccessibilityelement.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QUIACCESSIBILITYELEMENT_H +#define QUIACCESSIBILITYELEMENT_H + +#import <UIKit/UIKit.h> +#import <QtGui/QtGui> + +@interface QMacAccessibilityElement : UIAccessibilityElement +{} + +@property (readonly) QAccessible::Id axid; + +- (id) initWithId: (QAccessible::Id) anId withAccessibilityContainer: (id) view; ++ (QMacAccessibilityElement *) elementWithId: (QAccessible::Id) anId withAccessibilityContainer: (id) view; + +@end + +#endif diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.mm b/src/plugins/platforms/ios/quiaccessibilityelement.mm new file mode 100644 index 0000000000..331c38460c --- /dev/null +++ b/src/plugins/platforms/ios/quiaccessibilityelement.mm @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "quiaccessibilityelement.h" + +#include "private/qaccessiblecache_p.h" + +@implementation QMacAccessibilityElement + +- (id) initWithId: (QAccessible::Id) anId withAccessibilityContainer: (id) view +{ + Q_ASSERT((int)anId < 0); + self = [super initWithAccessibilityContainer: view]; + if (self) + _axid = anId; + + return self; +} + ++ (id) elementWithId: (QAccessible::Id) anId withAccessibilityContainer: (id) view +{ + Q_ASSERT(anId); + if (!anId) + return nil; + + QAccessibleCache *cache = QAccessibleCache::instance(); + + QMacAccessibilityElement *element = cache->elementForId(anId); + if (!element) { + Q_ASSERT(QAccessible::accessibleInterface(anId)); + element = [[self alloc] initWithId:anId withAccessibilityContainer: view]; + cache->insertElement(anId, element); + } + return element; +} + +- (void) invalidate +{ + [self release]; +} + +- (BOOL) isAccessibilityElement +{ + return YES; +} + +- (NSString*) accessibilityLabel +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (!iface) { + qWarning() << "invalid accessible interface for: " << self.axid; + return @""; + } + + return iface->text(QAccessible::Name).toNSString(); +} + +- (NSString*) accessibilityHint +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (!iface) { + qWarning() << "invalid accessible interface for: " << self.axid; + return @""; + } + return iface->text(QAccessible::Description).toNSString(); +} + +- (NSString*) accessibilityValue +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (!iface) { + qWarning() << "invalid accessible interface for: " << self.axid; + return @""; + } + + QAccessible::State state = iface->state(); + + if (state.checkable) + return state.checked ? @"checked" : @"unchecked"; // FIXME: translation + + QAccessibleValueInterface *val = iface->valueInterface(); + if (val) { + return val->currentValue().toString().toNSString(); + } else if (QAccessibleTextInterface *text = iface->textInterface()) { + // FIXME doesn't work? + return text->text(0, text->characterCount() - 1).toNSString(); + } + + return [super accessibilityHint]; +} + +- (CGRect) accessibilityFrame +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (!iface) { + qWarning() << "invalid accessible interface for: " << self.axid; + return CGRect(); + } + + QRect rect = iface->rect(); + return CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); +} + +- (UIAccessibilityTraits) accessibilityTraits +{ + UIAccessibilityTraits traits = UIAccessibilityTraitNone; + + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (!iface) { + qWarning() << "invalid accessible interface for: " << self.axid; + return traits; + } + QAccessible::State state = iface->state(); + if (state.disabled) + traits |= UIAccessibilityTraitNotEnabled; + + if (iface->role() == QAccessible::Button) + traits |= UIAccessibilityTraitButton; + + if (iface->valueInterface()) + traits |= UIAccessibilityTraitAdjustable; + + return traits; +} + +- (BOOL) accessibilityActivate +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (QAccessibleActionInterface *action = iface->actionInterface()) { + if (action->actionNames().contains(QAccessibleActionInterface::pressAction())) { + action->doAction(QAccessibleActionInterface::pressAction()); + return YES; + } else if (action->actionNames().contains(QAccessibleActionInterface::showMenuAction())) { + action->doAction(QAccessibleActionInterface::showMenuAction()); + return YES; + } + } + return NO; // fall back to sending mouse clicks +} + +- (void) accessibilityIncrement +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (QAccessibleActionInterface *action = iface->actionInterface()) + action->doAction(QAccessibleActionInterface::increaseAction()); +} + +- (void) accessibilityDecrement +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (QAccessibleActionInterface *action = iface->actionInterface()) + action->doAction(QAccessibleActionInterface::decreaseAction()); +} + +@end diff --git a/src/plugins/platforms/ios/quiview.h b/src/plugins/platforms/ios/quiview.h index 122e7c604b..c5bf3b6cbe 100644 --- a/src/plugins/platforms/ios/quiview.h +++ b/src/plugins/platforms/ios/quiview.h @@ -40,40 +40,30 @@ ****************************************************************************/ #import <UIKit/UIKit.h> -#include "qioswindow.h" + +#include <qhash.h> +#include <qstring.h> + +#include <qpa/qwindowsysteminterface.h> + +class QIOSWindow; @interface QUIView : UIView { -@public - UITextAutocapitalizationType autocapitalizationType; - UITextAutocorrectionType autocorrectionType; - BOOL enablesReturnKeyAutomatically; - UIKeyboardAppearance keyboardAppearance; - UIKeyboardType keyboardType; - UIReturnKeyType returnKeyType; - BOOL secureTextEntry; + @public QIOSWindow *m_qioswindow; + @private QHash<UITouch *, QWindowSystemInterface::TouchPoint> m_activeTouches; int m_nextTouchId; - QString m_markedText; - BOOL m_inSendEventToFocusObject; -} -@property(nonatomic, assign) id<UITextInputDelegate> inputDelegate; -@property(nonatomic) UITextAutocapitalizationType autocapitalizationType; -@property(nonatomic) UITextAutocorrectionType autocorrectionType; -@property(nonatomic) UITextSpellCheckingType spellCheckingType; -@property(nonatomic) BOOL enablesReturnKeyAutomatically; -@property(nonatomic) UIKeyboardAppearance keyboardAppearance; -@property(nonatomic) UIKeyboardType keyboardType; -@property(nonatomic) UIReturnKeyType returnKeyType; -@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; + @private + NSMutableArray *m_accessibleElements; +} +- (id)initWithQIOSWindow:(QIOSWindow *)window; +- (void)sendUpdatedExposeEvent; @end -@interface QUIView (TextInput) <UITextInput> -- (void)updateInputMethodWithQuery:(Qt::InputMethodQueries)query; -- (void)reset; -- (void)commit; -+ (bool)inUpdateKeyboardLayout; +@interface QUIView (Accessibility) +- (void)clearAccessibleCache; @end diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm new file mode 100644 index 0000000000..5687c078ea --- /dev/null +++ b/src/plugins/platforms/ios/quiview.mm @@ -0,0 +1,376 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "quiview.h" + +#include "qiosglobal.h" +#include "qiosintegration.h" +#include "qioswindow.h" + +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/private/qwindow_p.h> + +@implementation QUIView + ++ (Class)layerClass +{ + return [CAEAGLLayer class]; +} + +-(id)initWithQIOSWindow:(QIOSWindow *)window +{ + if (self = [self initWithFrame:toCGRect(window->geometry())]) + m_qioswindow = window; + + m_accessibleElements = [[NSMutableArray alloc] init]; + return self; +} + +- (id)initWithFrame:(CGRect)frame +{ + if ((self = [super initWithFrame:frame])) { + // Set up EAGL layer + CAEAGLLayer *eaglLayer = static_cast<CAEAGLLayer *>(self.layer); + eaglLayer.opaque = TRUE; + eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, + kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]; + + if (isQtApplication()) + self.hidden = YES; + + self.multipleTouchEnabled = YES; + } + + return self; +} + +- (void)willMoveToWindow:(UIWindow *)newWindow +{ + // UIKIt will normally set the scale factor of a view to match the corresponding + // screen scale factor, but views backed by CAEAGLLayers need to do this manually. + self.contentScaleFactor = newWindow && newWindow.screen ? + newWindow.screen.scale : [[UIScreen mainScreen] scale]; + + // FIXME: Allow the scale factor to be customized through QSurfaceFormat. +} + +- (void)didAddSubview:(UIView *)subview +{ + if ([subview isKindOfClass:[QUIView class]]) + self.clipsToBounds = YES; +} + +- (void)willRemoveSubview:(UIView *)subview +{ + for (UIView *view in self.subviews) { + if (view != subview && [view isKindOfClass:[QUIView class]]) + return; + } + + self.clipsToBounds = NO; +} + +- (void)setNeedsDisplay +{ + [super setNeedsDisplay]; + + // We didn't implement drawRect: so we have to manually + // mark the layer as needing display. + [self.layer setNeedsDisplay]; +} + +- (void)layoutSubviews +{ + // This method is the de facto way to know that view has been resized, + // or otherwise needs invalidation of its buffers. Note though that we + // do not get this callback when the view just changes its position, so + // the position of our QWindow (and platform window) will only get updated + // when the size is also changed. + + if (!CGAffineTransformIsIdentity(self.transform)) + qWarning() << m_qioswindow->window() + << "is backed by a UIView that has a transform set. This is not supported."; + + // The original geometry requested by setGeometry() might be different + // from what we end up with after applying window constraints. + QRect requestedGeometry = m_qioswindow->geometry(); + + QRect actualGeometry; + if (m_qioswindow->window()->isTopLevel()) { + UIWindow *uiWindow = self.window; + UIView *rootView = uiWindow.rootViewController.view; + CGRect rootViewPositionInRelationToRootViewController = + [rootView convertRect:uiWindow.bounds fromView:uiWindow]; + + actualGeometry = fromCGRect(CGRectOffset([self.superview convertRect:self.frame toView:rootView], + -rootViewPositionInRelationToRootViewController.origin.x, + -rootViewPositionInRelationToRootViewController.origin.y + + rootView.bounds.origin.y)).toRect(); + } else { + actualGeometry = fromCGRect(self.frame).toRect(); + } + + // Persist the actual/new geometry so that QWindow::geometry() can + // be queried on the resize event. + m_qioswindow->QPlatformWindow::setGeometry(actualGeometry); + + QRect previousGeometry = requestedGeometry != actualGeometry ? + requestedGeometry : qt_window_private(m_qioswindow->window())->geometry; + + QWindowSystemInterface::handleGeometryChange(m_qioswindow->window(), actualGeometry, previousGeometry); + QWindowSystemInterface::flushWindowSystemEvents(); + + if (actualGeometry.size() != previousGeometry.size()) { + // Trigger expose event on resize + [self setNeedsDisplay]; + + // A new size means we also need to resize the FBO's corresponding buffers, + // but we defer that to when the application calls makeCurrent. + } +} + +- (void)displayLayer:(CALayer *)layer +{ + Q_UNUSED(layer); + Q_ASSERT(layer == self.layer); + + [self sendUpdatedExposeEvent]; +} + +- (void)sendUpdatedExposeEvent +{ + QRegion region; + + if (m_qioswindow->isExposed()) { + QSize bounds = fromCGRect(self.layer.bounds).toRect().size(); + + Q_ASSERT(m_qioswindow->geometry().size() == bounds); + Q_ASSERT(self.hidden == !m_qioswindow->window()->isVisible()); + + region = QRect(QPoint(), bounds); + } + + QWindowSystemInterface::handleExposeEvent(m_qioswindow->window(), region); + QWindowSystemInterface::flushWindowSystemEvents(); +} + +// ------------------------------------------------------------------------- + +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (BOOL)becomeFirstResponder +{ + if ([super becomeFirstResponder]) { + QWindowSystemInterface::handleWindowActivated(m_qioswindow->window()); + QWindowSystemInterface::flushWindowSystemEvents(); + + return YES; + } + + return NO; +} + +- (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; + } + + return NO; +} + +// ------------------------------------------------------------------------- + + +- (void)updateTouchList:(NSSet *)touches withState:(Qt::TouchPointState)state +{ + // We deliver touch events in global coordinates. But global in this respect + // means the same coordinate system that we use for describing the geometry + // of the top level QWindow we're inside. And that would be the coordinate + // system of the superview of the UIView that backs that window: + QPlatformWindow *topLevel = m_qioswindow; + while (QPlatformWindow *topLevelParent = topLevel->parent()) + topLevel = topLevelParent; + UIView *rootView = reinterpret_cast<UIView *>(topLevel->winId()).superview; + CGSize rootViewSize = rootView.frame.size; + + foreach (UITouch *uiTouch, m_activeTouches.keys()) { + QWindowSystemInterface::TouchPoint &touchPoint = m_activeTouches[uiTouch]; + if (![touches containsObject:uiTouch]) { + touchPoint.state = Qt::TouchPointStationary; + } else { + touchPoint.state = state; + touchPoint.pressure = (state == Qt::TouchPointReleased) ? 0.0 : 1.0; + QPoint touchPos = fromCGPoint([uiTouch locationInView:rootView]).toPoint(); + touchPoint.area = QRectF(touchPos, QSize(0, 0)); + touchPoint.normalPosition = QPointF(touchPos.x() / rootViewSize.width, touchPos.y() / rootViewSize.height); + } + } +} + +- (void) sendTouchEventWithTimestamp:(ulong)timeStamp +{ + // Send touch event synchronously + QIOSIntegration *iosIntegration = QIOSIntegration::instance(); + QWindowSystemInterface::handleTouchEvent(m_qioswindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values()); + QWindowSystemInterface::flushWindowSystemEvents(); +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + // UIKit generates [Began -> Moved -> Ended] event sequences for + // each touch point. Internally we keep a hashmap of active UITouch + // points to QWindowSystemInterface::TouchPoints, and assigns each TouchPoint + // an id for use by Qt. + for (UITouch *touch in touches) { + Q_ASSERT(!m_activeTouches.contains(touch)); + m_activeTouches[touch].id = m_nextTouchId++; + } + + if (m_activeTouches.size() == 1) { + QPlatformWindow *topLevel = m_qioswindow; + while (QPlatformWindow *p = topLevel->parent()) + topLevel = p; + if (topLevel->window() != QGuiApplication::focusWindow()) + topLevel->requestActivateWindow(); + } + + [self updateTouchList:touches withState:Qt::TouchPointPressed]; + [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + [self updateTouchList:touches withState:Qt::TouchPointMoved]; + [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + [self updateTouchList:touches withState:Qt::TouchPointReleased]; + [self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)]; + + // Remove ended touch points from the active set: + for (UITouch *touch in touches) + m_activeTouches.remove(touch); + if (m_activeTouches.isEmpty()) + m_nextTouchId = 0; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event +{ + if (m_activeTouches.isEmpty()) + return; + + // When four-finger swiping, we get a touchesCancelled callback + // which includes all four touch points. The swipe gesture is + // then active until all four touches have been released, and + // we start getting touchesBegan events again. + + // When five-finger pinching, we also get a touchesCancelled + // callback with all five touch points, but the pinch gesture + // ends when the second to last finger is released from the + // screen. The last finger will not emit any more touch + // events, _but_, will contribute to starting another pinch + // gesture. That second pinch gesture will _not_ trigger a + // touchesCancelled event when starting, but as each finger + // is released, and we may get touchesMoved events for the + // remaining fingers. [event allTouches] also contains one + // less touch point than it should, so this behavior is + // likely a bug in the iOS system gesture recognizer, but we + // have to take it into account when maintaining the Qt state. + // We do this by assuming that there are no cases where a + // sub-set of the active touch events are intentionally cancelled. + + if (touches && (static_cast<NSInteger>([touches count]) != m_activeTouches.count())) + qWarning("Subset of active touches cancelled by UIKit"); + + m_activeTouches.clear(); + m_nextTouchId = 0; + + NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime]; + + // Send cancel touch event synchronously + QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); + QWindowSystemInterface::handleTouchCancelEvent(m_qioswindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice()); + QWindowSystemInterface::flushWindowSystemEvents(); +} + +@end + +@implementation UIView (QtHelpers) + +- (QWindow *)qwindow +{ + if ([self isKindOfClass:[QUIView class]]) { + if (QIOSWindow *w = static_cast<QUIView *>(self)->m_qioswindow) + return w->window(); + } + return nil; +} + +- (UIViewController *)viewController +{ + id responder = self; + while ((responder = [responder nextResponder])) { + if ([responder isKindOfClass:UIViewController.class]) + return responder; + } + return nil; +} + +@end + +// Include category as an alternative to using -ObjC (Apple QA1490) +#include "quiview_accessibility.mm" diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm new file mode 100644 index 0000000000..6565e08302 --- /dev/null +++ b/src/plugins/platforms/ios/quiview_accessibility.mm @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qiosplatformaccessibility.h" +#include "quiaccessibilityelement.h" + +#include <QtGui/private/qguiapplication_p.h> + +@implementation QUIView (Accessibility) + +- (void)createAccessibleElement:(QAccessibleInterface *)iface +{ + if (!iface || iface->state().invisible) + return; + QAccessible::Id accessibleId = QAccessible::uniqueId(iface); + UIAccessibilityElement *elem = [[QMacAccessibilityElement alloc] initWithId: accessibleId withAccessibilityContainer: self]; + [m_accessibleElements addObject: elem]; +} + +- (void)createAccessibleContainer:(QAccessibleInterface *)iface +{ + if (!iface) + return; + + if (iface->childCount() == 0) { + [self createAccessibleElement: iface]; + } else { + for (int i = 0; i < iface->childCount(); ++i) + [self createAccessibleContainer: iface->child(i)]; + } +} + +- (void)initAccessibility +{ + static bool init = false; + if (!init) + QGuiApplicationPrivate::platformIntegration()->accessibility()->setActive(true); + init = true; + + if ([m_accessibleElements count]) + return; + + QWindow *win = m_qioswindow->window(); + QAccessibleInterface *iface = win->accessibleRoot(); + if (iface) + [self createAccessibleContainer: iface]; +} + +- (void)clearAccessibleCache +{ + [m_accessibleElements removeAllObjects]; + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, @""); +} + +// this is a container, returning yes here means the functions below will never be called +- (BOOL)isAccessibilityElement +{ + return NO; +} + +- (NSInteger)accessibilityElementCount +{ + [self initAccessibility]; + return [m_accessibleElements count]; +} + +- (id)accessibilityElementAtIndex:(NSInteger)index +{ + [self initAccessibility]; + return m_accessibleElements[index]; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element +{ + [self initAccessibility]; + return [m_accessibleElements indexOfObject:element]; +} + +@end |