From f86993cdeef298131bfcdc4e2078e7a353d42dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Thu, 3 Jul 2014 17:55:59 +0200 Subject: iOS: Refactor qioswindow.mm into quiview.mm and quiview_accessibility.mm Change-Id: Ib6297e37d67b2c0ea251ae054b8ff877af2673a5 Reviewed-by: Richard Moe Gustavsen --- src/plugins/platforms/ios/ios.pro | 4 +- src/plugins/platforms/ios/qioswindow.mm | 380 +-------------------- src/plugins/platforms/ios/quiview.h | 26 +- src/plugins/platforms/ios/quiview.mm | 346 +++++++++++++++++++ src/plugins/platforms/ios/quiview_accessibility.mm | 115 +++++++ src/plugins/platforms/ios/quiview_textinput.mm | 2 + 6 files changed, 493 insertions(+), 380 deletions(-) create mode 100644 src/plugins/platforms/ios/quiview.mm create mode 100644 src/plugins/platforms/ios/quiview_accessibility.mm (limited to 'src/plugins/platforms/ios') diff --git a/src/plugins/platforms/ios/ios.pro b/src/plugins/platforms/ios/ios.pro index 57d4ac1770..ad9912a8d0 100644 --- a/src/plugins/platforms/ios/ios.pro +++ b/src/plugins/platforms/ios/ios.pro @@ -23,6 +23,7 @@ OBJECTIVE_SOURCES = \ qiostheme.mm \ qiosglobal.mm \ qiosservices.mm \ + quiview.mm \ qiosclipboard.mm \ quiaccessibilityelement.mm \ qiosplatformaccessibility.mm \ @@ -47,4 +48,5 @@ HEADERS = \ qiosplatformaccessibility.h \ OTHER_FILES = \ - quiview_textinput.mm + quiview_textinput.mm \ + quiview_accessibility.mm diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm index 3ed2bbe1a1..76bd9bb2b5 100644 --- a/src/plugins/platforms/ios/qioswindow.mm +++ b/src/plugins/platforms/ios/qioswindow.mm @@ -39,391 +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 +#include "quiview.h" + #include #include -#include "qiosplatformaccessibility.h" -#import "quiaccessibilityelement.h" - #import -#include -#include - #include -// 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; - - m_accessibleElements = [[NSMutableArray alloc] init]; - return self; -} - -- (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]; -} - -- (id)initWithFrame:(CGRect)frame -{ - if ((self = [super initWithFrame:frame])) { - // Set up EAGL layer - CAEAGLLayer *eaglLayer = static_cast(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(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(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([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(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(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]) diff --git a/src/plugins/platforms/ios/quiview.h b/src/plugins/platforms/ios/quiview.h index 7c65037004..99c710ffee 100644 --- a/src/plugins/platforms/ios/quiview.h +++ b/src/plugins/platforms/ios/quiview.h @@ -40,11 +40,23 @@ ****************************************************************************/ #import -#include "qioswindow.h" + +#include +#include + +#include + +class QIOSWindow; @interface QUIView : UIView { -@public + @public + QIOSWindow *m_qioswindow; + @private + QHash m_activeTouches; + int m_nextTouchId; + + @public UITextAutocapitalizationType autocapitalizationType; UITextAutocorrectionType autocorrectionType; BOOL enablesReturnKeyAutomatically; @@ -52,12 +64,10 @@ UIKeyboardType keyboardType; UIReturnKeyType returnKeyType; BOOL secureTextEntry; - QIOSWindow *m_qioswindow; - QHash m_activeTouches; - int m_nextTouchId; QString m_markedText; BOOL m_inSendEventToFocusObject; + @private NSMutableArray *m_accessibleElements; } @@ -71,6 +81,8 @@ @property(nonatomic) UIReturnKeyType returnKeyType; @property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; +- (id)initWithQIOSWindow:(QIOSWindow *)window; +- (void)sendUpdatedExposeEvent; @end @interface QUIView (TextInput) @@ -79,3 +91,7 @@ - (void)commit; + (bool)inUpdateKeyboardLayout; @end + +@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..8b79ba9e9f --- /dev/null +++ b/src/plugins/platforms/ios/quiview.mm @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** 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 +#include + +// Include category as an alternative to using -ObjC (Apple QA1490) +#include "quiview_textinput.mm" +#include "quiview_accessibility.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; + + m_accessibleElements = [[NSMutableArray alloc] init]; + return self; +} + +- (id)initWithFrame:(CGRect)frame +{ + if ((self = [super initWithFrame:frame])) { + // Set up EAGL layer + CAEAGLLayer *eaglLayer = static_cast(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(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(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([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(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(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 diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm new file mode 100644 index 0000000000..cf51a9add3 --- /dev/null +++ b/src/plugins/platforms/ios/quiview_accessibility.mm @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** 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" + +@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 diff --git a/src/plugins/platforms/ios/quiview_textinput.mm b/src/plugins/platforms/ios/quiview_textinput.mm index e65ac1cc46..3d2e3089cb 100644 --- a/src/plugins/platforms/ios/quiview_textinput.mm +++ b/src/plugins/platforms/ios/quiview_textinput.mm @@ -39,6 +39,8 @@ ** ****************************************************************************/ +#include "qiosinputcontext.h" + #include class StaticVariables -- cgit v1.2.3