diff options
Diffstat (limited to 'src/plugins/platforms/ios/qioswindow.mm')
-rw-r--r-- | src/plugins/platforms/ios/qioswindow.mm | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm new file mode 100644 index 0000000000..e4fb5e2e1c --- /dev/null +++ b/src/plugins/platforms/ios/qioswindow.mm @@ -0,0 +1,523 @@ +/**************************************************************************** +** +** Copyright (C) 2013 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 "qiosglobal.h" +#include "qioswindow.h" +#include "qioscontext.h" +#include "qiosinputcontext.h" +#include "qiosscreen.h" +#include "qiosapplicationdelegate.h" +#include "qiosviewcontroller.h" +#include "qiosintegration.h" +#include <QtGui/private/qguiapplication_p.h> +#include <qpa/qplatformintegration.h> + +#import <QuartzCore/CAEAGLLayer.h> + +#include <QtGui/QKeyEvent> +#include <qpa/qwindowsysteminterface.h> + +#include <QtDebug> + +@interface EAGLView : UIView <UIKeyInput> +{ +@public + UITextAutocapitalizationType autocapitalizationType; + UITextAutocorrectionType autocorrectionType; + BOOL enablesReturnKeyAutomatically; + UIKeyboardAppearance keyboardAppearance; + UIKeyboardType keyboardType; + UIReturnKeyType returnKeyType; + BOOL secureTextEntry; + QIOSWindow *m_qioswindow; +} + +@property(nonatomic) UITextAutocapitalizationType autocapitalizationType; +@property(nonatomic) UITextAutocorrectionType autocorrectionType; +@property(nonatomic) BOOL enablesReturnKeyAutomatically; +@property(nonatomic) UIKeyboardAppearance keyboardAppearance; +@property(nonatomic) UIKeyboardType keyboardType; +@property(nonatomic) UIReturnKeyType returnKeyType; +@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; + +@end + +@implementation EAGLView + ++ (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]; + + // Set up text input + autocapitalizationType = UITextAutocapitalizationTypeNone; + autocorrectionType = UITextAutocorrectionTypeNo; + enablesReturnKeyAutomatically = NO; + keyboardAppearance = UIKeyboardAppearanceDefault; + keyboardType = UIKeyboardTypeDefault; + returnKeyType = UIReturnKeyDone; + secureTextEntry = NO; + + if (isQtApplication()) + self.hidden = YES; + + self.multipleTouchEnabled = YES; + } + + return self; +} + +- (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."; + + QRect geometry = fromCGRect(self.frame); + m_qioswindow->QPlatformWindow::setGeometry(geometry); + QWindowSystemInterface::handleGeometryChange(m_qioswindow->window(), geometry); + + // If we have a new size here we need to resize the FBO's corresponding buffers, + // but we defer that to when the application calls makeCurrent. + + [super layoutSubviews]; +} + +/* + Touch handling: + + UIKit generates [Began -> Moved -> Ended] event sequences for + each touch point. The iOS plugin tracks each individual + touch and assigns it an id for use by Qt. The id counter is + incremented on each began and decrement as follows: + 1) by one when the most recent touch ends. + 2) to zero when all touches ends. + + The TouchPoint list is reused between events. +*/ +- (void)updateTouchList:(NSSet *)touches withState:(Qt::TouchPointState)state +{ + QList<QWindowSystemInterface::TouchPoint> &touchPoints = m_qioswindow->touchPoints(); + QHash<UITouch *, int> &activeTouches = m_qioswindow->activeTouches(); + + // Mark all touch points as stationary + for (QList<QWindowSystemInterface::TouchPoint>::iterator it = touchPoints.begin(); it != touchPoints.end(); ++it) + it->state = Qt::TouchPointStationary; + + // Update changed touch points with the new state + for (UITouch *touch in touches) { + const int touchId = activeTouches.value(touch); + QWindowSystemInterface::TouchPoint &touchPoint = touchPoints[touchId]; + touchPoint.state = state; + if (state == Qt::TouchPointPressed) + touchPoint.pressure = 1.0; + else if (state == Qt::TouchPointReleased) + touchPoint.pressure = 0.0; + + // Set position + QRect viewGeometry = fromCGRect(self.frame); + QPoint touchViewLocation = fromCGPoint([touch locationInView:self]); + QPoint touchScreenLocation = touchViewLocation + viewGeometry.topLeft(); + touchPoint.area = QRectF(touchScreenLocation , QSize(0, 0)); + + CGSize fullscreenSize = self.window.rootViewController.view.bounds.size; + touchPoint.normalPosition = QPointF(touchScreenLocation.x() / fullscreenSize.width, touchScreenLocation.y() / fullscreenSize.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_qioswindow->touchPoints()); + QWindowSystemInterface::flushWindowSystemEvents(); +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + QWindow *window = m_qioswindow->window(); + + // Transfer focus to the touched window: + if (window != QGuiApplication::focusWindow()) + m_qioswindow->requestActivateWindow(); + + // Track Cocoa touch id to Qt touch id. The UITouch pointer is constant + // for the touch duration. + QHash<UITouch *, int> &activeTouches = m_qioswindow->activeTouches(); + QList<QWindowSystemInterface::TouchPoint> &touchPoints = m_qioswindow->touchPoints(); + for (UITouch *touch in touches) + activeTouches.insert(touch, m_qioswindow->touchId()++); + + // Create new touch points if needed. + int newTouchPointsNeeded = m_qioswindow->touchId() - touchPoints.count(); + for (int i = 0; i < newTouchPointsNeeded; ++i) { + QWindowSystemInterface::TouchPoint touchPoint; + touchPoint.id = touchPoints.count(); // id is the index in the touchPoints list. + touchPoints.append(touchPoint); + } + + [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 (event processing has completed at this point) + QHash<UITouch *, int> &activeTouches = m_qioswindow->activeTouches(); + for (UITouch *touch in touches) { + int id = activeTouches.take(touch); + + // If this touch is the most recent touch we can reuse its id + if (id == m_qioswindow->touchId() - 1) + --m_qioswindow->touchId(); + } + + // Reset the touch id when there are no more active touches + if (activeTouches.isEmpty()) + m_qioswindow->touchId() = 0; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event +{ + Q_UNUSED(touches) // ### can a subset of the active touches be cancelled? + + // Clear current touch points + m_qioswindow->activeTouches().clear(); + m_qioswindow->touchId() = 0; + + // Send cancel touch event synchronously + QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); + QWindowSystemInterface::handleTouchCancelEvent(m_qioswindow->window(), ulong(event.timestamp * 1000), iosIntegration->touchDevice()); + QWindowSystemInterface::flushWindowSystemEvents(); +} + +@synthesize autocapitalizationType; +@synthesize autocorrectionType; +@synthesize enablesReturnKeyAutomatically; +@synthesize keyboardAppearance; +@synthesize keyboardType; +@synthesize returnKeyType; +@synthesize secureTextEntry; + +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (BOOL)hasText +{ + return YES; +} + +- (void)insertText:(NSString *)text +{ + QString string = QString::fromUtf8([text UTF8String]); + int key = 0; + if ([text isEqualToString:@"\n"]) + key = (int)Qt::Key_Return; + + // Send key event to window system interface + QWindowSystemInterface::handleKeyEvent( + 0, QEvent::KeyPress, key, Qt::NoModifier, string, false, int(string.length())); + QWindowSystemInterface::handleKeyEvent( + 0, QEvent::KeyRelease, key, Qt::NoModifier, string, false, int(string.length())); +} + +- (void)deleteBackward +{ + // Send key event to window system interface + QWindowSystemInterface::handleKeyEvent( + 0, QEvent::KeyPress, (int)Qt::Key_Backspace, Qt::NoModifier); + QWindowSystemInterface::handleKeyEvent( + 0, QEvent::KeyRelease, (int)Qt::Key_Backspace, Qt::NoModifier); +} + +@end + +@implementation UIView (QIOS) + +- (QWindow *)qwindow +{ + if ([self isKindOfClass:[EAGLView class]]) + return static_cast<EAGLView *>(self)->m_qioswindow->window(); + return nil; +} + +@end + +QT_BEGIN_NAMESPACE + +QIOSWindow::QIOSWindow(QWindow *window) + : QPlatformWindow(window) + , m_view([[EAGLView alloc] initWithQIOSWindow:this]) + , m_touchId(0) + , m_requestedGeometry(QPlatformWindow::geometry()) + , m_windowLevel(0) + , m_devicePixelRatio(1.0) +{ + setParent(parent()); + setWindowState(window->windowState()); + + // Retina support: get screen scale factor and set it in the content view. + // This will make framebufferObject() create a 2x frame buffer on retina + // displays. Also set m_devicePixelRatio which is used for scaling the + // paint device. + if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] == YES) { + m_devicePixelRatio = [[UIScreen mainScreen] scale]; + [m_view setContentScaleFactor: m_devicePixelRatio]; + } +} + +QIOSWindow::~QIOSWindow() +{ + [m_view removeFromSuperview]; + [m_view release]; +} + +bool QIOSWindow::blockedByModal() +{ + QWindow *modalWindow = QGuiApplication::modalWindow(); + return modalWindow && modalWindow != window(); +} + +void QIOSWindow::setVisible(bool visible) +{ + QPlatformWindow::setVisible(visible); + m_view.hidden = !visible; + + if (!isQtApplication()) + return; + + // Since iOS doesn't do window management the way a Qt application + // expects, we need to raise and activate windows ourselves: + if (visible) + updateWindowLevel(); + + if (blockedByModal()) { + if (visible) + raise(); + return; + } + + if (visible) { + requestActivateWindow(); + } else { + // Activate top-most visible QWindow: + NSArray *subviews = rootViewController().view.subviews; + for (int i = int(subviews.count) - 1; i >= 0; --i) { + UIView *view = [subviews objectAtIndex:i]; + if (!view.hidden) { + if (QWindow *window = view.qwindow) { + static_cast<QIOSWindow *>(window->handle())->requestActivateWindow(); + break; + } + } + } + } +} + +void QIOSWindow::setGeometry(const QRect &rect) +{ + // If the window is in fullscreen, just bookkeep the requested + // geometry in case the window goes into Qt::WindowNoState later: + m_requestedGeometry = rect; + if (window()->windowState() & (Qt::WindowMaximized | Qt::WindowFullScreen)) + return; + + // Since we don't support transformations on the UIView, we can set the frame + // directly and let UIKit deal with translating that into bounds and center. + // Changing the size of the view will end up in a call to -[EAGLView layoutSubviews] + // which will update QWindowSystemInterface with the new size. + m_view.frame = toCGRect(rect); +} + +void QIOSWindow::setWindowState(Qt::WindowState state) +{ + // FIXME: Figure out where or how we should disable/enable the statusbar. + // Perhaps setting QWindow to maximized should also mean that we'll show + // the statusbar, and vice versa for fullscreen? + + switch (state) { + case Qt::WindowMaximized: + case Qt::WindowFullScreen: { + // Since UIScreen does not take orientation into account when + // reporting geometry, we need to look at the top view instead: + CGSize fullscreenSize = m_view.window.rootViewController.view.bounds.size; + m_view.frame = CGRectMake(0, 0, fullscreenSize.width, fullscreenSize.height); + m_view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + break; } + default: + m_view.frame = toCGRect(m_requestedGeometry); + m_view.autoresizingMask = UIViewAutoresizingNone; + break; + } +} + +void QIOSWindow::setParent(const QPlatformWindow *parentWindow) +{ + if (parentWindow) { + UIView *parentView = reinterpret_cast<UIView *>(parentWindow->winId()); + [parentView addSubview:m_view]; + } else if (isQtApplication()) { + [rootViewController().view addSubview:m_view]; + } +} + +void QIOSWindow::requestActivateWindow() +{ + // Note that several windows can be active at the same time if they exist in the same + // hierarchy (transient children). But only one window can be QGuiApplication::focusWindow(). + // Dispite the name, 'requestActivateWindow' means raise and transfer focus to the window: + if (blockedByModal()) + return; + + raise(); + QPlatformInputContext *context = QGuiApplicationPrivate::platformIntegration()->inputContext(); + static_cast<QIOSInputContext *>(context)->focusViewChanged(m_view); + QPlatformWindow::requestActivateWindow(); +} + +void QIOSWindow::raiseOrLower(bool raise) +{ + // Re-insert m_view at the correct index among its sibling views + // (QWindows) according to their current m_windowLevel: + if (!isQtApplication()) + return; + + NSArray *subviews = m_view.superview.subviews; + if (subviews.count == 1) + return; + + for (int i = int(subviews.count) - 1; i >= 0; --i) { + UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]); + if (view.hidden || view == m_view) + continue; + int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel; + if (m_windowLevel > level || (raise && m_windowLevel == level)) { + [m_view.superview insertSubview:m_view aboveSubview:view]; + return; + } + } + [m_view.superview insertSubview:m_view atIndex:0]; +} + +void QIOSWindow::updateWindowLevel() +{ + Qt::WindowType type = static_cast<Qt::WindowType>(int(window()->flags() & Qt::WindowType_Mask)); + + if (type == Qt::ToolTip) + m_windowLevel = 120; + else if (window()->flags() & Qt::WindowStaysOnTopHint) + m_windowLevel = 100; + else if (window()->isModal()) + m_windowLevel = 30; + else if (type & Qt::Popup & ~Qt::Window) + m_windowLevel = 20; + else if (type == Qt::Tool) + m_windowLevel = 10; + else + m_windowLevel = 0; + + // A window should be in at least the same m_windowLevel as its parent: + QWindow *transientParent = window()->transientParent(); + QIOSWindow *transientParentWindow = transientParent ? static_cast<QIOSWindow *>(transientParent->handle()) : 0; + if (transientParentWindow) + m_windowLevel = qMax(transientParentWindow->m_windowLevel, m_windowLevel); +} + +void QIOSWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation) +{ + // Keep the status bar in sync with content orientation. This will ensure + // that the task bar (and associated gestures) are aligned correctly: + UIDeviceOrientation uiOrientation = fromQtScreenOrientation(orientation); + [[UIApplication sharedApplication] setStatusBarOrientation:uiOrientation animated:NO]; +} + +qreal QIOSWindow::devicePixelRatio() const +{ + return m_devicePixelRatio; +} + +int QIOSWindow::effectiveWidth() const +{ + return geometry().width() * m_devicePixelRatio; +} + +int QIOSWindow::effectiveHeight() const +{ + return geometry().height() * m_devicePixelRatio; +} + +QT_END_NAMESPACE |