summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@digia.com>2014-01-15 10:40:13 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2014-02-06 14:19:44 +0100
commit7eb8b67c8bc8e69b3dd6fb39dde09ac36f56e538 (patch)
tree1bdfb761e9fddd142e1d3e9ec3c4b6c90e6e0e78 /src
parente60357fa9efcc20f48a9ef93230fadf4c58d6a39 (diff)
iOS: implement support for input methods
This change will add support for input methods, word completion, spell checking and related functionality. Change-Id: I41d4de1cab521c679d414cfc7c1a2d0f9c1fcaaf Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@digia.com>
Diffstat (limited to 'src')
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.h8
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm38
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm1
-rw-r--r--src/plugins/platforms/ios/quiview.h8
-rw-r--r--src/plugins/platforms/ios/quiview_textinput.mm382
5 files changed, 420 insertions, 17 deletions
diff --git a/src/plugins/platforms/ios/qiosinputcontext.h b/src/plugins/platforms/ios/qiosinputcontext.h
index 91a6939ad4..aec1686b93 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
{
@@ -67,9 +68,14 @@ public:
void cursorRectangleChanged();
void scrollToCursor();
void scroll(int y);
+
+ void update(Qt::InputMethodQueries);
+ void reset();
+ void commit();
+
private:
QIOSKeyboardListener *m_keyboardListener;
- UIView<UIKeyInput> *m_focusView;
+ QUIView *m_focusView;
bool m_hasPendingHideRequest;
QObject *m_focusObject;
};
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index c25d76518f..15f5082f62 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 {
@@ -228,23 +229,12 @@ void QIOSInputContext::setFocusObject(QObject *focusObject)
{
m_focusObject = focusObject;
- 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_keyboardListener->m_ignoreKeyboardChanges = true;
- SEL sel = @selector(resignFirstResponder);
- [[m_focusView superclass] instanceMethodForSelector:sel](m_focusView, sel);
- [m_focusView becomeFirstResponder];
- m_keyboardListener->m_ignoreKeyboardChanges = false;
+ reset();
if (m_keyboardListener->m_keyboardVisibleAndDocked)
scrollToCursor();
@@ -252,8 +242,7 @@ void QIOSInputContext::setFocusObject(QObject *focusObject)
void QIOSInputContext::focusWindowChanged(QWindow *focusWindow)
{
- UIView<UIKeyInput> *view = focusWindow ?
- reinterpret_cast<UIView<UIKeyInput> *>(focusWindow->handle()->winId()) : 0;
+ QUIView *view = focusWindow ? reinterpret_cast<QUIView *>(focusWindow->handle()->winId()) : 0;
if ([m_focusView isFirstResponder])
[view becomeFirstResponder];
[m_focusView release];
@@ -315,3 +304,22 @@ void QIOSInputContext::scroll(int y)
completion:0];
}
+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/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index c25d6ba303..41a253e605 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -99,6 +99,7 @@
self.hidden = YES;
self.multipleTouchEnabled = YES;
+ m_inSendEventToFocusObject = NO;
}
return self;
diff --git a/src/plugins/platforms/ios/quiview.h b/src/plugins/platforms/ios/quiview.h
index 7a54da1321..8984561dd8 100644
--- a/src/plugins/platforms/ios/quiview.h
+++ b/src/plugins/platforms/ios/quiview.h
@@ -55,8 +55,11 @@
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) BOOL enablesReturnKeyAutomatically;
@@ -67,5 +70,8 @@
@end
-@interface QUIView (TextInput) <UIKeyInput>
+@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
index 76d697dc19..06d6fb4078 100644
--- a/src/plugins/platforms/ios/quiview_textinput.mm
+++ b/src/plugins/platforms/ios/quiview_textinput.mm
@@ -39,6 +39,94 @@
**
****************************************************************************/
+#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
@@ -64,6 +152,300 @@
return [super resignFirstResponder];
}
+- (void)updateInputMethodWithQuery:(Qt::InputMethodQueries)query
+{
+ // TODO: check what changed, and perhaps update delegate if the text was
+ // changed from somewhere other than this plugin....
+
+ // Note: This function is called both when as a result of the application changing the
+ // input, but also (and most commonly) as a response to us sending QInputMethodQueryEvents.
+ // Because of the latter, we cannot call textWill/DidChange here, as that will confuse
+ // iOS IM handling, and e.g stop spellchecking from working.
+ Q_UNUSED(query);
+
+ QObject *focusObject = QGuiApplication::focusObject();
+ if (!focusObject)
+ return;
+
+ if (!m_inSendEventToFocusObject)
+ [self.inputDelegate textWillChange:id<UITextInput>(self)];
+
+ staticVariables()->inputMethodQueryEvent = QInputMethodQueryEvent(Qt::ImQueryInput);
+ QCoreApplication::sendEvent(focusObject, &staticVariables()->inputMethodQueryEvent);
+
+ if (!m_inSendEventToFocusObject)
+ [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;
+}
+
+- (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 *)markedTextStyle
+{
+ return [NSDictionary dictionary];
+}
+
- (BOOL)hasText
{
return YES;