summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios/qiostextresponder.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/ios/qiostextresponder.mm')
-rw-r--r--src/plugins/platforms/ios/qiostextresponder.mm333
1 files changed, 206 insertions, 127 deletions
diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm
index a65f8bbaa1..deca8c7617 100644
--- a/src/plugins/platforms/ios/qiostextresponder.mm
+++ b/src/plugins/platforms/ios/qiostextresponder.mm
@@ -161,12 +161,11 @@
// -------------------------------------------------------------------------
-@implementation QIOSTextInputResponder {
+@implementation QIOSTextResponder {
+ @public
QT_PREPEND_NAMESPACE(QIOSInputContext) *m_inputContext;
QT_PREPEND_NAMESPACE(QInputMethodQueryEvent) *m_configuredImeState;
- QString m_markedText;
BOOL m_inSendEventToFocusObject;
- BOOL m_inSelectionChange;
}
- (instancetype)initWithInputContext:(QT_PREPEND_NAMESPACE(QIOSInputContext) *)inputContext
@@ -174,14 +173,207 @@
if (!(self = [self init]))
return self;
+ m_inputContext = inputContext;
+ m_configuredImeState = static_cast<QInputMethodQueryEvent*>(m_inputContext->imeState().currentState.clone());
m_inSendEventToFocusObject = NO;
+
+ return self;
+}
+
+- (void)dealloc
+{
+ delete m_configuredImeState;
+ [super dealloc];
+}
+
+- (QVariant)currentImeState:(Qt::InputMethodQuery)query
+{
+ return m_inputContext->imeState().currentState.value(query);
+}
+
+- (BOOL)canBecomeFirstResponder
+{
+ return YES;
+}
+
+- (BOOL)becomeFirstResponder
+{
+ FirstResponderCandidate firstResponderCandidate(self);
+
+ qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+
+ if (![super becomeFirstResponder]) {
+ qImDebug() << self << "was not allowed to become first responder";
+ return NO;
+ }
+
+ qImDebug() << self << "became first responder";
+
+ return YES;
+}
+
+- (BOOL)resignFirstResponder
+{
+ qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+
+ // Don't allow activation events of the window that we're doing text on behalf on
+ // to steal responder.
+ if (FirstResponderCandidate::currentCandidate() == [self nextResponder]) {
+ qImDebug("not allowing parent window to steal responder");
+ return NO;
+ }
+
+ if (![super resignFirstResponder])
+ return NO;
+
+ qImDebug() << self << "resigned first responder";
+
+ // Dismissing the keyboard will trigger resignFirstResponder, but so will
+ // a regular responder transfer to another window. In the former case, iOS
+ // will set the new first-responder to our next-responder, and in the latter
+ // case we'll have an active responder candidate.
+ if (![UIResponder currentFirstResponder] && !FirstResponderCandidate::currentCandidate()) {
+ // No first responder set anymore, sync this with Qt by clearing the
+ // focus object.
+ m_inputContext->clearCurrentFocusObject();
+ } else if ([UIResponder currentFirstResponder] == [self nextResponder]) {
+ // We have resigned the keyboard, and transferred first responder back to the parent view
+ Q_ASSERT(!FirstResponderCandidate::currentCandidate());
+ if ([self currentImeState:Qt::ImEnabled].toBool()) {
+ // The current focus object expects text input, but there
+ // is no keyboard to get input from. So we clear focus.
+ qImDebug("no keyboard available, clearing focus object");
+ m_inputContext->clearCurrentFocusObject();
+ }
+ } else {
+ // We've lost responder status because another Qt window was made active,
+ // another QIOSTextResponder was made first-responder, another UIView was
+ // made first-responder, or the first-responder was cleared globally. In
+ // either of these cases we don't have to do anything.
+ qImDebug("lost first responder, but not clearing focus object");
+ }
+
+ return YES;
+}
+
+- (UIResponder*)nextResponder
+{
+ return qApp->focusWindow() ?
+ reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
+}
+
+// -------------------------------------------------------------------------
+
+- (void)notifyInputDelegate:(Qt::InputMethodQueries)updatedProperties
+{
+ Q_UNUSED(updatedProperties);
+}
+
+- (BOOL)needsKeyboardReconfigure:(Qt::InputMethodQueries)updatedProperties
+{
+ if (updatedProperties & Qt::ImEnabled) {
+ qImDebug() << "Qt::ImEnabled has changed since text responder was configured, need reconfigure";
+ return YES;
+ }
+
+ if (updatedProperties & Qt::ImReadOnly) {
+ qImDebug() << "Qt::ImReadOnly has changed since text responder was configured, need reconfigure";
+ return YES;
+ }
+
+ return NO;
+}
+
+- (void)reset
+{
+ // Nothing to reset for read-only text fields
+}
+
+- (void)commit
+{
+ // Nothing to commit for read-only text fields
+}
+
+// -------------------------------------------------------------------------
+
+#ifndef QT_NO_SHORTCUT
+
+- (void)sendKeyPressRelease:(Qt::Key)key modifiers:(Qt::KeyboardModifiers)modifiers
+{
+ QScopedValueRollback<BOOL> rollback(m_inSendEventToFocusObject, true);
+ QWindowSystemInterface::handleKeyEvent(qApp->focusWindow(), QEvent::KeyPress, key, modifiers);
+ QWindowSystemInterface::handleKeyEvent(qApp->focusWindow(), QEvent::KeyRelease, key, modifiers);
+}
+
+- (void)sendShortcut:(QKeySequence::StandardKey)standardKey
+{
+ const QKeyCombination combination = QKeySequence(standardKey)[0];
+ [self sendKeyPressRelease:combination.key() modifiers:combination.keyboardModifiers()];
+}
+
+- (BOOL)hasSelection
+{
+ QInputMethodQueryEvent query(Qt::ImAnchorPosition | Qt::ImCursorPosition);
+ QGuiApplication::sendEvent(QGuiApplication::focusObject(), &query);
+ int anchorPos = query.value(Qt::ImAnchorPosition).toInt();
+ int cursorPos = query.value(Qt::ImCursorPosition).toInt();
+ return anchorPos != cursorPos;
+}
+
+- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
+{
+ const bool isSelectAction =
+ action == @selector(select:) ||
+ action == @selector(selectAll:);
+
+ const bool isReadAction = action == @selector(copy:);
+
+ if (!isSelectAction && !isReadAction)
+ return [super canPerformAction:action withSender:sender];
+
+ const bool hasSelection = [self hasSelection];
+ return (!hasSelection && isSelectAction) || (hasSelection && isReadAction);
+}
+
+- (void)copy:(id)sender
+{
+ Q_UNUSED(sender);
+ [self sendShortcut:QKeySequence::Copy];
+}
+
+- (void)select:(id)sender
+{
+ Q_UNUSED(sender);
+ [self sendShortcut:QKeySequence::MoveToPreviousWord];
+ [self sendShortcut:QKeySequence::SelectNextWord];
+}
+
+- (void)selectAll:(id)sender
+{
+ Q_UNUSED(sender);
+ [self sendShortcut:QKeySequence::SelectAll];
+}
+
+#endif // QT_NO_SHORTCUT
+
+@end
+
+// -------------------------------------------------------------------------
+
+@implementation QIOSTextInputResponder {
+ QString m_markedText;
+ BOOL m_inSelectionChange;
+}
+
+- (instancetype)initWithInputContext:(QT_PREPEND_NAMESPACE(QIOSInputContext) *)inputContext
+{
+ if (!(self = [super initWithInputContext:inputContext]))
+ return self;
+
m_inSelectionChange = NO;
- m_inputContext = inputContext;
- m_configuredImeState = static_cast<QInputMethodQueryEvent*>(m_inputContext->imeState().currentState.clone());
QVariantMap platformData = m_configuredImeState->value(Qt::ImPlatformData).toMap();
Qt::InputMethodHints hints = Qt::InputMethodHints(m_configuredImeState->value(Qt::ImHints).toUInt());
-
Qt::EnterKeyType enterKeyType = Qt::EnterKeyType(m_configuredImeState->value(Qt::ImEnterKeyType).toUInt());
switch (enterKeyType) {
@@ -264,29 +456,27 @@
{
self.inputView = 0;
self.inputAccessoryView = 0;
- delete m_configuredImeState;
[super dealloc];
}
- (BOOL)needsKeyboardReconfigure:(Qt::InputMethodQueries)updatedProperties
{
- if ((updatedProperties & Qt::ImEnabled)) {
- Q_ASSERT([self currentImeState:Qt::ImEnabled].toBool());
-
+ Qt::InputMethodQueries relevantProperties = updatedProperties;
+ if ((relevantProperties & Qt::ImEnabled)) {
// When switching on input-methods we need to consider hints and platform data
// as well, as the IM state that we were based on may have been invalidated when
// IM was switched off.
qImDebug("IM was turned on, we need to check hints and platform data as well");
- updatedProperties |= (Qt::ImHints | Qt::ImPlatformData);
+ relevantProperties |= (Qt::ImHints | Qt::ImPlatformData);
}
// Based on what we set up in initWithInputContext above
- updatedProperties &= (Qt::ImHints | Qt::ImEnterKeyType | Qt::ImPlatformData);
+ relevantProperties &= (Qt::ImHints | Qt::ImEnterKeyType | Qt::ImPlatformData);
- if (!updatedProperties)
- return NO;
+ if (!relevantProperties)
+ return [super needsKeyboardReconfigure:updatedProperties];
for (uint i = 0; i < (sizeof(Qt::ImQueryAll) * CHAR_BIT); ++i) {
if (Qt::InputMethodQuery property = Qt::InputMethodQuery(int(updatedProperties & (1 << i)))) {
@@ -297,100 +487,13 @@
}
}
- return NO;
-}
-
-- (BOOL)canBecomeFirstResponder
-{
- return YES;
-}
-
-- (BOOL)becomeFirstResponder
-{
- FirstResponderCandidate firstResponderCandidate(self);
-
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
-
- if (![super becomeFirstResponder]) {
- qImDebug() << self << "was not allowed to become first responder";
- return NO;
- }
-
- qImDebug() << self << "became first responder";
-
- return YES;
-}
-
-- (BOOL)resignFirstResponder
-{
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
-
- // Don't allow activation events of the window that we're doing text on behalf on
- // to steal responder.
- if (FirstResponderCandidate::currentCandidate() == [self nextResponder]) {
- qImDebug("not allowing parent window to steal responder");
- return NO;
- }
-
- if (![super resignFirstResponder])
- return NO;
-
- qImDebug() << self << "resigned first responder";
-
- // Dismissing the keyboard will trigger resignFirstResponder, but so will
- // a regular responder transfer to another window. In the former case, iOS
- // will set the new first-responder to our next-responder, and in the latter
- // case we'll have an active responder candidate.
- if (![UIResponder currentFirstResponder] && !FirstResponderCandidate::currentCandidate()) {
- // No first responder set anymore, sync this with Qt by clearing the
- // focus object.
- m_inputContext->clearCurrentFocusObject();
- } else if ([UIResponder currentFirstResponder] == [self nextResponder]) {
- // We have resigned the keyboard, and transferred first responder back to the parent view
- Q_ASSERT(!FirstResponderCandidate::currentCandidate());
- if ([self currentImeState:Qt::ImEnabled].toBool()) {
- // The current focus object expects text input, but there
- // is no keyboard to get input from. So we clear focus.
- qImDebug("no keyboard available, clearing focus object");
- m_inputContext->clearCurrentFocusObject();
- }
- } else {
- // We've lost responder status because another Qt window was made active,
- // another QIOSTextResponder was made first-responder, another UIView was
- // made first-responder, or the first-responder was cleared globally. In
- // either of these cases we don't have to do anything.
- qImDebug("lost first responder, but not clearing focus object");
- }
-
- return YES;
-}
-
-
-- (UIResponder*)nextResponder
-{
- return qApp->focusWindow() ?
- reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
+ return [super needsKeyboardReconfigure:updatedProperties];
}
// -------------------------------------------------------------------------
-- (void)sendKeyPressRelease:(Qt::Key)key modifiers:(Qt::KeyboardModifiers)modifiers
-{
- QScopedValueRollback<BOOL> rollback(m_inSendEventToFocusObject, true);
- QWindowSystemInterface::handleKeyEvent(qApp->focusWindow(), QEvent::KeyPress, key, modifiers);
- QWindowSystemInterface::handleKeyEvent(qApp->focusWindow(), QEvent::KeyRelease, key, modifiers);
-}
-
#ifndef QT_NO_SHORTCUT
-- (void)sendShortcut:(QKeySequence::StandardKey)standardKey
-{
- const int keys = QKeySequence(standardKey)[0];
- Qt::Key key = Qt::Key(keys & 0x0000FFFF);
- Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keys & 0xFFFF0000);
- [self sendKeyPressRelease:key modifiers:modifiers];
-}
-
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
bool isEditAction = (action == @selector(cut:)
@@ -410,7 +513,7 @@
|| action == @selector(redo));
const bool unknownAction = !isEditAction && !isSelectAction;
- const bool hasSelection = ![self selectedTextRange].empty;
+ const bool hasSelection = [self hasSelection];
if (unknownAction)
return [super canPerformAction:action withSender:sender];
@@ -434,31 +537,12 @@
[self sendShortcut:QKeySequence::Cut];
}
-- (void)copy:(id)sender
-{
- Q_UNUSED(sender);
- [self sendShortcut:QKeySequence::Copy];
-}
-
- (void)paste:(id)sender
{
Q_UNUSED(sender);
[self sendShortcut:QKeySequence::Paste];
}
-- (void)select:(id)sender
-{
- Q_UNUSED(sender);
- [self sendShortcut:QKeySequence::MoveToPreviousWord];
- [self sendShortcut:QKeySequence::SelectNextWord];
-}
-
-- (void)selectAll:(id)sender
-{
- Q_UNUSED(sender);
- [self sendShortcut:QKeySequence::SelectAll];
-}
-
- (void)delete:(id)sender
{
Q_UNUSED(sender);
@@ -647,11 +731,6 @@
QCoreApplication::sendEvent(focusObject, &e);
}
-- (QVariant)currentImeState:(Qt::InputMethodQuery)query
-{
- return m_inputContext->imeState().currentState.value(query);
-}
-
- (id<UITextInputTokenizer>)tokenizer
{
return [[[UITextInputStringTokenizer alloc] initWithTextInput:self] autorelease];