diff options
Diffstat (limited to 'src/plugins/platforms/ios')
36 files changed, 2650 insertions, 1080 deletions
diff --git a/src/plugins/platforms/ios/ios.pro b/src/plugins/platforms/ios/ios.pro index ffc4ff9b12..6b67a42f69 100644 --- a/src/plugins/platforms/ios/ios.pro +++ b/src/plugins/platforms/ios/ios.pro @@ -23,7 +23,12 @@ OBJECTIVE_SOURCES = \ qiostheme.mm \ qiosglobal.mm \ qiosservices.mm \ - qiosclipboard.mm + quiview.mm \ + qiosclipboard.mm \ + quiaccessibilityelement.mm \ + qiosplatformaccessibility.mm \ + qiostextresponder.mm \ + qiosmenu.mm \ HEADERS = \ qiosintegration.h \ @@ -40,7 +45,12 @@ HEADERS = \ qiosglobal.h \ qiosservices.h \ quiview.h \ - qiosclipboard.h + qiosclipboard.h \ + quiaccessibilityelement.h \ + qiosplatformaccessibility.h \ + qiostextresponder.h \ + qiosmenu.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..e89bb0d271 100644 --- a/src/plugins/platforms/ios/qiosapplicationdelegate.h +++ b/src/plugins/platforms/ios/qiosapplicationdelegate.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ @@ -45,7 +37,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/qiosapplicationstate.h b/src/plugins/platforms/ios/qiosapplicationstate.h index 57ea547e72..49620980db 100644 --- a/src/plugins/platforms/ios/qiosapplicationstate.h +++ b/src/plugins/platforms/ios/qiosapplicationstate.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ diff --git a/src/plugins/platforms/ios/qiosbackingstore.h b/src/plugins/platforms/ios/qiosbackingstore.h index 4199530a01..eff7455247 100644 --- a/src/plugins/platforms/ios/qiosbackingstore.h +++ b/src/plugins/platforms/ios/qiosbackingstore.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ diff --git a/src/plugins/platforms/ios/qiosclipboard.h b/src/plugins/platforms/ios/qiosclipboard.h index da4226a40d..7d758e3a56 100644 --- a/src/plugins/platforms/ios/qiosclipboard.h +++ b/src/plugins/platforms/ios/qiosclipboard.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ 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.h b/src/plugins/platforms/ios/qioscontext.h index 52357a5d58..9bc953cb6a 100644 --- a/src/plugins/platforms/ios/qioscontext.h +++ b/src/plugins/platforms/ios/qioscontext.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ 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/qioseventdispatcher.h b/src/plugins/platforms/ios/qioseventdispatcher.h index 5caa7f5d2d..fa35939c61 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.h +++ b/src/plugins/platforms/ios/qioseventdispatcher.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ diff --git a/src/plugins/platforms/ios/qiosglobal.h b/src/plugins/platforms/ios/qiosglobal.h index 17184dc21d..c09987f9dc 100644 --- a/src/plugins/platforms/ios/qiosglobal.h +++ b/src/plugins/platforms/ios/qiosglobal.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ @@ -65,4 +57,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..8850bbf80e 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.h +++ b/src/plugins/platforms/ios/qiosinputcontext.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ @@ -44,13 +36,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 +62,11 @@ public: ~QIOSInputContext(); QRectF keyboardRect() const; + void showInputPanel(); void hideInputPanel(); + void hideVirtualKeyboard(); + bool isInputPanelVisible() const; void setFocusObject(QObject *object); @@ -73,10 +79,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..13e91889a2 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -44,11 +44,19 @@ #import <UIKit/UIGestureRecognizerSubclass.h> #include "qiosglobal.h" +#include "qiostextresponder.h" #include "qioswindow.h" #include "quiview.h" + #include <QGuiApplication> #include <QtGui/private/qwindow_p.h> +static QUIView *focusView() +{ + return qApp->focusWindow() ? + reinterpret_cast<QUIView *>(qApp->focusWindow()->winId()) : 0; +} + @interface QIOSKeyboardListener : UIGestureRecognizer { @public QIOSInputContext *m_context; @@ -57,7 +65,7 @@ BOOL m_touchPressWhileKeyboardVisible; BOOL m_keyboardHiddenByGesture; QRectF m_keyboardRect; - QRectF m_keyboardEndRect; + CGRect m_keyboardEndRect; NSTimeInterval m_duration; UIViewAnimationCurve m_curve; UIViewController *m_viewController; @@ -131,20 +139,6 @@ [super dealloc]; } -- (QRectF) getKeyboardRect:(NSNotification *)notification -{ - // For Qt applications we rotate the keyboard rect to align with the screen - // orientation (which is the interface orientation of the root view controller). - // For hybrid apps we follow native behavior, and return the rect unmodified: - CGRect keyboardFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - if (isQtApplication()) { - UIView *view = m_viewController.view; - return fromCGRect(CGRectOffset([view convertRect:keyboardFrame fromView:view.window], 0, -view.bounds.origin.y)); - } else { - return fromCGRect(keyboardFrame); - } -} - - (void) keyboardDidChangeFrame:(NSNotification *)notification { Q_UNUSED(notification); @@ -158,26 +152,22 @@ - (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]; + m_keyboardEndRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; self.enabled = YES; if (!m_duration) { m_duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - m_curve = UIViewAnimationCurve([[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue] << 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]; + m_keyboardEndRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; if (!m_keyboardHiddenByGesture) { // Only disable the gesture if the hiding of the keyboard was not caused by it. // Otherwise we need to await the final touchEnd callback for doing some clean-up. @@ -188,14 +178,23 @@ - (void) handleKeyboardRectChanged { - QRectF rect = m_keyboardEndRect; - rect.moveTop(rect.y() + m_viewController.view.bounds.origin.y); - if (m_keyboardRect != rect) { - m_keyboardRect = rect; + // QInputmethod::keyboardRectangle() is documented to be in window coordinates. + // If there is no focus window, we return an empty rectangle + UIView *view = focusView(); + QRectF convertedRect = fromCGRect([view convertRect:m_keyboardEndRect fromView:nil]); + + // Set height to zero if keyboard is hidden. Otherwise the rect will not change + // when the keyboard hides on a scrolled screen (since the keyboard will already + // be at the bottom of the 'screen' in that case) + if (!m_keyboardVisibleAndDocked) + convertedRect.setHeight(0); + + if (convertedRect != m_keyboardRect) { + m_keyboardRect = convertedRect; m_context->emitKeyboardRectChanged(); } - BOOL visible = m_keyboardEndRect.intersects(fromCGRect([UIScreen mainScreen].bounds)); + BOOL visible = CGRectIntersectsRect(m_keyboardEndRect, [UIScreen mainScreen].bounds); if (m_keyboardVisible != visible) { m_keyboardVisible = visible; m_context->emitInputPanelVisibleChanged(); @@ -204,10 +203,10 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - QPointF p = fromCGPoint([[touches anyObject] locationInView:m_viewController.view]); - if (m_keyboardRect.contains(p)) { + CGPoint p = [[touches anyObject] locationInView:m_viewController.view.window]; + if (CGRectContainsPoint(m_keyboardEndRect, p)) { m_keyboardHiddenByGesture = YES; - m_context->hideInputPanel(); + m_context->hideVirtualKeyboard(); } [super touchesMoved:touches withEvent:event]; @@ -253,11 +252,37 @@ @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; +} + +// ------------------------------------------------------------------------- + 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 +292,7 @@ QIOSInputContext::QIOSInputContext() QIOSInputContext::~QIOSInputContext() { [m_keyboardListener release]; - [m_focusView release]; + [m_textResponder release]; } QRectF QIOSInputContext::keyboardRect() const @@ -277,61 +302,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; -} - -void QIOSInputContext::setFocusObject(QObject *focusObject) -{ - if (!focusObject || !m_focusView || !m_focusView.isFirstResponder) { - scroll(0); - return; - } - - reset(); - - if (m_keyboardListener->m_keyboardVisibleAndDocked) - scrollToCursor(); + static_cast<QWindowPrivate *>(QObjectPrivate::get(qApp->focusWindow()))->clearFocusObject(); } -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 +339,7 @@ void QIOSInputContext::cursorRectangleChanged() void QIOSInputContext::scrollToCursor() { - if (!isQtApplication() || !m_focusView) + if (!isQtApplication()) return; if (m_keyboardListener->m_touchPressWhileKeyboardVisible) { @@ -364,13 +350,14 @@ 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()); - qreal keyboardY = m_keyboardListener->m_keyboardEndRect.y(); + translatedCursorPos.translate(focusView().qwindow->geometry().topLeft()); + + qreal keyboardY = [view convertRect:m_keyboardListener->m_keyboardEndRect fromView:nil].origin.y; int statusBarY = qGuiApp->primaryScreen()->availableGeometry().y(); scroll((translatedCursorPos.bottomLeft().y() < keyboardY - margin) ? 0 @@ -379,18 +366,40 @@ void QIOSInputContext::scrollToCursor() void QIOSInputContext::scroll(int y) { - // Scroll the view the same way a UIScrollView - // works: by changing bounds.origin: - UIView *view = m_keyboardListener->m_viewController.view; - if (y == view.bounds.origin.y) + UIView *rootView = m_keyboardListener->m_viewController.view; + + CATransform3D translationTransform = CATransform3DMakeTranslation(0.0, -y, 0.0); + if (CATransform3DEqualToTransform(translationTransform, rootView.layer.sublayerTransform)) return; - CGRect newBounds = view.bounds; - newBounds.origin.y = y; QPointer<QIOSInputContext> self = this; [UIView animateWithDuration:m_keyboardListener->m_duration delay:0 - options:m_keyboardListener->m_curve | UIViewAnimationOptionBeginFromCurrentState - animations:^{ view.bounds = newBounds; } + options:(m_keyboardListener->m_curve << 16) | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + // The sublayerTransform property of CALayer is not implicitly animated for a + // layer-backed view, even inside a UIView animation block, so we need to set up + // an explicit CoreAnimation animation. Since there is no predefined media timing + // function that matches the custom keyboard animation curve we cheat by asking + // the view for an animation of another property, which will give us an animation + // that matches the parameters we passed to [UIView animateWithDuration] above. + // The reason we ask for the animation of 'backgroundColor' is that it's a simple + // property that will not return a compound animation, like eg. bounds will. + NSObject *action = (NSObject*)[rootView actionForLayer:rootView.layer forKey:@"backgroundColor"]; + + CABasicAnimation *animation; + if ([action isKindOfClass:[CABasicAnimation class]]) { + animation = static_cast<CABasicAnimation*>(action); + animation.keyPath = @"sublayerTransform"; // Instead of backgroundColor + } else { + animation = [CABasicAnimation animationWithKeyPath:@"sublayerTransform"]; + } + + CATransform3D currentSublayerTransform = static_cast<CALayer *>([rootView.layer presentationLayer]).sublayerTransform; + animation.fromValue = [NSValue valueWithCATransform3D:currentSublayerTransform]; + animation.toValue = [NSValue valueWithCATransform3D:translationTransform]; + [rootView.layer addAnimation:animation forKey:@"AnimateSubLayerTransform"]; + rootView.layer.sublayerTransform = translationTransform; + } completion:^(BOOL){ if (self) [m_keyboardListener handleKeyboardRectChanged]; @@ -398,18 +407,85 @@ 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) { - [m_focusView updateInputMethodWithQuery:query]; + Q_UNUSED(focusWindow); + + reset(); + + [m_keyboardListener handleKeyboardRectChanged]; + 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) +{ + // 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..85338b1c4d 100644 --- a/src/plugins/platforms/ios/qiosintegration.h +++ b/src/plugins/platforms/ios/qiosintegration.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ @@ -82,14 +74,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/qiosmenu.h b/src/plugins/platforms/ios/qiosmenu.h new file mode 100644 index 0000000000..16e1ee5d1f --- /dev/null +++ b/src/plugins/platforms/ios/qiosmenu.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** 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 QIOSMENU_H +#define QIOSMENU_H + +#import <UIKit/UIKit.h> + +#include <QtCore/QtCore> +#include <qpa/qplatformmenu.h> + +#import "quiview.h" + +class QIOSMenu; +@class QUIMenuController; +@class QUIPickerView; + +class QIOSMenuItem : public QPlatformMenuItem +{ +public: + QIOSMenuItem(); + + void setTag(quintptr tag) Q_DECL_OVERRIDE; + quintptr tag()const Q_DECL_OVERRIDE; + + void setText(const QString &text) Q_DECL_OVERRIDE; + void setIcon(const QIcon &) Q_DECL_OVERRIDE {} + void setMenu(QPlatformMenu *) Q_DECL_OVERRIDE; + void setVisible(bool isVisible) Q_DECL_OVERRIDE; + void setIsSeparator(bool) Q_DECL_OVERRIDE; + void setFont(const QFont &) Q_DECL_OVERRIDE {} + void setRole(MenuRole role) Q_DECL_OVERRIDE; + void setCheckable(bool) Q_DECL_OVERRIDE {} + void setChecked(bool) Q_DECL_OVERRIDE {} + void setShortcut(const QKeySequence&) Q_DECL_OVERRIDE {} + void setEnabled(bool enabled) Q_DECL_OVERRIDE; + void setIconSize(int) Q_DECL_OVERRIDE {} + + quintptr m_tag; + bool m_visible; + QString m_text; + MenuRole m_role; + bool m_enabled; + bool m_separator; + QIOSMenu *m_menu; + +private: + QString removeMnemonics(const QString &original); +}; + +typedef QList<QIOSMenuItem *> QIOSMenuItemList; + +class QIOSMenu : public QPlatformMenu +{ +public: + QIOSMenu(); + ~QIOSMenu(); + + void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) Q_DECL_OVERRIDE; + void removeMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE; + void syncMenuItem(QPlatformMenuItem *) Q_DECL_OVERRIDE {} + void syncSeparatorsCollapsible(bool) Q_DECL_OVERRIDE {} + + void setTag(quintptr tag) Q_DECL_OVERRIDE; + quintptr tag()const Q_DECL_OVERRIDE; + + void setText(const QString &) Q_DECL_OVERRIDE; + void setIcon(const QIcon &) Q_DECL_OVERRIDE {} + void setEnabled(bool enabled) Q_DECL_OVERRIDE; + void setVisible(bool visible) Q_DECL_OVERRIDE; + void setMenuType(MenuType type) Q_DECL_OVERRIDE; + + void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) Q_DECL_OVERRIDE; + void dismiss() Q_DECL_OVERRIDE; + + QPlatformMenuItem *menuItemAt(int position) const Q_DECL_OVERRIDE; + QPlatformMenuItem *menuItemForTag(quintptr tag) const Q_DECL_OVERRIDE; + + void handleItemSelected(QIOSMenuItem *menuItem); + + static QIOSMenu *currentMenu() { return m_currentMenu; } + static id menuActionTarget() { return m_currentMenu ? m_currentMenu->m_menuController : 0; } + +protected: + bool eventFilter(QObject *obj, QEvent *event); + +private: + quintptr m_tag; + bool m_enabled; + bool m_visible; + QString m_text; + MenuType m_menuType; + MenuType m_effectiveMenuType; + QPointer<QWindow> m_parentWindow; + QRect m_targetRect; + const QIOSMenuItem *m_targetItem; + QUIMenuController *m_menuController; + QUIPickerView *m_pickerView; + QIOSMenuItemList m_menuItems; + + static QIOSMenu *m_currentMenu; + + void updateVisibility(); + void toggleShowUsingUIMenuController(bool show); + void toggleShowUsingUIPickerView(bool show); + QIOSMenuItemList visibleMenuItems() const; + void repositionMenu(); +}; + +#endif // QIOSMENU_H diff --git a/src/plugins/platforms/ios/qiosmenu.mm b/src/plugins/platforms/ios/qiosmenu.mm new file mode 100644 index 0000000000..c247c16514 --- /dev/null +++ b/src/plugins/platforms/ios/qiosmenu.mm @@ -0,0 +1,528 @@ +/**************************************************************************** +** +** 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 <qglobal.h> +#include <qguiapplication.h> + +#include "qiosglobal.h" +#include "qiosmenu.h" +#include "qioswindow.h" +#include "qiosinputcontext.h" +#include "qiosintegration.h" +#include "qiostextresponder.h" + +// m_currentMenu points to the currently visible menu. +// Only one menu will be visible at a time, and if a second menu +// is shown on top of a first, the first one will be told to hide. +QIOSMenu *QIOSMenu::m_currentMenu = 0; + +// ------------------------------------------------------------------------- + +static NSString *const kSelectorPrefix = @"_qtMenuItem_"; + +@interface QUIMenuController : UIResponder { + QIOSMenuItemList m_visibleMenuItems; +} +@end + +@implementation QUIMenuController + +- (id)initWithVisibleMenuItems:(const QIOSMenuItemList &)visibleMenuItems +{ + if (self = [super init]) { + m_visibleMenuItems = visibleMenuItems; + NSMutableArray *menuItemArray = [NSMutableArray arrayWithCapacity:m_visibleMenuItems.size()]; + // Create an array of UIMenuItems, one for each visible QIOSMenuItem. Each + // UIMenuItem needs a callback assigned, so we assign one of the placeholder methods + // added to UIWindow (QIOSMenuActionTargets) below. Each method knows its own index, which + // corresponds to the index of the corresponding QIOSMenuItem in m_visibleMenuItems. When + // triggered, menuItemActionCallback will end up being called. + for (int i = 0; i < m_visibleMenuItems.count(); ++i) { + QIOSMenuItem *item = m_visibleMenuItems.at(i); + SEL sel = NSSelectorFromString([NSString stringWithFormat:@"%@%i:", kSelectorPrefix, i]); + [menuItemArray addObject:[[[UIMenuItem alloc] initWithTitle:item->m_text.toNSString() action:sel] autorelease]]; + } + [UIMenuController sharedMenuController].menuItems = menuItemArray; + } + + return self; +} + +- (id)targetForAction:(SEL)action withSender:(id)sender +{ + BOOL containsPrefix = ([NSStringFromSelector(action) rangeOfString:kSelectorPrefix].location != NSNotFound); + return (containsPrefix && [sender isKindOfClass:[UIMenuController class]]) ? self : 0; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector +{ + Q_UNUSED(selector); + // Just return a dummy signature that NSObject can create an NSInvocation from. + // We end up only checking selector in forwardInvocation anyway. + return [super methodSignatureForSelector:@selector(methodSignatureForSelector:)]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + // Since none of the menu item selector methods actually exist, this function + // will end up being called as a final resort. We can then handle the action. + NSString *selector = NSStringFromSelector(invocation.selector); + NSRange range = NSMakeRange(kSelectorPrefix.length, selector.length - kSelectorPrefix.length - 1); + NSInteger selectedIndex = [[selector substringWithRange:range] integerValue]; + QIOSMenu::currentMenu()->handleItemSelected(m_visibleMenuItems.at(selectedIndex)); +} + +@end + +// ------------------------------------------------------------------------- + +@interface QUIPickerView : UIPickerView <UIPickerViewDelegate, UIPickerViewDataSource> { + QIOSMenuItemList m_visibleMenuItems; + QPointer<QObject> m_focusObjectWithPickerView; + NSInteger m_selectedRow; +} + +@property(retain) UIToolbar *toolbar; + +@end + +@implementation QUIPickerView + +- (id)initWithVisibleMenuItems:(const QIOSMenuItemList &)visibleMenuItems selectItem:(const QIOSMenuItem *)selectItem +{ + if (self = [super init]) { + self.autoresizingMask = UIViewAutoresizingFlexibleWidth; + m_visibleMenuItems = visibleMenuItems; + m_selectedRow = visibleMenuItems.indexOf(const_cast<QIOSMenuItem *>(selectItem)); + if (m_selectedRow == -1) + m_selectedRow = 0; + + self.toolbar = [[[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 100, 44)] autorelease]; + self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + UIBarButtonItem *doneButton = [[[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self action:@selector(closeMenu)] autorelease]; + UIBarButtonItem *spaceButton = [[[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace + target:self action:@selector(closeMenu)] autorelease]; + UIBarButtonItem *cancelButton = [[[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemCancel + target:self action:@selector(cancelMenu)] autorelease]; + [self.toolbar setItems:[NSArray arrayWithObjects:doneButton, spaceButton, cancelButton, nil]]; + + [self setDelegate:self]; + [self setDataSource:self]; + [self selectRow:m_selectedRow inComponent:0 animated:false]; + } + + return self; +} + +-(void)dealloc +{ + self.toolbar = 0; + [super dealloc]; +} + +- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component +{ + Q_UNUSED(pickerView); + Q_UNUSED(component); + return m_visibleMenuItems.at(row)->m_text.toNSString(); +} + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView +{ + Q_UNUSED(pickerView); + return 1; +} + +- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component +{ + Q_UNUSED(pickerView); + Q_UNUSED(component); + return m_visibleMenuItems.length(); +} + +- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component +{ + Q_UNUSED(pickerView); + Q_UNUSED(component); + m_selectedRow = row; +} + +- (void)closeMenu +{ + if (!m_visibleMenuItems.isEmpty()) + QIOSMenu::currentMenu()->handleItemSelected(m_visibleMenuItems.at(m_selectedRow)); + else + QIOSMenu::currentMenu()->dismiss(); +} + +- (void)cancelMenu +{ + QIOSMenu::currentMenu()->dismiss(); +} + +@end + +// ------------------------------------------------------------------------- + +QIOSMenuItem::QIOSMenuItem() + : QPlatformMenuItem() + , m_tag(0) + , m_visible(true) + , m_text(QString()) + , m_role(MenuRole(0)) + , m_enabled(true) + , m_separator(false) + , m_menu(0) +{ +} + +void QIOSMenuItem::setTag(quintptr tag) +{ + m_tag = tag; +} + +quintptr QIOSMenuItem::tag() const +{ + return m_tag; +} + +void QIOSMenuItem::setText(const QString &text) +{ + m_text = removeMnemonics(text); +} + +void QIOSMenuItem::setMenu(QPlatformMenu *menu) +{ + m_menu = static_cast<QIOSMenu *>(menu); +} + +void QIOSMenuItem::setVisible(bool isVisible) +{ + m_visible = isVisible; +} + +void QIOSMenuItem::setIsSeparator(bool isSeparator) +{ + m_separator = isSeparator; +} + +void QIOSMenuItem::setRole(QPlatformMenuItem::MenuRole role) +{ + m_role = role; +} + +void QIOSMenuItem::setEnabled(bool enabled) +{ + m_enabled = enabled; +} + +QString QIOSMenuItem::removeMnemonics(const QString &original) +{ + // Copied from qcocoahelpers + QString returnText(original.size(), 0); + int finalDest = 0; + int currPos = 0; + int l = original.length(); + while (l) { + if (original.at(currPos) == QLatin1Char('&') + && (l == 1 || original.at(currPos + 1) != QLatin1Char('&'))) { + ++currPos; + --l; + if (l == 0) + break; + } else if (original.at(currPos) == QLatin1Char('(') && l >= 4 && + original.at(currPos + 1) == QLatin1Char('&') && + original.at(currPos + 2) != QLatin1Char('&') && + original.at(currPos + 3) == QLatin1Char(')')) { + /* remove mnemonics its format is "\s*(&X)" */ + int n = 0; + while (finalDest > n && returnText.at(finalDest - n - 1).isSpace()) + ++n; + finalDest -= n; + currPos += 4; + l -= 4; + continue; + } + returnText[finalDest] = original.at(currPos); + ++currPos; + ++finalDest; + --l; + } + returnText.truncate(finalDest); + return returnText; +} + +QIOSMenu::QIOSMenu() + : QPlatformMenu() + , m_tag(0) + , m_enabled(true) + , m_visible(true) + , m_text(QString()) + , m_menuType(DefaultMenu) + , m_effectiveMenuType(DefaultMenu) + , m_parentWindow(0) + , m_targetItem(0) + , m_menuController(0) + , m_pickerView(0) +{ +} + +QIOSMenu::~QIOSMenu() +{ + dismiss(); +} + +void QIOSMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) +{ + if (!before) { + m_menuItems.append(static_cast<QIOSMenuItem *>(menuItem)); + } else { + int index = m_menuItems.indexOf(static_cast<QIOSMenuItem *>(before)) + 1; + m_menuItems.insert(index, static_cast<QIOSMenuItem *>(menuItem)); + } +} + +void QIOSMenu::removeMenuItem(QPlatformMenuItem *menuItem) +{ + m_menuItems.removeOne(static_cast<QIOSMenuItem *>(menuItem)); +} + +void QIOSMenu::setTag(quintptr tag) +{ + m_tag = tag; +} + +quintptr QIOSMenu::tag() const +{ + return m_tag; +} + +void QIOSMenu::setText(const QString &text) +{ + m_text = text; +} + +void QIOSMenu::setEnabled(bool enabled) +{ + m_enabled = enabled; +} + +void QIOSMenu::setVisible(bool visible) +{ + m_visible = visible; +} + +void QIOSMenu::setMenuType(QPlatformMenu::MenuType type) +{ + m_menuType = type; +} + +void QIOSMenu::handleItemSelected(QIOSMenuItem *menuItem) +{ + emit menuItem->activated(); + dismiss(); + + if (QIOSMenu *menu = menuItem->m_menu) { + menu->setMenuType(m_effectiveMenuType); + menu->showPopup(m_parentWindow, m_targetRect, 0); + } +} + +void QIOSMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) +{ + if (m_currentMenu == this || !m_visible || !m_enabled || !parentWindow) + return; + + emit aboutToShow(); + + m_parentWindow = const_cast<QWindow *>(parentWindow); + m_targetRect = targetRect; + m_targetItem = static_cast<const QIOSMenuItem *>(item); + + if (!m_parentWindow->isActive()) + m_parentWindow->requestActivate(); + + if (m_currentMenu && m_currentMenu != this) + m_currentMenu->dismiss(); + + m_currentMenu = this; + m_effectiveMenuType = m_menuType; + connect(qGuiApp, &QGuiApplication::focusObjectChanged, this, &QIOSMenu::dismiss); + + switch (m_effectiveMenuType) { + case EditMenu: + toggleShowUsingUIMenuController(true); + break; + default: + toggleShowUsingUIPickerView(true); + break; + } +} + +void QIOSMenu::dismiss() +{ + if (m_currentMenu != this) + return; + + emit aboutToHide(); + + disconnect(qGuiApp, &QGuiApplication::focusObjectChanged, this, &QIOSMenu::dismiss); + + switch (m_effectiveMenuType) { + case EditMenu: + toggleShowUsingUIMenuController(false); + break; + default: + toggleShowUsingUIPickerView(false); + break; + } + + m_currentMenu = 0; +} + +void QIOSMenu::toggleShowUsingUIMenuController(bool show) +{ + if (show) { + Q_ASSERT(!m_menuController); + m_menuController = [[QUIMenuController alloc] initWithVisibleMenuItems:visibleMenuItems()]; + repositionMenu(); + connect(qGuiApp->inputMethod(), &QInputMethod::keyboardRectangleChanged, this, &QIOSMenu::repositionMenu); + } else { + disconnect(qGuiApp->inputMethod(), &QInputMethod::keyboardRectangleChanged, this, &QIOSMenu::repositionMenu); + + Q_ASSERT(m_menuController); + [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES]; + [m_menuController release]; + m_menuController = 0; + } +} + +void QIOSMenu::toggleShowUsingUIPickerView(bool show) +{ + static QObject *focusObjectWithPickerView = 0; + + if (show) { + Q_ASSERT(!m_pickerView); + m_pickerView = [[QUIPickerView alloc] initWithVisibleMenuItems:visibleMenuItems() selectItem:m_targetItem]; + + Q_ASSERT(!focusObjectWithPickerView); + focusObjectWithPickerView = qApp->focusWindow()->focusObject(); + focusObjectWithPickerView->installEventFilter(this); + qApp->inputMethod()->update(Qt::ImPlatformData); + } else { + Q_ASSERT(focusObjectWithPickerView); + focusObjectWithPickerView->removeEventFilter(this); + qApp->inputMethod()->update(Qt::ImPlatformData); + focusObjectWithPickerView = 0; + + Q_ASSERT(m_pickerView); + [m_pickerView release]; + m_pickerView = 0; + } +} + +bool QIOSMenu::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::InputMethodQuery) { + QInputMethodQueryEvent *queryEvent = static_cast<QInputMethodQueryEvent *>(event); + if (queryEvent->queries() & Qt::ImPlatformData) { + // Let object fill inn default query results + obj->event(queryEvent); + + QVariantMap imPlatformData = queryEvent->value(Qt::ImPlatformData).toMap(); + imPlatformData.insert(kImePlatformDataInputView, QVariant::fromValue(static_cast<void *>(m_pickerView))); + imPlatformData.insert(kImePlatformDataInputAccessoryView, QVariant::fromValue(static_cast<void *>(m_pickerView.toolbar))); + queryEvent->setValue(Qt::ImPlatformData, imPlatformData); + + return true; + } + } + + return QObject::eventFilter(obj, event); +} + +QIOSMenuItemList QIOSMenu::visibleMenuItems() const +{ + QIOSMenuItemList visibleMenuItems = m_menuItems; + + for (int i = visibleMenuItems.count() - 1; i >= 0; --i) { + QIOSMenuItem *item = visibleMenuItems.at(i); + if (!item->m_enabled || !item->m_visible || item->m_separator) + visibleMenuItems.removeAt(i); + } + + return visibleMenuItems; +} + +void QIOSMenu::repositionMenu() +{ + switch (m_effectiveMenuType) { + case EditMenu: { + UIView *view = reinterpret_cast<UIView *>(m_parentWindow->winId()); + [[UIMenuController sharedMenuController] setTargetRect:toCGRect(m_targetRect) inView:view]; + [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES]; + break; } + default: + break; + } +} + +QPlatformMenuItem *QIOSMenu::menuItemAt(int position) const +{ + if (position < 0 || position >= m_menuItems.size()) + return 0; + return m_menuItems.at(position); +} + +QPlatformMenuItem *QIOSMenu::menuItemForTag(quintptr tag) const +{ + for (int i = 0; i < m_menuItems.size(); ++i) { + QPlatformMenuItem *item = m_menuItems.at(i); + if (item->tag() == tag) + return item; + } + return 0; +} diff --git a/src/plugins/platforms/ios/qiosplatformaccessibility.h b/src/plugins/platforms/ios/qiosplatformaccessibility.h new file mode 100644 index 0000000000..4aa9dc3c7d --- /dev/null +++ b/src/plugins/platforms/ios/qiosplatformaccessibility.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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:LGPL21$ +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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. +** +** $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..7987ef82d5 100644 --- a/src/plugins/platforms/ios/qiosscreen.h +++ b/src/plugins/platforms/ios/qiosscreen.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ @@ -55,11 +47,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; @@ -75,13 +65,10 @@ public: UIScreen *uiScreen() const; void updateProperties(); - void layoutWindows(); - -public slots: - void updateStatusBarVisibility(); 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..d8a49eace6 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,30 +178,52 @@ 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; + } } - connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &QIOSScreen::updateStatusBarVisibility); + 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; + } + } updateProperties(); } @@ -149,111 +231,24 @@ 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() - : QRect(m_uiScreen.bounds.origin.x, m_uiScreen.bounds.origin.y, - m_uiScreen.bounds.size.height, m_uiScreen.bounds.size.width); + UIView *rootView = m_uiWindow.rootViewController.view; - if (geometry != m_geometry) { - m_geometry = geometry; + m_geometry = fromCGRect([rootView convertRect:m_uiScreen.bounds fromView:m_uiWindow]).toRect(); + m_availableGeometry = fromCGRect([rootView convertRect:m_uiScreen.applicationFrame fromView:m_uiWindow]).toRect(); + 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); - } - - QRect availableGeometry = geometry; - - CGSize applicationFrameSize = m_uiScreen.applicationFrame.size; - int statusBarHeight = geometry.height() - (inPortrait ? applicationFrameSize.height : applicationFrameSize.width); - - availableGeometry.adjust(0, statusBarHeight, 0, 0); - - if (availableGeometry != m_availableGeometry) { - m_availableGeometry = availableGeometry; - QWindowSystemInterface::handleScreenAvailableGeometryChange(screen(), m_availableGeometry); - } - - if (screen()) - layoutWindows(); -} - -void QIOSScreen::updateStatusBarVisibility() -{ - if (!isQtApplication()) - return; - - QWindow *focusWindow = QGuiApplication::focusWindow(); - - // If we don't have a focus window we leave the status - // bar as is, so that the user can activate a new window - // with the same window state without the status bar jumping - // back and forth. - if (!focusWindow) - return; - - UIView *view = reinterpret_cast<UIView *>(focusWindow->handle()->winId()); - QIOSViewController *viewController = static_cast<QIOSViewController *>(view.viewController); - - bool currentStatusBarVisibility = [UIApplication sharedApplication].statusBarHidden; - if (viewController.prefersStatusBarHidden == currentStatusBarVisibility) - return; - -#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_7_0) { - [viewController setNeedsStatusBarAppearanceUpdate]; - dispatch_async(dispatch_get_main_queue(), ^{ - updateProperties(); - }); - } else -#endif - { - [[UIApplication sharedApplication] - setStatusBarHidden:[viewController prefersStatusBarHidden] - withAnimation:UIStatusBarAnimationNone]; - - updateProperties(); - } -} - -void QIOSScreen::layoutWindows() -{ - QList<QWindow*> windows = QGuiApplication::topLevelWindows(); - - const QRect oldGeometry = screen()->geometry(); - const QRect oldAvailableGeometry = screen()->availableGeometry(); - const QRect newGeometry = geometry(); - const QRect newAvailableGeometry = availableGeometry(); - - for (int i = 0; i < windows.size(); ++i) { - QWindow *window = windows.at(i); - - if (platformScreenForWindow(window) != this) - continue; - - QIOSWindow *platformWindow = static_cast<QIOSWindow *>(window->handle()); - if (!platformWindow) - continue; - - // FIXME: Handle more complex cases of no-state and/or child windows when rotating - - if (window->windowState() & Qt::WindowFullScreen - || (window->windowState() & Qt::WindowNoState && window->geometry() == oldGeometry)) - platformWindow->applyGeometry(newGeometry); - else if (window->windowState() & Qt::WindowMaximized - || (window->windowState() & Qt::WindowNoState && window->geometry() == oldAvailableGeometry)) - platformWindow->applyGeometry(newAvailableGeometry); + QWindowSystemInterface::handleScreenGeometryChange(screen(), m_geometry, m_availableGeometry); } } @@ -294,8 +289,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/qiosservices.h b/src/plugins/platforms/ios/qiosservices.h index aa39fbbed4..91b71f8b47 100644 --- a/src/plugins/platforms/ios/qiosservices.h +++ b/src/plugins/platforms/ios/qiosservices.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ 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/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h index b03f65f556..16b330a030 100644 --- a/src/plugins/platforms/ios/qiostheme.h +++ b/src/plugins/platforms/ios/qiostheme.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ @@ -55,6 +47,9 @@ public: QVariant themeHint(ThemeHint hint) const; + QPlatformMenuItem* createPlatformMenuItem() const Q_DECL_OVERRIDE; + QPlatformMenu* createPlatformMenu() const Q_DECL_OVERRIDE; + const QFont *font(Font type = SystemFont) const; static const char *name; diff --git a/src/plugins/platforms/ios/qiostheme.mm b/src/plugins/platforms/ios/qiostheme.mm index e51e97bd5a..cbeb157cf2 100644 --- a/src/plugins/platforms/ios/qiostheme.mm +++ b/src/plugins/platforms/ios/qiostheme.mm @@ -53,6 +53,8 @@ #include <UIKit/UIFont.h> #include <UIKit/UIInterface.h> +#include "qiosmenu.h" + QT_BEGIN_NAMESPACE const char *QIOSTheme::name = "ios"; @@ -66,6 +68,16 @@ QIOSTheme::~QIOSTheme() qDeleteAll(m_fonts); } +QPlatformMenuItem* QIOSTheme::createPlatformMenuItem() const +{ + return new QIOSMenuItem(); +} + +QPlatformMenu* QIOSTheme::createPlatformMenu() const +{ + return new QIOSMenu(); +} + QVariant QIOSTheme::themeHint(ThemeHint hint) const { switch (hint) { diff --git a/src/plugins/platforms/ios/qiosviewcontroller.h b/src/plugins/platforms/ios/qiosviewcontroller.h index a0017808d3..cbb28aec0d 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.h +++ b/src/plugins/platforms/ios/qiosviewcontroller.h @@ -1,47 +1,48 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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> -@interface QIOSViewController : UIViewController -- (BOOL)prefersStatusBarHidden; +class QIOSScreen; + +@interface QIOSViewController : UIViewController { + QIOSScreen *m_screen; +} + +@property (nonatomic, assign) BOOL changingOrientation; +@property (nonatomic, assign) BOOL prefersStatusBarHidden; + +- (id)initWithQIOSScreen:(QIOSScreen *)screen; +- (void)updateProperties; @end diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm index 2fe679fc20..ca8d48bf6d 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.mm +++ b/src/plugins/platforms/ios/qiosviewcontroller.mm @@ -50,9 +50,179 @@ #include "qiosscreen.h" #include "qiosglobal.h" #include "qioswindow.h" +#include "quiview.h" + +// ------------------------------------------------------------------------- + +@interface QIOSDesktopManagerView : UIView +@end + +@implementation QIOSDesktopManagerView + +- (void)layoutSubviews +{ + for (int i = int(self.subviews.count) - 1; i >= 0; --i) { + UIView *view = static_cast<UIView *>([self.subviews objectAtIndex:i]); + if (![view isKindOfClass:[QUIView class]]) + continue; + + [self layoutView: static_cast<QUIView *>(view)]; + } +} + +- (void)layoutView:(QUIView *)view +{ + QWindow *window = view.qwindow; + Q_ASSERT(window->handle()); + + // Re-apply window states to update geometry + if (window->windowState() & (Qt::WindowFullScreen | Qt::WindowMaximized)) + window->handle()->setWindowState(window->windowState()); +} + +// Even if the root view controller has both wantsFullScreenLayout and +// extendedLayoutIncludesOpaqueBars enabled, iOS will still push the root +// view down 20 pixels (and shrink the view accordingly) when the in-call +// statusbar is active (instead of updating the topLayoutGuide). Since +// we treat the root view controller as our screen, we want to reflect +// the in-call statusbar as a change in available geometry, not in screen +// geometry. To simplify the screen geometry mapping code we reset the +// view modifications that iOS does and take the statusbar height +// explicitly into account in QIOSScreen::updateProperties(). + +- (void)setFrame:(CGRect)newFrame +{ + [super setFrame:CGRectMake(0, 0, CGRectGetWidth(newFrame), CGRectGetHeight(self.window.bounds))]; +} + +- (void)setBounds:(CGRect)newBounds +{ + CGRect transformedWindowBounds = [self convertRect:self.window.bounds fromView:self.window]; + [super setBounds:CGRectMake(0, 0, CGRectGetWidth(newBounds), CGRectGetHeight(transformedWindowBounds))]; +} + +- (void)setCenter:(CGPoint)newCenter +{ + Q_UNUSED(newCenter); + [super setCenter:self.window.center]; +} + +- (void)didMoveToWindow +{ + // The initial frame computed during startup may happen before the view has + // a window, meaning our calculations above will be wrong. We ensure that the + // frame is set correctly once we have a window to base our calulations on. + [self setFrame:self.window.bounds]; +} + +@end + +// ------------------------------------------------------------------------- @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 + + self.changingOrientation = NO; + + // Status bar may be initially hidden at startup through Info.plist + self.prefersStatusBarHidden = infoPlistValue(@"UIStatusBarHidden", false); + + QObject::connect(qApp, &QGuiApplication::focusWindowChanged, [self]() { + [self updateProperties]; + }); + } + + return self; +} + +- (void)loadView +{ + self.view = [[[QIOSDesktopManagerView alloc] init] autorelease]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(willChangeStatusBarFrame:) + name:UIApplicationWillChangeStatusBarFrameNotification + object:[UIApplication sharedApplication]]; +} + +- (void)viewDidUnload +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:nil]; + [super viewDidUnload]; +} + +// ------------------------------------------------------------------------- + +- (void)updateProperties +{ + if (!isQtApplication()) + return; + + QWindow *focusWindow = QGuiApplication::focusWindow(); + + // If we don't have a focus window we leave the statusbar + // as is, so that the user can activate a new window with + // the same window state without the status bar jumping + // back and forth. + if (!focusWindow) + return; + + // We only care about changes to focusWindow that involves our screen + if (!focusWindow->screen() || focusWindow->screen()->handle() != m_screen) + return; + + // All decisions are based on the the top level window + focusWindow = qt_window_private(focusWindow)->topLevelWindow(); + + bool currentStatusBarVisibility = self.prefersStatusBarHidden; + self.prefersStatusBarHidden = focusWindow->windowState() == Qt::WindowFullScreen; + if (self.prefersStatusBarHidden != currentStatusBarVisibility) { +#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_IOS_7_0) { + [self setNeedsStatusBarAppearanceUpdate]; + } else +#endif + { + [[UIApplication sharedApplication] + setStatusBarHidden:self.prefersStatusBarHidden + withAnimation:UIStatusBarAnimationNone]; + } + + [self.view setNeedsLayout]; + } +} + +// ------------------------------------------------------------------------- + -(BOOL)shouldAutorotate { // Until a proper orientation and rotation API is in place, we always auto rotate. @@ -77,16 +247,55 @@ } #endif -- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration { + Q_UNUSED(orientation); Q_UNUSED(duration); - Q_UNUSED(interfaceOrientation); + self.changingOrientation = YES; +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orientation +{ + Q_UNUSED(orientation); + + self.changingOrientation = NO; +} + +- (void)willChangeStatusBarFrame:(NSNotification*)notification +{ + Q_UNUSED(notification); + + if (self.view.window.screen != [UIScreen mainScreen]) + return; + + // Orientation changes will already result in laying out subviews, so we don't + // need to do anything extra for frame changes during an orientation change. + // Technically we can receive another actual statusbar frame update during the + // orientation change that we should react to, but to simplify the logic we + // use a simple bool variable instead of a ignoreNextFrameChange approach. + if (self.changingOrientation) + return; + + // UIKit doesn't have a delegate callback for statusbar changes that's run inside the + // animation block, like UIViewController's willAnimateRotationToInterfaceOrientation, + // nor does it expose a constant for the duration and easing of the animation. However, + // though poking at the various UIStatusBar methods, we can observe that the animation + // uses the default easing curve, and runs with a duration of 0.35 seconds. + static qreal kUIStatusBarAnimationDuration = 0.35; + + [UIView animateWithDuration:kUIStatusBarAnimationDuration animations:^{ + [self.view setNeedsLayout]; + [self.view layoutIfNeeded]; + }]; +} + +- (void)viewWillLayoutSubviews +{ if (!QCoreApplication::instance()) - return; // FIXME: Store orientation for later (?) + return; - QIOSScreen *qiosScreen = static_cast<QIOSScreen *>(QGuiApplication::primaryScreen()->handle()); - qiosScreen->updateProperties(); + m_screen->updateProperties(); } #if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_7_0) @@ -102,17 +311,5 @@ } #endif -- (BOOL)prefersStatusBarHidden -{ - static bool hiddenFromPlist = infoPlistValue(@"UIStatusBarHidden", false); - if (hiddenFromPlist) - return YES; - QWindow *focusWindow = QGuiApplication::focusWindow(); - if (!focusWindow) - return [UIApplication sharedApplication].statusBarHidden; - - return qt_window_private(focusWindow)->topLevelWindow()->windowState() == Qt::WindowFullScreen; -} - @end diff --git a/src/plugins/platforms/ios/qioswindow.h b/src/plugins/platforms/ios/qioswindow.h index 6b6892e6e4..5bbd76fb36 100644 --- a/src/plugins/platforms/ios/qioswindow.h +++ b/src/plugins/platforms/ios/qioswindow.h @@ -1,40 +1,32 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** 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$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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$ ** ****************************************************************************/ @@ -73,6 +65,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 +80,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..e29ff53876 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]; @@ -417,7 +119,6 @@ void QIOSWindow::setVisible(bool visible) if (visible) { requestActivateWindow(); - static_cast<QIOSScreen *>(screen())->updateStatusBarVisibility(); } else { // Activate top-most visible QWindow: NSArray *subviews = m_view.viewController.view.subviews; @@ -434,6 +135,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; @@ -471,24 +177,7 @@ void QIOSWindow::applyGeometry(const QRect &rect) // The baseclass takes care of persisting this for us. QPlatformWindow::setGeometry(rect); - if (window()->isTopLevel()) { - // The QWindow is in QScreen coordinates, which maps to a possibly rotated root-view-controller. - // Since the root-view-controller might be translated in relation to the UIWindow, we need to - // check specifically for that and compensate. Also check if the root view has been scrolled - // as a result of the keyboard being open. - UIWindow *uiWindow = m_view.window; - UIView *rootView = uiWindow.rootViewController.view; - CGRect rootViewPositionInRelationToRootViewController = - [rootView convertRect:uiWindow.bounds fromView:uiWindow]; - - m_view.frame = CGRectOffset([m_view.superview convertRect:toCGRect(rect) fromView:rootView], - rootViewPositionInRelationToRootViewController.origin.x, - rootViewPositionInRelationToRootViewController.origin.y - + rootView.bounds.origin.y); - } else { - // Easy, in parent's coordinates - m_view.frame = toCGRect(rect); - } + m_view.frame = toCGRect(rect); // iOS will automatically trigger -[layoutSubviews:] for resize, // but not for move, so we force it just in case. @@ -512,7 +201,7 @@ void QIOSWindow::setWindowState(Qt::WindowState state) qt_window_private(window())->windowState = state; if (window()->isTopLevel() && window()->isVisible() && window()->isActive()) - static_cast<QIOSScreen *>(screen())->updateStatusBarVisibility(); + [m_view.qtViewController updateProperties]; switch (state) { case Qt::WindowNoState: @@ -557,12 +246,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 +323,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..31ba5af870 --- /dev/null +++ b/src/plugins/platforms/ios/quiaccessibilityelement.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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:LGPL21$ +** 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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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. +** +** $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..fe71e5bed7 100644 --- a/src/plugins/platforms/ios/quiview.h +++ b/src/plugins/platforms/ios/quiview.h @@ -5,75 +5,64 @@ ** ** This file is part of the plugins of the Qt Toolkit. ** -** $QT_BEGIN_LICENSE:LGPL$ +** $QT_BEGIN_LICENSE:LGPL21$ ** 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 +** 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. +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** 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 +** 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 "qioswindow.h" + +#include <qhash.h> +#include <qstring.h> + +#include <qpa/qwindowsysteminterface.h> + +class QIOSWindow; + +@class QIOSViewController; @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 (Accessibility) +- (void)clearAccessibleCache; @end -@interface QUIView (TextInput) <UITextInput> -- (void)updateInputMethodWithQuery:(Qt::InputMethodQueries)query; -- (void)reset; -- (void)commit; -+ (bool)inUpdateKeyboardLayout; +@interface QUIView (QtHelpers) +- (QIOSViewController*)qtViewController; @end + diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm new file mode 100644 index 0000000000..200b07b5fc --- /dev/null +++ b/src/plugins/platforms/ios/quiview.mm @@ -0,0 +1,385 @@ +/**************************************************************************** +** +** 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 "qiosviewcontroller.h" +#include "qioswindow.h" +#include "qiosmenu.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 = 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 +{ + foreach (UITouch *uiTouch, m_activeTouches.keys()) { + QWindowSystemInterface::TouchPoint &touchPoint = m_activeTouches[uiTouch]; + if (![touches containsObject:uiTouch]) { + touchPoint.state = Qt::TouchPointStationary; + } else { + touchPoint.state = state; + + // Touch positions are expected to be in QScreen global coordinates, and + // as we already have the QWindow positioned at the right place, we can + // just map from the local view position to global coordinates. + QPoint localViewPosition = fromCGPoint([uiTouch locationInView:self]).toPoint(); + QPoint globalScreenPosition = m_qioswindow->mapToGlobal(localViewPosition); + + touchPoint.area = QRectF(globalScreenPosition, QSize(0, 0)); + + // FIXME: Do we really need to support QTouchDevice::NormalizedPosition? + QSize screenSize = m_qioswindow->screen()->geometry().size(); + touchPoint.normalPosition = QPointF(globalScreenPosition.x() / screenSize.width(), + globalScreenPosition.y() / screenSize.height()); + + // We don't claim that our touch device supports QTouchDevice::Pressure, + // but fill in a meaningfull value in case clients use it anyways. + touchPoint.pressure = (state == Qt::TouchPointReleased) ? 0.0 : 1.0; + } + } +} + +- (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(); +} + +- (id)targetForAction:(SEL)action withSender:(id)sender +{ + // Check first if QIOSMenu should handle the action before continuing up the responder chain + id target = [QIOSMenu::menuActionTarget() targetForAction:action withSender:sender]; + return target ? target : [super targetForAction:action withSender:sender]; +} + +@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; +} + +- (QIOSViewController*)qtViewController +{ + UIViewController *vc = self.viewController; + if ([vc isKindOfClass:QIOSViewController.class]) + return static_cast<QIOSViewController *>(vc); + + 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 |