diff options
Diffstat (limited to 'src/plugins/platforms/ios')
-rw-r--r-- | src/plugins/platforms/ios/ios.pro | 10 | ||||
-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.mm | 238 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosglobal.mm | 8 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosinputcontext.h | 13 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosinputcontext.mm | 181 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosintegration.h | 4 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosintegration.mm | 13 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosviewcontroller.mm | 8 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qioswindow.h | 2 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qioswindow.mm | 184 | ||||
-rw-r--r-- | src/plugins/platforms/ios/quiview.h | 78 | ||||
-rw-r--r-- | src/plugins/platforms/ios/quiview_textinput.mm | 557 |
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 |