summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/ios')
-rw-r--r--src/plugins/platforms/ios/ios.pro10
-rw-r--r--src/plugins/platforms/ios/qiosclipboard.h (renamed from src/plugins/platforms/ios/qiossoftwareinputhandler.h)33
-rw-r--r--src/plugins/platforms/ios/qiosclipboard.mm238
-rw-r--r--src/plugins/platforms/ios/qiosglobal.mm8
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.h13
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm181
-rw-r--r--src/plugins/platforms/ios/qiosintegration.h4
-rw-r--r--src/plugins/platforms/ios/qiosintegration.mm13
-rw-r--r--src/plugins/platforms/ios/qiosviewcontroller.mm8
-rw-r--r--src/plugins/platforms/ios/qioswindow.h2
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm184
-rw-r--r--src/plugins/platforms/ios/quiview.h78
-rw-r--r--src/plugins/platforms/ios/quiview_textinput.mm557
13 files changed, 1067 insertions, 262 deletions
diff --git a/src/plugins/platforms/ios/ios.pro b/src/plugins/platforms/ios/ios.pro
index 72716e6a4c..b7e074b95a 100644
--- a/src/plugins/platforms/ios/ios.pro
+++ b/src/plugins/platforms/ios/ios.pro
@@ -21,7 +21,8 @@ OBJECTIVE_SOURCES = \
qiosinputcontext.mm \
qiostheme.mm \
qiosglobal.mm \
- qiosservices.mm
+ qiosservices.mm \
+ qiosclipboard.mm
HEADERS = \
qiosintegration.h \
@@ -36,6 +37,9 @@ HEADERS = \
qiosinputcontext.h \
qiostheme.h \
qiosglobal.h \
- qiosservices.h
+ qiosservices.h \
+ quiview.h \
+ qiosclipboard.h
-#HEADERS = qiossoftwareinputhandler.h
+OTHER_FILES = \
+ quiview_textinput.mm
diff --git a/src/plugins/platforms/ios/qiossoftwareinputhandler.h b/src/plugins/platforms/ios/qiosclipboard.h
index 5dad6b8d86..da4226a40d 100644
--- a/src/plugins/platforms/ios/qiossoftwareinputhandler.h
+++ b/src/plugins/platforms/ios/qiosclipboard.h
@@ -39,33 +39,30 @@
**
****************************************************************************/
-#ifndef QIOSSOFTWAREINPUTHANDLER_H
-#define QIOSSOFTWAREINPUTHANDLER_H
+#ifndef QIOSCLIPBOARD_H
+#define QIOSCLIPBOARD_H
-#include <QtCore/QObject>
-#include <QtCore/QPointer>
-#include <QtWidgets/QWidget>
+#import <UIKit/UIKit.h>
+
+#include <qpa/qplatformclipboard.h>
+
+@class QUIClipboard;
QT_BEGIN_NAMESPACE
-class QIOSSoftwareInputHandler : public QObject
+class QIOSClipboard : public QPlatformClipboard
{
- Q_OBJECT
-
public:
- QIOSSoftwareInputHandler() : m_CurrentFocusWidget(0), m_CurrentFocusObject(0) {}
- bool eventFilter(QObject *obj, QEvent *event);
-
-private slots:
- void activeFocusChanged(bool focus);
+ QIOSClipboard();
+ QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) Q_DECL_OVERRIDE;
+ void setMimeData(QMimeData *mimeData, QClipboard::Mode mode = QClipboard::Clipboard) Q_DECL_OVERRIDE;
+ bool supportsMode(QClipboard::Mode mode) const Q_DECL_OVERRIDE;
+ bool ownsMode(QClipboard::Mode mode) const Q_DECL_OVERRIDE;
private:
- bool closeSoftwareInputPanel(QWidget *widget);
-
- QPointer<QWidget> m_currentFocusWidget;
- QPointer<QObject> m_currentFocusObject;
+ QUIClipboard *m_clipboard;
};
QT_END_NAMESPACE
-#endif
+#endif // QIOSCLIPBOARD_H
diff --git a/src/plugins/platforms/ios/qiosclipboard.mm b/src/plugins/platforms/ios/qiosclipboard.mm
new file mode 100644
index 0000000000..0a7b34a216
--- /dev/null
+++ b/src/plugins/platforms/ios/qiosclipboard.mm
@@ -0,0 +1,238 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qiosclipboard.h"
+
+#include <QtPlatformSupport/private/qmacmime_p.h>
+#include <QtCore/QMimeData>
+#include <QtGui/QGuiApplication>
+
+@interface UIPasteboard (QUIPasteboard)
+ + (UIPasteboard *)pasteboardWithQClipboardMode:(QClipboard::Mode)mode;
+@end
+
+@implementation UIPasteboard (QUIPasteboard)
++ (UIPasteboard *)pasteboardWithQClipboardMode:(QClipboard::Mode)mode
+{
+ NSString *name = (mode == QClipboard::Clipboard) ? UIPasteboardNameGeneral : UIPasteboardNameFind;
+ return [UIPasteboard pasteboardWithName:name create:NO];
+}
+@end
+
+// --------------------------------------------------------------------
+
+@interface QUIClipboard : NSObject
+{
+@public
+ QIOSClipboard *m_qiosClipboard;
+ NSInteger m_changeCountClipboard;
+ NSInteger m_changeCountFindBuffer;
+}
+@end
+
+@implementation QUIClipboard
+
+-(id)initWithQIOSClipboard:(QIOSClipboard *)qiosClipboard
+{
+ self = [super init];
+ if (self) {
+ m_qiosClipboard = qiosClipboard;
+ m_changeCountClipboard = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::Clipboard].changeCount;
+ m_changeCountFindBuffer = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::FindBuffer].changeCount;
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(updatePasteboardChanged:)
+ name:UIPasteboardChangedNotification object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(updatePasteboardChanged:)
+ name:UIPasteboardRemovedNotification object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(updatePasteboardChanged:)
+ name:UIApplicationDidBecomeActiveNotification
+ object:nil];
+ }
+ return self;
+}
+
+-(void)dealloc
+{
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:UIPasteboardChangedNotification object:nil];
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:UIPasteboardRemovedNotification object:nil];
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:UIApplicationDidBecomeActiveNotification
+ object:nil];
+ [super dealloc];
+}
+
+- (void)updatePasteboardChanged:(NSNotification *)notification
+{
+ Q_UNUSED(notification);
+ NSInteger changeCountClipboard = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::Clipboard].changeCount;
+ NSInteger changeCountFindBuffer = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::FindBuffer].changeCount;
+
+ if (m_changeCountClipboard != changeCountClipboard) {
+ m_changeCountClipboard = changeCountClipboard;
+ m_qiosClipboard->emitChanged(QClipboard::Clipboard);
+ }
+
+ if (m_changeCountFindBuffer != changeCountFindBuffer) {
+ m_changeCountFindBuffer = changeCountFindBuffer;
+ m_qiosClipboard->emitChanged(QClipboard::FindBuffer);
+ }
+}
+
+@end
+
+// --------------------------------------------------------------------
+
+QT_BEGIN_NAMESPACE
+
+class QIOSMimeData : public QMimeData {
+public:
+ QIOSMimeData(QClipboard::Mode mode) : QMimeData(), m_mode(mode) { }
+ ~QIOSMimeData() { }
+
+ QStringList formats() const Q_DECL_OVERRIDE;
+ QVariant retrieveData(const QString &mimeType, QVariant::Type type) const Q_DECL_OVERRIDE;
+
+private:
+ const QClipboard::Mode m_mode;
+};
+
+QStringList QIOSMimeData::formats() const
+{
+ QStringList foundMimeTypes;
+ UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:m_mode];
+ NSArray *pasteboardTypes = [pb pasteboardTypes];
+
+ for (NSUInteger i = 0; i < [pasteboardTypes count]; ++i) {
+ QString uti = QString::fromNSString([pasteboardTypes objectAtIndex:i]);
+ QString mimeType = QMacInternalPasteboardMime::flavorToMime(QMacInternalPasteboardMime::MIME_ALL, uti);
+ if (!mimeType.isEmpty() && !foundMimeTypes.contains(mimeType))
+ foundMimeTypes << mimeType;
+ }
+
+ return foundMimeTypes;
+}
+
+QVariant QIOSMimeData::retrieveData(const QString &mimeType, QVariant::Type) const
+{
+ UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:m_mode];
+ NSArray *pasteboardTypes = [pb pasteboardTypes];
+
+ foreach (QMacInternalPasteboardMime *converter,
+ QMacInternalPasteboardMime::all(QMacInternalPasteboardMime::MIME_ALL)) {
+ if (!converter->canConvert(mimeType, converter->flavorFor(mimeType)))
+ continue;
+
+ for (NSUInteger i = 0; i < [pasteboardTypes count]; ++i) {
+ NSString *availableUtiNSString = [pasteboardTypes objectAtIndex:i];
+ QString availableUti = QString::fromNSString(availableUtiNSString);
+ if (!converter->canConvert(mimeType, availableUti))
+ continue;
+
+ NSData *nsData = [pb dataForPasteboardType:availableUtiNSString];
+ QList<QByteArray> dataList;
+ dataList << QByteArray(reinterpret_cast<const char *>([nsData bytes]), [nsData length]);
+ return converter->convertToMime(mimeType, dataList, availableUti);
+ }
+ }
+
+ return QVariant();
+}
+
+// --------------------------------------------------------------------
+
+QIOSClipboard::QIOSClipboard()
+ : m_clipboard([[QUIClipboard alloc] initWithQIOSClipboard:this])
+{
+}
+
+QMimeData *QIOSClipboard::mimeData(QClipboard::Mode mode)
+{
+ Q_ASSERT(supportsMode(mode));
+ return new QIOSMimeData(mode);
+}
+
+void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
+{
+ Q_ASSERT(supportsMode(mode));
+
+ UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:mode];
+ NSMutableDictionary *pbItem = [NSMutableDictionary dictionaryWithCapacity:mimeData->formats().size()];
+
+ foreach (const QString &mimeType, mimeData->formats()) {
+ foreach (QMacInternalPasteboardMime *converter,
+ QMacInternalPasteboardMime::all(QMacInternalPasteboardMime::MIME_ALL)) {
+ QString uti = converter->flavorFor(mimeType);
+ if (uti.isEmpty() || !converter->canConvert(mimeType, uti))
+ continue;
+
+ QByteArray byteArray = converter->convertFromMime(mimeType, mimeData->data(mimeType), uti).first();
+ NSData *nsData = [NSData dataWithBytes:byteArray.constData() length:byteArray.size()];
+ [pbItem setValue:nsData forKey:uti.toNSString()];
+ break;
+ }
+ }
+
+ pb.items = [NSArray arrayWithObject:pbItem];
+}
+
+bool QIOSClipboard::supportsMode(QClipboard::Mode mode) const
+{
+ return (mode == QClipboard::Clipboard || mode == QClipboard::FindBuffer);
+}
+
+bool QIOSClipboard::ownsMode(QClipboard::Mode mode) const
+{
+ Q_UNUSED(mode);
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosglobal.mm b/src/plugins/platforms/ios/qiosglobal.mm
index 8dd690f301..2ce064582e 100644
--- a/src/plugins/platforms/ios/qiosglobal.mm
+++ b/src/plugins/platforms/ios/qiosglobal.mm
@@ -86,10 +86,10 @@ Qt::ScreenOrientation toQtScreenOrientation(UIDeviceOrientation uiDeviceOrientat
qtOrientation = Qt::InvertedPortraitOrientation;
break;
case UIDeviceOrientationLandscapeLeft:
- qtOrientation = Qt::InvertedLandscapeOrientation;
+ qtOrientation = Qt::LandscapeOrientation;
break;
case UIDeviceOrientationLandscapeRight:
- qtOrientation = Qt::LandscapeOrientation;
+ qtOrientation = Qt::InvertedLandscapeOrientation;
break;
case UIDeviceOrientationFaceUp:
case UIDeviceOrientationFaceDown:
@@ -108,10 +108,10 @@ UIDeviceOrientation fromQtScreenOrientation(Qt::ScreenOrientation qtOrientation)
UIDeviceOrientation uiOrientation;
switch (qtOrientation) {
case Qt::LandscapeOrientation:
- uiOrientation = UIDeviceOrientationLandscapeRight;
+ uiOrientation = UIDeviceOrientationLandscapeLeft;
break;
case Qt::InvertedLandscapeOrientation:
- uiOrientation = UIDeviceOrientationLandscapeLeft;
+ uiOrientation = UIDeviceOrientationLandscapeRight;
break;
case Qt::InvertedPortraitOrientation:
uiOrientation = UIDeviceOrientationPortraitUpsideDown;
diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h
index 533ba686e1..13255ada56 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.h
+++ b/src/plugins/platforms/ios/qiosinputcontext.h
@@ -50,6 +50,7 @@
QT_BEGIN_NAMESPACE
@class QIOSKeyboardListener;
+@class QUIView;
class QIOSInputContext : public QPlatformInputContext
{
@@ -64,14 +65,18 @@ public:
void setFocusObject(QObject *object);
void focusWindowChanged(QWindow *focusWindow);
- void scrollRootView();
+ void cursorRectangleChanged();
+ void scrollToCursor();
+ void scroll(int y);
+
+ void update(Qt::InputMethodQueries);
+ void reset();
+ void commit();
private:
QIOSKeyboardListener *m_keyboardListener;
- UIView<UIKeyInput> *m_focusView;
- QTransform m_inputItemTransform;
+ QUIView *m_focusView;
bool m_hasPendingHideRequest;
- bool m_inSetFocusObject;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index 39a22f367e..fff7de8170 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.mm
+++ b/src/plugins/platforms/ios/qiosinputcontext.mm
@@ -42,6 +42,7 @@
#include "qiosglobal.h"
#include "qiosinputcontext.h"
#include "qioswindow.h"
+#include "quiview.h"
#include <QGuiApplication>
@interface QIOSKeyboardListener : NSObject {
@@ -49,6 +50,7 @@
QIOSInputContext *m_context;
BOOL m_keyboardVisible;
BOOL m_keyboardVisibleAndDocked;
+ BOOL m_ignoreKeyboardChanges;
QRectF m_keyboardRect;
QRectF m_keyboardEndRect;
NSTimeInterval m_duration;
@@ -66,6 +68,7 @@
m_context = context;
m_keyboardVisible = NO;
m_keyboardVisibleAndDocked = NO;
+ m_ignoreKeyboardChanges = NO;
m_duration = 0;
m_curve = UIViewAnimationCurveEaseOut;
m_viewController = 0;
@@ -117,7 +120,7 @@
// 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][UIKeyboardFrameEndUserInfoKey] CGRectValue];
+ 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));
@@ -128,39 +131,56 @@
- (void) keyboardDidChangeFrame:(NSNotification *)notification
{
- m_keyboardRect = [self getKeyboardRect:notification];
- m_context->emitKeyboardRectChanged();
+ Q_UNUSED(notification);
+ if (m_ignoreKeyboardChanges)
+ return;
- BOOL visible = m_keyboardRect.intersects(fromCGRect([UIScreen mainScreen].bounds));
- if (m_keyboardVisible != visible) {
- m_keyboardVisible = visible;
- m_context->emitInputPanelVisibleChanged();
- }
+ [self handleKeyboardRectChanged];
// If the keyboard was visible and docked from before, this is just a geometry
// change (normally caused by an orientation change). In that case, update scroll:
if (m_keyboardVisibleAndDocked)
- m_context->scrollRootView();
+ m_context->scrollToCursor();
}
- (void) keyboardWillShow:(NSNotification *)notification
{
+ if (m_ignoreKeyboardChanges)
+ return;
// Note that UIKeyboardWillShowNotification is only sendt when the keyboard is docked.
m_keyboardVisibleAndDocked = YES;
m_keyboardEndRect = [self getKeyboardRect:notification];
if (!m_duration) {
- m_duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
- m_curve = UIViewAnimationCurve([notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16);
+ m_duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
+ m_curve = UIViewAnimationCurve([[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16);
}
- m_context->scrollRootView();
+ m_context->scrollToCursor();
}
- (void) keyboardWillHide:(NSNotification *)notification
{
+ if (m_ignoreKeyboardChanges)
+ return;
// Note that UIKeyboardWillHideNotification is also sendt when the keyboard is undocked.
m_keyboardVisibleAndDocked = NO;
m_keyboardEndRect = [self getKeyboardRect:notification];
- m_context->scrollRootView();
+ m_context->scroll(0);
+}
+
+- (void) handleKeyboardRectChanged
+{
+ QRectF rect = m_keyboardEndRect;
+ rect.moveTop(rect.y() + m_viewController.view.bounds.origin.y);
+ if (m_keyboardRect != rect) {
+ m_keyboardRect = rect;
+ m_context->emitKeyboardRectChanged();
+ }
+
+ BOOL visible = m_keyboardEndRect.intersects(fromCGRect([UIScreen mainScreen].bounds));
+ if (m_keyboardVisible != visible) {
+ m_keyboardVisible = visible;
+ m_context->emitInputPanelVisibleChanged();
+ }
}
@end
@@ -170,10 +190,9 @@ QIOSInputContext::QIOSInputContext()
, m_keyboardListener([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this])
, m_focusView(0)
, m_hasPendingHideRequest(false)
- , m_inSetFocusObject(false)
{
if (isQtApplication())
- connect(qGuiApp->inputMethod(), &QInputMethod::cursorRectangleChanged, this, &QIOSInputContext::scrollRootView);
+ connect(qGuiApp->inputMethod(), &QInputMethod::cursorRectangleChanged, this, &QIOSInputContext::cursorRectangleChanged);
connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &QIOSInputContext::focusWindowChanged);
}
@@ -216,76 +235,104 @@ bool QIOSInputContext::isInputPanelVisible() const
return m_keyboardListener->m_keyboardVisible;
}
-void QIOSInputContext::setFocusObject(QObject *)
+void QIOSInputContext::setFocusObject(QObject *focusObject)
{
- m_inputItemTransform = qApp->inputMethod()->inputItemTransform();
-
- if (!m_focusView || !m_focusView.isFirstResponder)
+ if (!focusObject || !m_focusView || !m_focusView.isFirstResponder) {
+ scroll(0);
return;
+ }
- // Since m_focusView is the first responder, it means that the keyboard is open and we
- // should update keyboard layout. But there seem to be no way to tell it to reread the
- // UITextInputTraits from m_focusView. To work around that, we quickly resign first
- // responder status just to reassign it again. To not remove the focusObject in the same
- // go, we need to call the super implementation of resignFirstResponder. Since the call
- // will cause a 'keyboardWillHide' notification to be sendt, we also block scrollRootView
- // to avoid artifacts:
- m_inSetFocusObject = true;
- SEL sel = @selector(resignFirstResponder);
- [[m_focusView superclass] instanceMethodForSelector:sel](m_focusView, sel);
- m_inSetFocusObject = false;
- [m_focusView becomeFirstResponder];
+ reset();
+
+ if (m_keyboardListener->m_keyboardVisibleAndDocked)
+ scrollToCursor();
}
void QIOSInputContext::focusWindowChanged(QWindow *focusWindow)
{
- UIView<UIKeyInput> *view = reinterpret_cast<UIView<UIKeyInput> *>(focusWindow->handle()->winId());
+ 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);
+}
+
+void QIOSInputContext::cursorRectangleChanged()
+{
+ if (!m_keyboardListener->m_keyboardVisibleAndDocked)
+ return;
+
+ // Check if the cursor has changed position inside the input item. Since
+ // qApp->inputMethod()->cursorRectangle() will also change when the input item
+ // itself moves, we need to ask the focus object for ImCursorRectangle:
+ static QPoint prevCursor;
+ QInputMethodQueryEvent queryEvent(Qt::ImCursorRectangle);
+ QCoreApplication::sendEvent(qApp->focusObject(), &queryEvent);
+ QPoint cursor = queryEvent.value(Qt::ImCursorRectangle).toRect().topLeft();
+ if (cursor != prevCursor)
+ scrollToCursor();
+ prevCursor = cursor;
}
-void QIOSInputContext::scrollRootView()
+void QIOSInputContext::scrollToCursor()
{
- // Scroll the root view (screen) if:
- // - our backend controls the root view controller on the main screen (no hybrid app)
- // - the focus object is on the same screen as the keyboard.
- // - the first responder is a QUIView, and not some other foreign UIView.
- // - the keyboard is docked. Otherwise the user can move the keyboard instead.
- // - the inputItem has not been moved/scrolled
- if (!isQtApplication() || !m_focusView || m_inSetFocusObject)
+ if (!isQtApplication() || !m_focusView)
return;
- if (m_inputItemTransform != qApp->inputMethod()->inputItemTransform()) {
- // The inputItem has moved since the last scroll update. To avoid competing
- // with the application where the cursor/inputItem should be, we bail:
+ UIView *view = m_keyboardListener->m_viewController.view;
+ if (view.window != m_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();
+ int statusBarY = qGuiApp->primaryScreen()->availableGeometry().y();
+
+ scroll((translatedCursorPos.bottomLeft().y() < keyboardY - margin) ? 0
+ : qMin(view.bounds.size.height - keyboardY, translatedCursorPos.y() - statusBarY - margin));
+}
+
+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;
- qreal scrollTo = 0;
-
- if (m_focusView.isFirstResponder
- && m_keyboardListener->m_keyboardVisibleAndDocked
- && m_focusView.window == view.window) {
- QRectF cursorRect = qGuiApp->inputMethod()->cursorRectangle();
- cursorRect.translate(m_focusView.qwindow->geometry().topLeft());
- qreal keyboardY = m_keyboardListener->m_keyboardEndRect.y();
- int statusBarY = qGuiApp->primaryScreen()->availableGeometry().y();
- const int margin = 20;
-
- if (cursorRect.bottomLeft().y() > keyboardY - margin)
- scrollTo = qMin(view.bounds.size.height - keyboardY, cursorRect.y() - statusBarY - margin);
- }
+ if (y == view.bounds.origin.y)
+ return;
- if (scrollTo != view.bounds.origin.y) {
- // Scroll the view the same way a UIScrollView works: by changing bounds.origin:
- CGRect newBounds = view.bounds;
- newBounds.origin.y = scrollTo;
- [UIView animateWithDuration:m_keyboardListener->m_duration delay:0
- options:m_keyboardListener->m_curve
- animations:^{ view.bounds = newBounds; }
- completion:0];
- }
+ 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
+ animations:^{ view.bounds = newBounds; }
+ completion:^(BOOL){
+ if (self)
+ [m_keyboardListener handleKeyboardRectChanged];
+ }
+ ];
+}
+
+void QIOSInputContext::update(Qt::InputMethodQueries query)
+{
+ [m_focusView updateInputMethodWithQuery:query];
}
+
+void QIOSInputContext::reset()
+{
+ // Since the call to reset will cause a 'keyboardWillHide'
+ // notification to be sendt, we block keyboard nofifications to avoid artifacts:
+ m_keyboardListener->m_ignoreKeyboardChanges = true;
+ [m_focusView reset];
+ m_keyboardListener->m_ignoreKeyboardChanges = false;
+}
+
+void QIOSInputContext::commit()
+{
+ [m_focusView commit];
+}
+
diff --git a/src/plugins/platforms/ios/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h
index fdecf70725..a28926ff99 100644
--- a/src/plugins/platforms/ios/qiosintegration.h
+++ b/src/plugins/platforms/ios/qiosintegration.h
@@ -66,6 +66,7 @@ public:
QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const;
QPlatformFontDatabase *fontDatabase() const;
+ QPlatformClipboard *clipboard() const;
QPlatformInputContext *inputContext() const;
QPlatformServices *services() const Q_DECL_OVERRIDE;
@@ -74,8 +75,6 @@ public:
QStringList themeNames() const;
QPlatformTheme *createPlatformTheme(const QString &name) const;
- QPlatformDrag *drag() const Q_DECL_OVERRIDE { return 0; }
-
QAbstractEventDispatcher *createEventDispatcher() const;
QPlatformNativeInterface *nativeInterface() const;
@@ -84,6 +83,7 @@ public:
QTouchDevice *touchDevice();
private:
QPlatformFontDatabase *m_fontDatabase;
+ QPlatformClipboard *m_clipboard;
QPlatformInputContext *m_inputContext;
QPlatformScreen *m_screen;
QTouchDevice *m_touchDevice;
diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm
index 660da6397f..7a40e349c9 100644
--- a/src/plugins/platforms/ios/qiosintegration.mm
+++ b/src/plugins/platforms/ios/qiosintegration.mm
@@ -46,11 +46,13 @@
#include "qiosbackingstore.h"
#include "qiosscreen.h"
#include "qioscontext.h"
+#include "qiosclipboard.h"
#include "qiosinputcontext.h"
#include "qiostheme.h"
#include "qiosservices.h"
#include <QtPlatformSupport/private/qcoretextfontdatabase_p.h>
+#include <QtPlatformSupport/private/qmacmime_p.h>
#include <QDir>
#include <QtDebug>
@@ -59,6 +61,7 @@ QT_BEGIN_NAMESPACE
QIOSIntegration::QIOSIntegration()
: m_fontDatabase(new QCoreTextFontDatabase)
+ , m_clipboard(new QIOSClipboard)
, m_inputContext(new QIOSInputContext)
, m_screen(new QIOSScreen(QIOSScreen::MainScreen))
, m_platformServices(new QIOSServices)
@@ -81,6 +84,7 @@ QIOSIntegration::QIOSIntegration()
m_touchDevice->setType(QTouchDevice::TouchScreen);
m_touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::NormalizedPosition);
QWindowSystemInterface::registerTouchDevice(m_touchDevice);
+ QMacInternalPasteboardMime::initializeMimeTypes();
}
QIOSIntegration::~QIOSIntegration()
@@ -88,6 +92,10 @@ QIOSIntegration::~QIOSIntegration()
delete m_fontDatabase;
m_fontDatabase = 0;
+ delete m_clipboard;
+ m_clipboard = 0;
+ QMacInternalPasteboardMime::destroyMimeTypes();
+
delete m_inputContext;
m_inputContext = 0;
@@ -149,6 +157,11 @@ QPlatformFontDatabase * QIOSIntegration::fontDatabase() const
return m_fontDatabase;
}
+QPlatformClipboard *QIOSIntegration::clipboard() const
+{
+ return m_clipboard;
+}
+
QPlatformInputContext *QIOSIntegration::inputContext() const
{
return m_inputContext;
diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm
index 0a6a00b753..2fe679fc20 100644
--- a/src/plugins/platforms/ios/qiosviewcontroller.mm
+++ b/src/plugins/platforms/ios/qiosviewcontroller.mm
@@ -44,6 +44,9 @@
#include <QtGui/QGuiApplication>
#include <QtGui/QWindow>
#include <QtGui/QScreen>
+
+#include <QtGui/private/qwindow_p.h>
+
#include "qiosscreen.h"
#include "qiosglobal.h"
#include "qioswindow.h"
@@ -105,11 +108,10 @@
if (hiddenFromPlist)
return YES;
QWindow *focusWindow = QGuiApplication::focusWindow();
- if (!focusWindow || !focusWindow->handle())
+ if (!focusWindow)
return [UIApplication sharedApplication].statusBarHidden;
- QWindow *topLevel = static_cast<QIOSWindow *>(focusWindow->handle())->topLevelWindow();
- return topLevel->windowState() == Qt::WindowFullScreen;
+ 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 8a5eb589d2..fc99543aa6 100644
--- a/src/plugins/platforms/ios/qioswindow.h
+++ b/src/plugins/platforms/ios/qioswindow.h
@@ -87,8 +87,6 @@ public:
WId winId() const { return WId(m_view); };
- QWindow *topLevelWindow() const;
-
private:
void applyGeometry(const QRect &rect);
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index 7a0ff055ec..7d5c507972 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -41,6 +41,7 @@
#include "qiosglobal.h"
#include "qioswindow.h"
+#include "quiview.h"
#include "qioscontext.h"
#include "qiosinputcontext.h"
#include "qiosscreen.h"
@@ -58,33 +59,19 @@
#include <QtDebug>
-@interface QUIView : UIView <UIKeyInput>
-{
-@public
- UITextAutocapitalizationType autocapitalizationType;
- UITextAutocorrectionType autocorrectionType;
- BOOL enablesReturnKeyAutomatically;
- UIKeyboardAppearance keyboardAppearance;
- UIKeyboardType keyboardType;
- UIReturnKeyType returnKeyType;
- BOOL secureTextEntry;
- QIOSWindow *m_qioswindow;
- QHash<UITouch *, QWindowSystemInterface::TouchPoint> m_activeTouches;
- int m_nextTouchId;
-}
-
-@property(nonatomic) UITextAutocapitalizationType autocapitalizationType;
-@property(nonatomic) UITextAutocorrectionType autocorrectionType;
-@property(nonatomic) BOOL enablesReturnKeyAutomatically;
-@property(nonatomic) UIKeyboardAppearance keyboardAppearance;
-@property(nonatomic) UIKeyboardType keyboardType;
-@property(nonatomic) UIReturnKeyType returnKeyType;
-@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry;
-
-@end
+// 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];
@@ -112,6 +99,7 @@
self.hidden = YES;
self.multipleTouchEnabled = YES;
+ m_inSendEventToFocusObject = NO;
}
return self;
@@ -259,6 +247,14 @@
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)];
}
@@ -271,14 +267,6 @@
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
- QWindow *window = m_qioswindow->window();
- if (window != QGuiApplication::focusWindow() && m_activeTouches.size() == 1) {
- // Activate the touched window if the last touch was released inside it:
- UITouch *touch = static_cast<UITouch *>([[touches allObjects] lastObject]);
- if (CGRectContainsPoint([self bounds], [touch locationInView:self]))
- m_qioswindow->requestActivateWindow();
- }
-
[self updateTouchList:touches withState:Qt::TouchPointReleased];
[self sendTouchEventWithTimestamp:ulong(event.timestamp * 1000)];
@@ -314,109 +302,6 @@
QWindowSystemInterface::flushWindowSystemEvents();
}
-@synthesize autocapitalizationType;
-@synthesize autocorrectionType;
-@synthesize enablesReturnKeyAutomatically;
-@synthesize keyboardAppearance;
-@synthesize keyboardType;
-@synthesize returnKeyType;
-@synthesize secureTextEntry;
-
-- (BOOL)canBecomeFirstResponder
-{
- return YES;
-}
-
-- (BOOL)becomeFirstResponder
-{
- // Note: QIOSInputContext controls our first responder status based on
- // whether or not the keyboard should be open or closed.
- [self updateTextInputTraits];
- return [super becomeFirstResponder];
-}
-
-- (BOOL)resignFirstResponder
-{
- // 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];
-}
-
-- (BOOL)hasText
-{
- return YES;
-}
-
-- (void)insertText:(NSString *)text
-{
- QString string = QString::fromUtf8([text UTF8String]);
- int key = 0;
- if ([text isEqualToString:@"\n"]) {
- key = (int)Qt::Key_Return;
- if (self.returnKeyType == UIReturnKeyDone)
- [self resignFirstResponder];
- }
-
- // Send key event to window system interface
- QWindowSystemInterface::handleKeyEvent(
- 0, QEvent::KeyPress, key, Qt::NoModifier, string, false, int(string.length()));
- QWindowSystemInterface::handleKeyEvent(
- 0, QEvent::KeyRelease, key, Qt::NoModifier, string, false, int(string.length()));
-}
-
-- (void)deleteBackward
-{
- // Send key event to window system interface
- QWindowSystemInterface::handleKeyEvent(
- 0, QEvent::KeyPress, (int)Qt::Key_Backspace, Qt::NoModifier);
- QWindowSystemInterface::handleKeyEvent(
- 0, QEvent::KeyRelease, (int)Qt::Key_Backspace, Qt::NoModifier);
-}
-
-- (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;
-
- 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
@implementation UIView (QIOS)
@@ -487,7 +372,7 @@ void QIOSWindow::setVisible(bool visible)
m_view.hidden = !visible;
[m_view setNeedsDisplay];
- if (!isQtApplication())
+ if (!isQtApplication() || !window()->isTopLevel())
return;
// Since iOS doesn't do window management the way a Qt application
@@ -503,18 +388,16 @@ void QIOSWindow::setVisible(bool visible)
if (visible) {
requestActivateWindow();
-
- if (window()->isTopLevel())
- static_cast<QIOSScreen *>(screen())->updateStatusBarVisibility();
-
+ static_cast<QIOSScreen *>(screen())->updateStatusBarVisibility();
} else {
// Activate top-most visible QWindow:
NSArray *subviews = m_view.viewController.view.subviews;
for (int i = int(subviews.count) - 1; i >= 0; --i) {
UIView *view = [subviews objectAtIndex:i];
if (!view.hidden) {
- if (QWindow *window = view.qwindow) {
- static_cast<QIOSWindow *>(window->handle())->requestActivateWindow();
+ QWindow *w = view.qwindow;
+ if (w && w->isTopLevel()) {
+ static_cast<QIOSWindow *>(w->handle())->requestActivateWindow();
break;
}
}
@@ -636,23 +519,6 @@ void QIOSWindow::setParent(const QPlatformWindow *parentWindow)
}
}
-QWindow *QIOSWindow::topLevelWindow() const
-{
- QWindow *window = this->window();
- while (window) {
- QWindow *parent = window->parent();
- if (!parent)
- parent = window->transientParent();
-
- if (!parent)
- break;
-
- window = parent;
- }
-
- return window;
-}
-
void QIOSWindow::requestActivateWindow()
{
// Note that several windows can be active at the same time if they exist in the same
diff --git a/src/plugins/platforms/ios/quiview.h b/src/plugins/platforms/ios/quiview.h
new file mode 100644
index 0000000000..575dedab89
--- /dev/null
+++ b/src/plugins/platforms/ios/quiview.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** 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 "qioswindow.h"
+
+@interface QUIView : UIView
+{
+@public
+ UITextAutocapitalizationType autocapitalizationType;
+ UITextAutocorrectionType autocorrectionType;
+ BOOL enablesReturnKeyAutomatically;
+ UIKeyboardAppearance keyboardAppearance;
+ UIKeyboardType keyboardType;
+ UIReturnKeyType returnKeyType;
+ BOOL secureTextEntry;
+ QIOSWindow *m_qioswindow;
+ 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;
+
+@end
+
+@interface QUIView (TextInput) <UITextInput>
+- (void)updateInputMethodWithQuery:(Qt::InputMethodQueries)query;
+- (void)reset;
+- (void)commit;
+@end
diff --git a/src/plugins/platforms/ios/quiview_textinput.mm b/src/plugins/platforms/ios/quiview_textinput.mm
new file mode 100644
index 0000000000..d0088d415a
--- /dev/null
+++ b/src/plugins/platforms/ios/quiview_textinput.mm
@@ -0,0 +1,557 @@
+/****************************************************************************
+**
+** 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 <QtGui/qtextformat.h>
+
+class StaticVariables
+{
+public:
+ QInputMethodQueryEvent inputMethodQueryEvent;
+ QTextCharFormat markedTextFormat;
+
+ StaticVariables() : inputMethodQueryEvent(Qt::ImQueryInput)
+ {
+ // 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));
+ }
+};
+
+Q_GLOBAL_STATIC(StaticVariables, staticVariables);
+
+// -------------------------------------------------------------------------
+
+@interface QUITextPosition : UITextPosition
+{
+}
+
+@property (nonatomic) NSUInteger index;
++ (QUITextPosition *)positionWithIndex:(NSUInteger)index;
+
+@end
+
+@implementation QUITextPosition
+
++ (QUITextPosition *)positionWithIndex:(NSUInteger)index
+{
+ QUITextPosition *pos = [[QUITextPosition alloc] init];
+ pos.index = index;
+ return [pos autorelease];
+}
+
+@end
+
+// -------------------------------------------------------------------------
+
+@interface QUITextRange : UITextRange
+{
+}
+
+@property (nonatomic) NSRange range;
++ (QUITextRange *)rangeWithNSRange:(NSRange)range;
+
+@end
+
+@implementation QUITextRange
+
++ (QUITextRange *)rangeWithNSRange:(NSRange)nsrange
+{
+ QUITextRange *range = [[QUITextRange alloc] init];
+ range.range = nsrange;
+ return [range autorelease];
+}
+
+- (UITextPosition *)start
+{
+ return [QUITextPosition positionWithIndex:self.range.location];
+}
+
+- (UITextPosition *)end
+{
+ return [QUITextPosition positionWithIndex:(self.range.location + self.range.length)];
+}
+
+- (NSRange) range
+{
+ return _range;
+}
+
+-(BOOL)isEmpty
+{
+ return (self.range.length == 0);
+}
+
+@end
+
+// -------------------------------------------------------------------------
+
+@implementation QUIView (TextInput)
+
+- (BOOL)canBecomeFirstResponder
+{
+ return YES;
+}
+
+- (BOOL)becomeFirstResponder
+{
+ // Note: QIOSInputContext controls our first responder status based on
+ // whether or not the keyboard should be open or closed.
+ [self updateTextInputTraits];
+ return [super becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder
+{
+ // 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];
+}
+
+- (void)updateInputMethodWithQuery:(Qt::InputMethodQueries)query
+{
+ Q_UNUSED(query);
+
+ QObject *focusObject = QGuiApplication::focusObject();
+ if (!focusObject)
+ return;
+
+ if (!m_inSendEventToFocusObject) {
+ if (query & (Qt::ImCursorPosition | Qt::ImAnchorPosition))
+ [self.inputDelegate selectionWillChange:id<UITextInput>(self)];
+ if (query & Qt::ImSurroundingText)
+ [self.inputDelegate textWillChange:id<UITextInput>(self)];
+ }
+
+ // 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);
+
+ if (!m_inSendEventToFocusObject) {
+ if (query & (Qt::ImCursorPosition | Qt::ImAnchorPosition))
+ [self.inputDelegate selectionDidChange:id<UITextInput>(self)];
+ if (query & Qt::ImSurroundingText)
+ [self.inputDelegate textDidChange:id<UITextInput>(self)];
+ }
+}
+
+- (void)sendEventToFocusObject:(QEvent &)e
+{
+ QObject *focusObject = QGuiApplication::focusObject();
+ if (!focusObject)
+ return;
+
+ // While sending the event, we will receive back updateInputMethodWithQuery calls.
+ // To not confuse iOS, we cannot not call textWillChange/textDidChange at that
+ // point since it will cause spell checking etc to fail. So we use a guard.
+ m_inSendEventToFocusObject = YES;
+ QCoreApplication::sendEvent(focusObject, &e);
+ m_inSendEventToFocusObject = NO;
+}
+
+- (void)reset
+{
+ [self.inputDelegate textWillChange:id<UITextInput>(self)];
+ [self setMarkedText:@"" selectedRange:NSMakeRange(0, 0)];
+ [self updateInputMethodWithQuery:Qt::ImQueryInput];
+
+ if ([self isFirstResponder]) {
+ // There seem to be no way to inform that the keyboard needs to update (since
+ // text input traits might have changed). As a work-around, we quickly resign
+ // first responder status just to reassign it again:
+ [super resignFirstResponder];
+ [self updateTextInputTraits];
+ [super becomeFirstResponder];
+ }
+ [self.inputDelegate textDidChange:id<UITextInput>(self)];
+}
+
+- (void)commit
+{
+ [self.inputDelegate textWillChange:id<UITextInput>(self)];
+ [self unmarkText];
+ [self.inputDelegate textDidChange:id<UITextInput>(self)];
+}
+
+- (QVariant)imValue:(Qt::InputMethodQuery)query
+{
+ return staticVariables()->inputMethodQueryEvent.value(query);
+}
+
+-(id<UITextInputTokenizer>)tokenizer
+{
+ return [[[UITextInputStringTokenizer alloc] initWithTextInput:id<UITextInput>(self)] autorelease];
+}
+
+-(UITextPosition *)beginningOfDocument
+{
+ return [QUITextPosition positionWithIndex:0];
+}
+
+-(UITextPosition *)endOfDocument
+{
+ int endPosition = [self imValue:Qt::ImSurroundingText].toString().length();
+ return [QUITextPosition positionWithIndex:endPosition];
+}
+
+- (void)setSelectedTextRange:(UITextRange *)range
+{
+ QUITextRange *r = static_cast<QUITextRange *>(range);
+ QList<QInputMethodEvent::Attribute> attrs;
+ attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, r.range.location, r.range.length, 0);
+ QInputMethodEvent e(m_markedText, attrs);
+ [self sendEventToFocusObject:e];
+}
+
+- (UITextRange *)selectedTextRange {
+ int cursorPos = [self imValue:Qt::ImCursorPosition].toInt();
+ int anchorPos = [self imValue:Qt::ImAnchorPosition].toInt();
+ return [QUITextRange rangeWithNSRange:NSMakeRange(cursorPos, (anchorPos - cursorPos))];
+}
+
+- (NSString *)textInRange:(UITextRange *)range
+{
+ int s = static_cast<QUITextPosition *>([range start]).index;
+ int e = static_cast<QUITextPosition *>([range end]).index;
+ return [self imValue:Qt::ImSurroundingText].toString().mid(s, e - s).toNSString();
+}
+
+- (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange
+{
+ Q_UNUSED(selectedRange);
+
+ m_markedText = markedText ? QString::fromNSString(markedText) : QString();
+
+ QList<QInputMethodEvent::Attribute> attrs;
+ attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, markedText.length, staticVariables()->markedTextFormat);
+ QInputMethodEvent e(m_markedText, attrs);
+ [self sendEventToFocusObject:e];
+}
+
+- (void)unmarkText
+{
+ if (m_markedText.isEmpty())
+ return;
+
+ QInputMethodEvent e;
+ e.setCommitString(m_markedText);
+ [self sendEventToFocusObject:e];
+
+ m_markedText.clear();
+}
+
+- (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other
+{
+ int p = static_cast<QUITextPosition *>(position).index;
+ int o = static_cast<QUITextPosition *>(other).index;
+ if (p > o)
+ return NSOrderedAscending;
+ else if (p < o)
+ return NSOrderedDescending;
+ return NSOrderedSame;
+}
+
+- (UITextRange *)markedTextRange {
+ return m_markedText.isEmpty() ? nil : [QUITextRange rangeWithNSRange:NSMakeRange(0, m_markedText.length())];
+}
+
+- (UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
+{
+ int f = static_cast<QUITextPosition *>(fromPosition).index;
+ int t = static_cast<QUITextPosition *>(toPosition).index;
+ return [QUITextRange rangeWithNSRange:NSMakeRange(f, t - f)];
+}
+
+- (UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset
+{
+ int p = static_cast<QUITextPosition *>(position).index;
+ return [QUITextPosition positionWithIndex:p + offset];
+}
+
+- (UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset
+{
+ int p = static_cast<QUITextPosition *>(position).index;
+ return [QUITextPosition positionWithIndex:(direction == UITextLayoutDirectionRight ? p + offset : p - offset)];
+}
+
+- (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction
+{
+ NSRange r = static_cast<QUITextRange *>(range).range;
+ if (direction == UITextLayoutDirectionRight)
+ return [QUITextPosition positionWithIndex:r.location + r.length];
+ return [QUITextPosition positionWithIndex:r.location];
+}
+
+- (NSInteger)offsetFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
+{
+ int f = static_cast<QUITextPosition *>(fromPosition).index;
+ int t = static_cast<QUITextPosition *>(toPosition).index;
+ return t - f;
+}
+
+- (UIView *)textInputView
+{
+ // iOS expects rects we return from other UITextInput methods
+ // 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;
+ while (QPlatformWindow *p = topLevel->parent())
+ topLevel = p;
+ return reinterpret_cast<UIView *>(topLevel->winId());
+}
+
+- (CGRect)firstRectForRange:(UITextRange *)range
+{
+ QObject *focusObject = QGuiApplication::focusObject();
+ if (!focusObject)
+ return CGRectZero;
+
+ // Using a work-around to get the current rect until
+ // a better API is in place:
+ if (!m_markedText.isEmpty())
+ return CGRectZero;
+
+ int cursorPos = [self imValue:Qt::ImCursorPosition].toInt();
+ int anchorPos = [self imValue:Qt::ImAnchorPosition].toInt();
+
+ NSRange r = static_cast<QUITextRange*>(range).range;
+ QList<QInputMethodEvent::Attribute> attrs;
+ attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, r.location, 0, 0);
+ QInputMethodEvent e(m_markedText, attrs);
+ [self sendEventToFocusObject:e];
+ QRectF startRect = qApp->inputMethod()->cursorRectangle();
+
+ attrs = QList<QInputMethodEvent::Attribute>();
+ attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, r.location + r.length, 0, 0);
+ e = QInputMethodEvent(m_markedText, attrs);
+ [self sendEventToFocusObject:e];
+ QRectF endRect = qApp->inputMethod()->cursorRectangle();
+
+ if (cursorPos != int(r.location + r.length) || cursorPos != anchorPos) {
+ attrs = QList<QInputMethodEvent::Attribute>();
+ attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, cursorPos, (cursorPos - anchorPos), 0);
+ e = QInputMethodEvent(m_markedText, attrs);
+ [self sendEventToFocusObject:e];
+ }
+
+ return toCGRect(startRect.united(endRect));
+}
+
+- (CGRect)caretRectForPosition:(UITextPosition *)position
+{
+ Q_UNUSED(position);
+ // Assume for now that position is always the same as
+ // cursor index until a better API is in place:
+ QRectF cursorRect = qApp->inputMethod()->cursorRectangle();
+ return toCGRect(cursorRect);
+}
+
+- (void)replaceRange:(UITextRange *)range withText:(NSString *)text
+{
+ [self setSelectedTextRange:range];
+
+ QInputMethodEvent e;
+ e.setCommitString(QString::fromNSString(text));
+ [self sendEventToFocusObject:e];
+}
+
+- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range
+{
+ Q_UNUSED(writingDirection);
+ Q_UNUSED(range);
+ // Writing direction is handled by QLocale
+}
+
+- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
+{
+ Q_UNUSED(position);
+ Q_UNUSED(direction);
+ if (QLocale::system().textDirection() == Qt::RightToLeft)
+ return UITextWritingDirectionRightToLeft;
+ return UITextWritingDirectionLeftToRight;
+}
+
+- (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction
+{
+ int p = static_cast<QUITextPosition *>(position).index;
+ if (direction == UITextLayoutDirectionLeft)
+ return [QUITextRange rangeWithNSRange:NSMakeRange(0, p)];
+ int l = [self imValue:Qt::ImSurroundingText].toString().length();
+ return [QUITextRange rangeWithNSRange:NSMakeRange(p, l - p)];
+}
+
+- (UITextPosition *)closestPositionToPoint:(CGPoint)point
+{
+ // No API in Qt for determining this. Use sensible default instead:
+ Q_UNUSED(point);
+ return [QUITextPosition positionWithIndex:[self imValue:Qt::ImCursorPosition].toInt()];
+}
+
+- (UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range
+{
+ // No API in Qt for determining this. Use sensible default instead:
+ Q_UNUSED(point);
+ Q_UNUSED(range);
+ return [QUITextPosition positionWithIndex:[self imValue:Qt::ImCursorPosition].toInt()];
+}
+
+- (UITextRange *)characterRangeAtPoint:(CGPoint)point
+{
+ // No API in Qt for determining this. Use sensible default instead:
+ Q_UNUSED(point);
+ return [QUITextRange rangeWithNSRange:NSMakeRange([self imValue:Qt::ImCursorPosition].toInt(), 0)];
+}
+
+- (void)setMarkedTextStyle:(NSDictionary *)style
+{
+ Q_UNUSED(style);
+ // No-one is going to change our style. If UIKit itself did that
+ // it would be very welcome, since then we knew how to style marked
+ // text instead of just guessing...
+}
+
+- (NSDictionary *)textStylingAtPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
+{
+ Q_UNUSED(position);
+ Q_UNUSED(direction);
+
+ QObject *focusObject = QGuiApplication::focusObject();
+ if (!focusObject)
+ return [NSDictionary dictionary];
+
+ // Assume position is the same as the cursor for now. QInputMethodQueryEvent with Qt::ImFont
+ // needs to be extended to take an extra position argument before this can be fully correct.
+ QInputMethodQueryEvent e(Qt::ImFont);
+ QCoreApplication::sendEvent(focusObject, &e);
+ QFont qfont = qvariant_cast<QFont>(e.value(Qt::ImFont));
+ UIFont *uifont = [UIFont fontWithName:qfont.family().toNSString() size:qfont.pointSize()];
+ return [NSDictionary dictionaryWithObject:uifont forKey:UITextInputTextFontKey];
+}
+
+-(NSDictionary *)markedTextStyle
+{
+ return [NSDictionary dictionary];
+}
+
+- (BOOL)hasText
+{
+ return YES;
+}
+
+- (void)insertText:(NSString *)text
+{
+ QObject *focusObject = QGuiApplication::focusObject();
+ if (!focusObject)
+ return;
+
+ if ([text isEqualToString:@"\n"] && self.returnKeyType == UIReturnKeyDone)
+ [self resignFirstResponder];
+
+ QInputMethodEvent e;
+ e.setCommitString(QString::fromNSString(text));
+ [self sendEventToFocusObject:e];
+}
+
+- (void)deleteBackward
+{
+ // Since we're posting im events directly to the focus object, we should do the
+ // same for key events. Otherwise they might end up in a different place or out
+ // of sync with im events.
+ QKeyEvent press(QEvent::KeyPress, (int)Qt::Key_Backspace, Qt::NoModifier);
+ QKeyEvent release(QEvent::KeyRelease, (int)Qt::Key_Backspace, Qt::NoModifier);
+ [self sendEventToFocusObject:press];
+ [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