Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qiostextresponder.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qiostextresponder.h"
5
6#include "qiosglobal.h"
7#include "qiosinputcontext.h"
8#include "quiview.h"
9
10#include <QtCore/qscopedvaluerollback.h>
11
12#include <QtGui/qevent.h>
13#include <QtGui/qtextformat.h>
14#include <QtGui/private/qguiapplication_p.h>
15#include <QtGui/qpa/qplatformwindow.h>
16
17// -------------------------------------------------------------------------
18
19@interface QUITextPosition : UITextPosition
20
21@property (nonatomic) NSUInteger index;
22+ (instancetype)positionWithIndex:(NSUInteger)index;
23
24@end
25
26@implementation QUITextPosition
27
28+ (instancetype)positionWithIndex:(NSUInteger)index
29{
30 QUITextPosition *pos = [[QUITextPosition alloc] init];
31 pos.index = index;
32 return [pos autorelease];
33}
34
35@end
36
37// -------------------------------------------------------------------------
38
39@interface QUITextRange : UITextRange
40
41@property (nonatomic) NSRange range;
42+ (instancetype)rangeWithNSRange:(NSRange)range;
43
44@end
45
46@implementation QUITextRange
47
48+ (instancetype)rangeWithNSRange:(NSRange)nsrange
49{
50 QUITextRange *range = [[self alloc] init];
51 range.range = nsrange;
52 return [range autorelease];
53}
54
55- (UITextPosition *)start
56{
57 return [QUITextPosition positionWithIndex:self.range.location];
58}
59
60- (UITextPosition *)end
61{
62 return [QUITextPosition positionWithIndex:(self.range.location + self.range.length)];
63}
64
65- (NSRange)range
66{
67 return _range;
68}
69
70- (BOOL)isEmpty
71{
72 return (self.range.length == 0);
73}
74
75@end
76
77// -------------------------------------------------------------------------
78
79@interface WrapperView : UIView
80@end
81
82@implementation WrapperView
83
84- (instancetype)initWithView:(UIView *)view
85{
86 if (self = [self init]) {
87 [self addSubview:view];
88
89 self.autoresizingMask = view.autoresizingMask;
90
91 [self sizeToFit];
92 }
93
94 return self;
95}
96
97- (void)layoutSubviews
98{
99 UIView *view = [self.subviews firstObject];
100 view.frame = self.bounds;
101
102 // FIXME: During orientation changes the size and position
103 // of the view is not respected by the host view, even if
104 // we call sizeToFit or setNeedsLayout on the superview.
105}
106
107- (CGSize)sizeThatFits:(CGSize)size
108{
109 return [[self.subviews firstObject] sizeThatFits:size];
110}
111
112// By keeping the responder (QIOSTextInputResponder in this case)
113// retained, we ensure that all messages sent to the view during
114// its lifetime in a window hierarchy will be able to traverse the
115// responder chain.
116- (void)willMoveToWindow:(UIWindow *)window
117{
118 if (window)
119 [[self nextResponder] retain];
120 else
121 [[self nextResponder] autorelease];
122}
123
124@end
125
126// -------------------------------------------------------------------------
127
128@implementation QIOSTextResponder {
129 @public
130 QT_PREPEND_NAMESPACE(QIOSInputContext) *m_inputContext;
131 QT_PREPEND_NAMESPACE(QInputMethodQueryEvent) *m_configuredImeState;
133}
134
135- (instancetype)initWithInputContext:(QT_PREPEND_NAMESPACE(QIOSInputContext) *)inputContext
136{
137 if (!(self = [self init]))
138 return self;
139
140 m_inputContext = inputContext;
141 m_configuredImeState = static_cast<QInputMethodQueryEvent*>(m_inputContext->imeState().currentState.clone());
143
144 return self;
145}
146
147- (void)dealloc
148{
149 delete m_configuredImeState;
150 [super dealloc];
151}
152
153- (QVariant)currentImeState:(Qt::InputMethodQuery)query
154{
155 return m_inputContext->imeState().currentState.value(query);
156}
157
158- (BOOL)canBecomeFirstResponder
159{
160 return YES;
161}
162
163- (BOOL)becomeFirstResponder
164{
165 FirstResponderCandidate firstResponderCandidate(self);
166
167 qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
168
169 if (![super becomeFirstResponder]) {
170 qImDebug() << self << "was not allowed to become first responder";
171 return NO;
172 }
173
174 qImDebug() << self << "became first responder";
175
176 return YES;
177}
178
179- (BOOL)resignFirstResponder
180{
181 qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
182
183 // Don't allow activation events of the window that we're doing text on behalf on
184 // to steal responder.
185 if (FirstResponderCandidate::currentCandidate() == [self nextResponder]) {
186 qImDebug("not allowing parent window to steal responder");
187 return NO;
188 }
189
190 if (![super resignFirstResponder])
191 return NO;
192
193 qImDebug() << self << "resigned first responder";
194
195 // Dismissing the keyboard will trigger resignFirstResponder, but so will
196 // a regular responder transfer to another window. In the former case, iOS
197 // will set the new first-responder to our next-responder, and in the latter
198 // case we'll have an active responder candidate.
199 if (![UIResponder qt_currentFirstResponder] && !FirstResponderCandidate::currentCandidate()) {
200 // No first responder set anymore, sync this with Qt by clearing the
201 // focus object.
202 m_inputContext->clearCurrentFocusObject();
203 } else if ([UIResponder qt_currentFirstResponder] == [self nextResponder]) {
204 // We have resigned the keyboard, and transferred first responder back to the parent view
206 if ([self currentImeState:Qt::ImEnabled].toBool()) {
207 // The current focus object expects text input, but there
208 // is no keyboard to get input from. So we clear focus.
209 qImDebug("no keyboard available, clearing focus object");
210 m_inputContext->clearCurrentFocusObject();
211 }
212 } else {
213 // We've lost responder status because another Qt window was made active,
214 // another QIOSTextResponder was made first-responder, another UIView was
215 // made first-responder, or the first-responder was cleared globally. In
216 // either of these cases we don't have to do anything.
217 qImDebug("lost first responder, but not clearing focus object");
218 }
219
220 return YES;
221}
222
223- (UIResponder*)nextResponder
224{
225 // Make sure we have a handle/platform window before getting the winId().
226 // In the dtor of QIOSWindow the platform window is set to null before calling
227 // removeFromSuperview which will end up calling nextResponder. That means it's
228 // possible that we can get here while the window is being torn down.
229 return (qApp->focusWindow() && qApp->focusWindow()->handle()) ?
230 reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
231}
232
233// -------------------------------------------------------------------------
234
235- (void)notifyInputDelegate:(Qt::InputMethodQueries)updatedProperties
236{
237 Q_UNUSED(updatedProperties);
238}
239
240- (BOOL)needsKeyboardReconfigure:(Qt::InputMethodQueries)updatedProperties
241{
242 if (updatedProperties & Qt::ImEnabled) {
243 qImDebug() << "Qt::ImEnabled has changed since text responder was configured, need reconfigure";
244 return YES;
245 }
246
247 if (updatedProperties & Qt::ImReadOnly) {
248 qImDebug() << "Qt::ImReadOnly has changed since text responder was configured, need reconfigure";
249 return YES;
250 }
251
252 return NO;
253}
254
255- (void)reset
256{
257 // Nothing to reset for read-only text fields
258}
259
260- (void)commit
261{
262 // Nothing to commit for read-only text fields
263}
264
265// -------------------------------------------------------------------------
266
267#ifndef QT_NO_SHORTCUT
268
269- (void)sendKeyPressRelease:(Qt::Key)key modifiers:(Qt::KeyboardModifiers)modifiers
270{
271 QScopedValueRollback<BOOL> rollback(m_inSendEventToFocusObject, true);
274}
275
276- (void)sendShortcut:(QKeySequence::StandardKey)standardKey
277{
278 const QKeyCombination combination = QKeySequence(standardKey)[0];
279 [self sendKeyPressRelease:combination.key() modifiers:combination.keyboardModifiers()];
280}
281
282- (BOOL)hasSelection
283{
286 int anchorPos = query.value(Qt::ImAnchorPosition).toInt();
287 int cursorPos = query.value(Qt::ImCursorPosition).toInt();
288 return anchorPos != cursorPos;
289}
290
291- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
292{
293 const bool isSelectAction =
294 action == @selector(select:) ||
295 action == @selector(selectAll:);
296
297 const bool isReadAction = action == @selector(copy:);
298
299 if (!isSelectAction && !isReadAction)
300 return [super canPerformAction:action withSender:sender];
301
302 const bool hasSelection = [self hasSelection];
303 return (!hasSelection && isSelectAction) || (hasSelection && isReadAction);
304}
305
306- (void)copy:(id)sender
307{
308 Q_UNUSED(sender);
309 [self sendShortcut:QKeySequence::Copy];
310}
311
312- (void)select:(id)sender
313{
314 Q_UNUSED(sender);
315 [self sendShortcut:QKeySequence::MoveToPreviousWord];
316 [self sendShortcut:QKeySequence::SelectNextWord];
317}
318
319- (void)selectAll:(id)sender
320{
321 Q_UNUSED(sender);
322 [self sendShortcut:QKeySequence::SelectAll];
323}
324
325#endif // QT_NO_SHORTCUT
326
327@end
328
329// -------------------------------------------------------------------------
330
331@implementation QIOSTextInputResponder {
332 QString m_markedText;
334}
335
336- (instancetype)initWithInputContext:(QT_PREPEND_NAMESPACE(QIOSInputContext) *)inputContext
337{
338 if (!(self = [super initWithInputContext:inputContext]))
339 return self;
340
342
343 QVariantMap platformData = m_configuredImeState->value(Qt::ImPlatformData).toMap();
344 Qt::InputMethodHints hints = Qt::InputMethodHints(m_configuredImeState->value(Qt::ImHints).toUInt());
345 Qt::EnterKeyType enterKeyType = Qt::EnterKeyType(m_configuredImeState->value(Qt::ImEnterKeyType).toUInt());
346
347 switch (enterKeyType) {
349 self.returnKeyType = UIReturnKeyDefault;
350 break;
351 case Qt::EnterKeyDone:
352 self.returnKeyType = UIReturnKeyDone;
353 break;
354 case Qt::EnterKeyGo:
355 self.returnKeyType = UIReturnKeyGo;
356 break;
357 case Qt::EnterKeySend:
358 self.returnKeyType = UIReturnKeySend;
359 break;
361 self.returnKeyType = UIReturnKeySearch;
362 break;
363 case Qt::EnterKeyNext:
364 self.returnKeyType = UIReturnKeyNext;
365 break;
366 default:
367 self.returnKeyType = (hints & Qt::ImhMultiLine) ? UIReturnKeyDefault : UIReturnKeyDone;
368 break;
369 }
370
371 self.secureTextEntry = BOOL(hints & Qt::ImhHiddenText);
372 self.autocorrectionType = (hints & Qt::ImhNoPredictiveText) ?
373 UITextAutocorrectionTypeNo : UITextAutocorrectionTypeDefault;
374 self.spellCheckingType = (hints & Qt::ImhNoPredictiveText) ?
375 UITextSpellCheckingTypeNo : UITextSpellCheckingTypeDefault;
376
377 if (hints & Qt::ImhUppercaseOnly)
378 self.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters;
379 else if (hints & Qt::ImhNoAutoUppercase)
380 self.autocapitalizationType = UITextAutocapitalizationTypeNone;
381 else
382 self.autocapitalizationType = UITextAutocapitalizationTypeSentences;
383
384 if (hints & Qt::ImhUrlCharactersOnly)
385 self.keyboardType = UIKeyboardTypeURL;
386 else if (hints & Qt::ImhEmailCharactersOnly)
387 self.keyboardType = UIKeyboardTypeEmailAddress;
388 else if (hints & Qt::ImhDigitsOnly)
389 self.keyboardType = UIKeyboardTypeNumberPad;
390 else if (hints & Qt::ImhDialableCharactersOnly)
391 self.keyboardType = UIKeyboardTypePhonePad;
392 else if (hints & Qt::ImhLatinOnly)
393 self.keyboardType = UIKeyboardTypeASCIICapable;
395 self.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
396 else
397 self.keyboardType = UIKeyboardTypeDefault;
398
399 if (UIView *inputView = static_cast<UIView *>(platformData.value(kImePlatformDataInputView).value<void *>()))
400 self.inputView = [[[WrapperView alloc] initWithView:inputView] autorelease];
401 if (UIView *accessoryView = static_cast<UIView *>(platformData.value(kImePlatformDataInputAccessoryView).value<void *>()))
402 self.inputAccessoryView = [[[WrapperView alloc] initWithView:accessoryView] autorelease];
403
404#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
405 if (platformData.value(kImePlatformDataHideShortcutsBar).toBool()) {
406 // According to the docs, leadingBarButtonGroups/trailingBarButtonGroups should be set to nil to hide the shortcuts bar.
407 // However, starting with iOS 10, the API has been surrounded with NS_ASSUME_NONNULL, which contradicts this and causes
408 // compiler warnings. Still it is the way to go to really hide the space reserved for that.
409#pragma clang diagnostic push
410#pragma clang diagnostic ignored "-Wnonnull"
411 self.inputAssistantItem.leadingBarButtonGroups = nil;
412 self.inputAssistantItem.trailingBarButtonGroups = nil;
413#pragma clang diagnostic pop
414 }
415#endif
416
417 self.undoManager.groupsByEvent = NO;
418 [self rebuildUndoStack];
419
420 return self;
421}
422
423- (void)dealloc
424{
425 self.inputView = 0;
426 self.inputAccessoryView = 0;
427
428 [super dealloc];
429}
430
431- (BOOL)needsKeyboardReconfigure:(Qt::InputMethodQueries)updatedProperties
432{
433 Qt::InputMethodQueries relevantProperties = updatedProperties;
434 if ((relevantProperties & Qt::ImEnabled)) {
435 // When switching on input-methods we need to consider hints and platform data
436 // as well, as the IM state that we were based on may have been invalidated when
437 // IM was switched off.
438
439 qImDebug("IM was turned on, we need to check hints and platform data as well");
440 relevantProperties |= (Qt::ImHints | Qt::ImPlatformData);
441 }
442
443 // Based on what we set up in initWithInputContext above
444 relevantProperties &= (Qt::ImHints | Qt::ImEnterKeyType | Qt::ImPlatformData);
445
446 if (!relevantProperties)
447 return [super needsKeyboardReconfigure:updatedProperties];
448
449 for (uint i = 0; i < (sizeof(Qt::ImQueryAll) * CHAR_BIT); ++i) {
450 if (Qt::InputMethodQuery property = Qt::InputMethodQuery(int(updatedProperties & (1 << i)))) {
451 if ([self currentImeState:property] != m_configuredImeState->value(property)) {
452 qImDebug() << property << "has changed since text responder was configured, need reconfigure";
453 return YES;
454 }
455 }
456 }
457
458 return [super needsKeyboardReconfigure:updatedProperties];
459}
460
461- (void)reset
462{
463 [self setMarkedText:@"" selectedRange:NSMakeRange(0, 0)];
464 [self notifyInputDelegate:Qt::ImSurroundingText];
465}
466
467- (void)commit
468{
469 [self unmarkText];
470 [self notifyInputDelegate:Qt::ImSurroundingText];
471}
472
473// -------------------------------------------------------------------------
474
475#ifndef QT_NO_SHORTCUT
476
477- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
478{
479 bool isEditAction = (action == @selector(cut:)
480 || action == @selector(copy:)
481 || action == @selector(paste:)
482 || action == @selector(delete:)
483 || action == @selector(toggleBoldface:)
484 || action == @selector(toggleItalics:)
485 || action == @selector(toggleUnderline:)
486 || action == @selector(undo)
487 || action == @selector(redo));
488
489 bool isSelectAction = (action == @selector(select:)
490 || action == @selector(selectAll:)
491 || action == @selector(paste:)
492 || action == @selector(undo)
493 || action == @selector(redo));
494
495 const bool unknownAction = !isEditAction && !isSelectAction;
496 const bool hasSelection = [self hasSelection];
497
498 if (unknownAction)
499 return [super canPerformAction:action withSender:sender];
500
501 QObject *focusObject = QGuiApplication::focusObject();
502 if (focusObject && focusObject->property("qt_im_readonly").toBool()) {
503 // exceptional menu items for read-only views: do include Copy, do not include Paste etc.
504 if (action == @selector(cut:)
505 || action == @selector(paste:)
506 || action == @selector(delete:))
507 return NO;
508 if (action == @selector(copy:))
509 return YES;
510 }
511 return (hasSelection && isEditAction) || (!hasSelection && isSelectAction);
512}
513
514- (void)cut:(id)sender
515{
516 Q_UNUSED(sender);
517 [self sendShortcut:QKeySequence::Cut];
518}
519
520- (void)paste:(id)sender
521{
522 Q_UNUSED(sender);
523 [self sendShortcut:QKeySequence::Paste];
524}
525
526- (void)delete:(id)sender
527{
528 Q_UNUSED(sender);
529 [self sendShortcut:QKeySequence::Delete];
530}
531
532- (void)toggleBoldface:(id)sender
533{
534 Q_UNUSED(sender);
535 [self sendShortcut:QKeySequence::Bold];
536}
537
538- (void)toggleItalics:(id)sender
539{
540 Q_UNUSED(sender);
541 [self sendShortcut:QKeySequence::Italic];
542}
543
544- (void)toggleUnderline:(id)sender
545{
546 Q_UNUSED(sender);
547 [self sendShortcut:QKeySequence::Underline];
548}
549
550// -------------------------------------------------------------------------
551
552- (void)undo
553{
554 [self sendShortcut:QKeySequence::Undo];
555 [self rebuildUndoStack];
556}
557
558- (void)redo
559{
560 [self sendShortcut:QKeySequence::Redo];
561 [self rebuildUndoStack];
562}
563
564- (void)registerRedo
565{
566 NSUndoManager *undoMgr = self.undoManager;
567 [undoMgr beginUndoGrouping];
568 [undoMgr registerUndoWithTarget:self selector:@selector(redo) object:nil];
569 [undoMgr endUndoGrouping];
570}
571
572- (void)rebuildUndoStack
573{
574 dispatch_async(dispatch_get_main_queue (), ^{
575 // Register dummy undo/redo operations to enable Cmd-Z and Cmd-Shift-Z
576 // Ensure we do this outside any undo/redo callback since NSUndoManager
577 // will treat registerUndoWithTarget as registering a redo when called
578 // from within a undo callback.
579 NSUndoManager *undoMgr = self.undoManager;
580 [undoMgr removeAllActions];
581
582 [undoMgr beginUndoGrouping];
583 [undoMgr registerUndoWithTarget:self selector:@selector(undo) object:nil];
584 [undoMgr endUndoGrouping];
585 [undoMgr beginUndoGrouping];
586 [undoMgr registerUndoWithTarget:self selector:@selector(undo) object:nil];
587 [undoMgr endUndoGrouping];
588
589 // Schedule operations that we immediately pop off to be able to schedule redos
590 [undoMgr beginUndoGrouping];
591 [undoMgr registerUndoWithTarget:self selector:@selector(registerRedo) object:nil];
592 [undoMgr endUndoGrouping];
593 [undoMgr beginUndoGrouping];
594 [undoMgr registerUndoWithTarget:self selector:@selector(registerRedo) object:nil];
595 [undoMgr endUndoGrouping];
596 [undoMgr undo];
597 [undoMgr undo];
598
599 // Note that, perhaps because of a bug in UIKit, the buttons on the shortcuts bar ends up
600 // disabled if a undo/redo callback doesn't lead to a [UITextInputDelegate textDidChange].
601 // And we only call that method if Qt made changes to the text. The effect is that the buttons
602 // become disabled when there is nothing more to undo (Qt didn't change anything upon receiving
603 // an undo request). This seems to be OK behavior, so we let it stay like that unless it shows
604 // to cause problems.
605
606 // QTBUG-63393: Having two operations on the rebuilt undo stack keeps the undo/redo widgets
607 // always enabled on the shortcut bar. This workaround was found by experimenting with
608 // removing the removeAllActions call, and is related to the unknown internal implementation
609 // details of how the shortcut bar updates the dimming of its buttons.
610 });
611}
612
613// -------------------------------------------------------------------------
614
615- (void)keyCommandTriggered:(UIKeyCommand *)keyCommand
616{
618 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
619
620 if (keyCommand.input == UIKeyInputLeftArrow)
622 else if (keyCommand.input == UIKeyInputRightArrow)
624 else if (keyCommand.input == UIKeyInputUpArrow)
625 key = Qt::Key_Up;
626 else if (keyCommand.input == UIKeyInputDownArrow)
628 else
629 Q_UNREACHABLE();
630
631 if (keyCommand.modifierFlags & UIKeyModifierAlternate)
633 if (keyCommand.modifierFlags & UIKeyModifierShift)
635 if (keyCommand.modifierFlags & UIKeyModifierCommand)
637
638 [self sendKeyPressRelease:key modifiers:modifiers];
639}
640
641- (void)addKeyCommandsToArray:(NSMutableArray<UIKeyCommand *> *)array key:(NSString *)key
642{
643 SEL s = @selector(keyCommandTriggered:);
644 [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:0 action:s]];
645 [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierShift action:s]];
646 [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierAlternate action:s]];
647 [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierAlternate|UIKeyModifierShift action:s]];
648 [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierCommand action:s]];
649 [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierCommand|UIKeyModifierShift action:s]];
650}
651
652- (NSArray<UIKeyCommand *> *)keyCommands
653{
654 // Since keyCommands is called for every key
655 // press/release, we cache the result
656 static dispatch_once_t once;
657 static NSMutableArray<UIKeyCommand *> *array;
658
659 dispatch_once(&once, ^{
660 // We let Qt move the cursor around when the arrow keys are being used. This
661 // is normally implemented through UITextInput, but since IM in Qt have poor
662 // support for moving the cursor vertically, and even less support for selecting
663 // text across multiple paragraphs, we do this through key events.
664 array = [NSMutableArray<UIKeyCommand *> new];
665 [self addKeyCommandsToArray:array key:UIKeyInputUpArrow];
666 [self addKeyCommandsToArray:array key:UIKeyInputDownArrow];
667 [self addKeyCommandsToArray:array key:UIKeyInputLeftArrow];
668 [self addKeyCommandsToArray:array key:UIKeyInputRightArrow];
669 });
670
671 return array;
672}
673
674#endif // QT_NO_SHORTCUT
675
676// -------------------------------------------------------------------------
677
678- (void)notifyInputDelegate:(Qt::InputMethodQueries)updatedProperties
679{
680 // As documented, we should not report textWillChange/textDidChange unless the text
681 // was changed externally. That will cause spell checking etc to fail. But we don't
682 // really know if the text/selection was changed by UITextInput or Qt/app when getting
683 // update calls from Qt. We therefore use a less ideal approach where we always assume
684 // that UITextView caused the change if we're currently processing an event sendt from it.
686 return;
687
688 if (updatedProperties & (Qt::ImCursorPosition | Qt::ImAnchorPosition)) {
689 QScopedValueRollback<BOOL> rollback(m_inSelectionChange, true);
690 [self.inputDelegate selectionWillChange:self];
691 [self.inputDelegate selectionDidChange:self];
692 }
693
694 if (updatedProperties & Qt::ImSurroundingText) {
695 [self.inputDelegate textWillChange:self];
696 [self.inputDelegate textDidChange:self];
697 }
698}
699
700- (void)sendEventToFocusObject:(QEvent &)e
701{
702 QObject *focusObject = QGuiApplication::focusObject();
703 if (!focusObject)
704 return;
705
706 // While sending the event, we will receive back updateInputMethodWithQuery calls.
707 // Note that it would be more correct to post the event instead, but UITextInput expects
708 // callbacks to take effect immediately (it will query us for information after a callback).
709 QScopedValueRollback<BOOL> rollback(m_inSendEventToFocusObject);
711 QCoreApplication::sendEvent(focusObject, &e);
712}
713
714- (id<UITextInputTokenizer>)tokenizer
715{
716 return [[[UITextInputStringTokenizer alloc] initWithTextInput:self] autorelease];
717}
718
719- (UITextPosition *)beginningOfDocument
720{
722}
723
724- (UITextPosition *)endOfDocument
725{
726 QString surroundingText = [self currentImeState:Qt::ImSurroundingText].toString();
727 int endPosition = surroundingText.length() + m_markedText.length();
728 return [QUITextPosition positionWithIndex:endPosition];
729}
730
731- (void)setSelectedTextRange:(UITextRange *)range
732{
734 // After [UITextInputDelegate selectionWillChange], UIKit will cancel
735 // any ongoing auto correction (if enabled) and ask us to set an empty selection.
736 // This is contradictory to our current attempt to set a selection, so we ignore
737 // the callback. UIKit will be re-notified of the new selection after
738 // [UITextInputDelegate selectionDidChange].
739 return;
740 }
741
742 QUITextRange *r = static_cast<QUITextRange *>(range);
743 QList<QInputMethodEvent::Attribute> attrs;
744 attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, r.range.location, r.range.length, 0);
745 QInputMethodEvent e(m_markedText, attrs);
746 [self sendEventToFocusObject:e];
747}
748
749- (UITextRange *)selectedTextRange
750{
751 int cursorPos = [self currentImeState:Qt::ImCursorPosition].toInt();
752 int anchorPos = [self currentImeState:Qt::ImAnchorPosition].toInt();
753 return [QUITextRange rangeWithNSRange:NSMakeRange(qMin(cursorPos, anchorPos), qAbs(anchorPos - cursorPos))];
754}
755
756- (NSString *)textInRange:(UITextRange *)range
757{
758 QString text = [self currentImeState:Qt::ImSurroundingText].toString();
759 if (!m_markedText.isEmpty()) {
760 // [UITextInput textInRange] is sparsely documented, but it turns out that unconfirmed
761 // marked text should be seen as a part of the text document. This is different from
762 // ImSurroundingText, which excludes it.
763 int cursorPos = [self currentImeState:Qt::ImCursorPosition].toInt();
764 text = text.left(cursorPos) + m_markedText + text.mid(cursorPos);
765 }
766
767 int s = static_cast<QUITextPosition *>([range start]).index;
768 int e = static_cast<QUITextPosition *>([range end]).index;
769 return text.mid(s, e - s).toNSString();
770}
771
772- (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange
773{
774 Q_UNUSED(selectedRange);
775
776 m_markedText = markedText ? QString::fromNSString(markedText) : QString();
777
778 static QTextCharFormat markedTextFormat;
779 if (markedTextFormat.isEmpty()) {
780 // There seems to be no way to query how the preedit text
781 // should be drawn. So we need to hard-code the color.
782 markedTextFormat.setBackground(QColor(206, 221, 238));
783 }
784
785 QList<QInputMethodEvent::Attribute> attrs;
786 attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, markedText.length, markedTextFormat);
787 QInputMethodEvent e(m_markedText, attrs);
788 [self sendEventToFocusObject:e];
789}
790
791- (void)unmarkText
792{
793 if (m_markedText.isEmpty())
794 return;
795
797 e.setCommitString(m_markedText);
798 [self sendEventToFocusObject:e];
799
800 m_markedText.clear();
801}
802
803- (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other
804{
805 int p = static_cast<QUITextPosition *>(position).index;
806 int o = static_cast<QUITextPosition *>(other).index;
807 if (p > o)
808 return NSOrderedAscending;
809 else if (p < o)
810 return NSOrderedDescending;
811 return NSOrderedSame;
812}
813
814- (UITextRange *)markedTextRange
815{
816 return m_markedText.isEmpty() ? nil : [QUITextRange rangeWithNSRange:NSMakeRange(0, m_markedText.length())];
817}
818
819- (UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
820{
821 int f = static_cast<QUITextPosition *>(fromPosition).index;
822 int t = static_cast<QUITextPosition *>(toPosition).index;
823 return [QUITextRange rangeWithNSRange:NSMakeRange(f, t - f)];
824}
825
826- (UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset
827{
828 int p = static_cast<QUITextPosition *>(position).index;
829 const int posWithIndex = p + offset;
830 const int textLength = [self currentImeState:Qt::ImSurroundingText].toString().length();
831 if (posWithIndex < 0 || posWithIndex > textLength)
832 return nil;
833 return [QUITextPosition positionWithIndex:posWithIndex];
834}
835
836- (UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset
837{
838 int p = static_cast<QUITextPosition *>(position).index;
839
840 switch (direction) {
841 case UITextLayoutDirectionLeft:
842 return [QUITextPosition positionWithIndex:p - offset];
843 case UITextLayoutDirectionRight:
844 return [QUITextPosition positionWithIndex:p + offset];
845 default:
846 // Qt doesn't support getting the position above or below the current position, so
847 // for those cases we just return the current position, making it a no-op.
848 return position;
849 }
850}
851
852- (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction
853{
854 NSRange r = static_cast<QUITextRange *>(range).range;
855 if (direction == UITextLayoutDirectionRight)
856 return [QUITextPosition positionWithIndex:r.location + r.length];
857 return [QUITextPosition positionWithIndex:r.location];
858}
859
860- (NSInteger)offsetFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
861{
862 int f = static_cast<QUITextPosition *>(fromPosition).index;
863 int t = static_cast<QUITextPosition *>(toPosition).index;
864 return t - f;
865}
866
867- (UIView *)textInputView
868{
869 auto *focusWindow = QGuiApplication::focusWindow();
870 if (!focusWindow)
871 return nil;
872
873 // iOS expects rects we return from other UITextInput methods
874 // to be relative to the view this method returns.
875 // Since QInputMethod returns rects relative to the top level
876 // QWindow, that is also the view we need to return.
877 Q_ASSERT(focusWindow->handle());
878 QPlatformWindow *topLevel = focusWindow->handle();
879 while (QPlatformWindow *p = topLevel->parent())
880 topLevel = p;
881 return reinterpret_cast<UIView *>(topLevel->winId());
882}
883
884- (CGRect)firstRectForRange:(UITextRange *)range
885{
886 QObject *focusObject = QGuiApplication::focusObject();
887 if (!focusObject)
888 return CGRectZero;
889
890 // Using a work-around to get the current rect until
891 // a better API is in place:
892 if (!m_markedText.isEmpty())
893 return CGRectZero;
894
895 int cursorPos = [self currentImeState:Qt::ImCursorPosition].toInt();
896 int anchorPos = [self currentImeState:Qt::ImAnchorPosition].toInt();
897
898 NSRange r = static_cast<QUITextRange*>(range).range;
899 QList<QInputMethodEvent::Attribute> attrs;
901 {
902 QInputMethodEvent e(m_markedText, attrs);
903 [self sendEventToFocusObject:e];
904 }
906
907 attrs = QList<QInputMethodEvent::Attribute>();
909 {
910 QInputMethodEvent e(m_markedText, attrs);
911 [self sendEventToFocusObject:e];
912 }
914
915 if (cursorPos != int(r.location + r.length) || cursorPos != anchorPos) {
916 attrs = QList<QInputMethodEvent::Attribute>();
917 attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, qMin(cursorPos, anchorPos), qAbs(cursorPos - anchorPos), 0);
918 QInputMethodEvent e(m_markedText, attrs);
919 [self sendEventToFocusObject:e];
920 }
921
922 return startRect.united(endRect).toCGRect();
923}
924
925- (NSArray<UITextSelectionRect *> *)selectionRectsForRange:(UITextRange *)range
926{
928 // This method is supposed to return a rectangle for each line with selection. Since we don't
929 // expose an API in Qt/IM for getting this information, and since we never seems to be getting
930 // a call from UIKit for this, we return an empty array until a need arise.
931 return [[NSArray<UITextSelectionRect *> new] autorelease];
932}
933
934- (CGRect)caretRectForPosition:(UITextPosition *)position
935{
937 // Assume for now that position is always the same as
938 // cursor index until a better API is in place:
939 return QPlatformInputContext::cursorRectangle().toCGRect();
940}
941
942- (void)replaceRange:(UITextRange *)range withText:(NSString *)text
943{
944 [self setSelectedTextRange:range];
945
947 e.setCommitString(QString::fromNSString(text));
948 [self sendEventToFocusObject:e];
949}
950
951- (void)setBaseWritingDirection:(NSWritingDirection)writingDirection forRange:(UITextRange *)range
952{
953 Q_UNUSED(writingDirection);
955 // Writing direction is handled by QLocale
956}
957
958- (NSWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
959{
962 if (QLocale::system().textDirection() == Qt::RightToLeft)
963 return NSWritingDirectionRightToLeft;
964 return NSWritingDirectionLeftToRight;
965}
966
967- (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction
968{
969 int p = static_cast<QUITextPosition *>(position).index;
970 if (direction == UITextLayoutDirectionLeft)
971 return [QUITextRange rangeWithNSRange:NSMakeRange(0, p)];
972 int l = [self currentImeState:Qt::ImSurroundingText].toString().length();
973 return [QUITextRange rangeWithNSRange:NSMakeRange(p, l - p)];
974}
975
976- (UITextPosition *)closestPositionToPoint:(CGPoint)point
977{
978 int textPos = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, QPointF::fromCGPoint(point)).toInt();
979 return [QUITextPosition positionWithIndex:textPos];
980}
981
982- (UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range
983{
984 // No API in Qt for determining this. Use sensible default instead:
985 Q_UNUSED(point);
987 return [QUITextPosition positionWithIndex:[self currentImeState:Qt::ImCursorPosition].toInt()];
988}
989
990- (UITextRange *)characterRangeAtPoint:(CGPoint)point
991{
992 // No API in Qt for determining this. Use sensible default instead:
993 Q_UNUSED(point);
994 return [QUITextRange rangeWithNSRange:NSMakeRange([self currentImeState:Qt::ImCursorPosition].toInt(), 0)];
995}
996
997- (void)setMarkedTextStyle:(NSDictionary *)style
998{
999 Q_UNUSED(style);
1000 // No-one is going to change our style. If UIKit itself did that
1001 // it would be very welcome, since then we knew how to style marked
1002 // text instead of just guessing...
1003}
1004
1005#ifndef Q_OS_TVOS
1006- (NSDictionary *)textStylingAtPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
1007{
1010
1011 QObject *focusObject = QGuiApplication::focusObject();
1012 if (!focusObject)
1013 return @{};
1014
1015 // Assume position is the same as the cursor for now. QInputMethodQueryEvent with Qt::ImFont
1016 // needs to be extended to take an extra position argument before this can be fully correct.
1018 QCoreApplication::sendEvent(focusObject, &e);
1019 QFont qfont = qvariant_cast<QFont>(e.value(Qt::ImFont));
1020 UIFont *uifont = [UIFont fontWithName:qfont.family().toNSString() size:qfont.pointSize()];
1021 if (!uifont)
1022 return @{};
1023 return @{NSFontAttributeName: uifont};
1024}
1025#endif
1026
1027- (NSDictionary *)markedTextStyle
1028{
1029 return [NSDictionary dictionary];
1030}
1031
1032- (BOOL)hasText
1033{
1034 return YES;
1035}
1036
1037- (void)insertText:(NSString *)text
1038{
1039 QObject *focusObject = QGuiApplication::focusObject();
1040 if (!focusObject)
1041 return;
1042
1043 if ([text isEqualToString:@"\n"]) {
1044 [self sendKeyPressRelease:Qt::Key_Return modifiers:Qt::NoModifier];
1045
1046 // An onEnter handler of a TextInput might move to the next input by calling
1047 // nextInput.forceActiveFocus() which changes the focusObject.
1048 // In that case we don't want to hide the VKB.
1049 if (focusObject != QGuiApplication::focusObject()) {
1050 qImDebug() << "focusObject already changed, not resigning first responder.";
1051 return;
1052 }
1053
1054 if (self.returnKeyType == UIReturnKeyDone || self.returnKeyType == UIReturnKeyGo
1055 || self.returnKeyType == UIReturnKeySend || self.returnKeyType == UIReturnKeySearch)
1056 [self resignFirstResponder];
1057
1058 return;
1059 }
1060
1062 e.setCommitString(QString::fromNSString(text));
1063 [self sendEventToFocusObject:e];
1064}
1065
1066- (void)deleteBackward
1067{
1068 // UITextInput selects the text to be deleted before calling this method. To avoid
1069 // drawing the selection, we flush after posting the key press/release.
1070 [self sendKeyPressRelease:Qt::Key_Backspace modifiers:Qt::NoModifier];
1071}
1072
1073@end
static UIResponder * currentCandidate()
Definition qiosglobal.h:56
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
static bool sendEvent(QObject *receiver, QEvent *event)
Sends event event directly to receiver receiver, using the notify() function.
\inmodule QtCore
Definition qcoreevent.h:45
@ KeyRelease
Definition qcoreevent.h:65
@ KeyPress
Definition qcoreevent.h:64
\reentrant
Definition qfont.h:22
static QObject * focusObject()
Returns the QObject in currently active window that will be final receiver of events tied to focus,...
static QWindow * focusWindow()
Returns the QWindow that receives events tied to focus, such as key events.
The QInputMethodEvent class provides parameters for input method events.
Definition qevent.h:625
void setCommitString(const QString &commitString, int replaceFrom=0, int replaceLength=0)
Sets the commit string to commitString.
Definition qevent.cpp:2272
The QInputMethodQueryEvent class provides an event sent by the input context to input objects.
Definition qevent.h:679
The QKeySequence class encapsulates a key sequence as used by shortcuts.
static QLocale system()
Returns a QLocale object initialized to the system locale.
Definition qlocale.cpp:2862
T value(const Key &key, const T &defaultValue=T()) const
Definition qmap.h:357
\inmodule QtCore
Definition qobject.h:103
QVariant property(const char *name) const
Returns the value of the object's name property.
Definition qobject.cpp:4323
static QVariant queryFocusObject(Qt::InputMethodQuery query, QPointF position)
QPlatformInputContext::queryFocusObject.
static QRectF cursorRectangle()
QPlatformInputContext::cursorRectangle.
The QPlatformWindow class provides an abstraction for top-level windows.
QPlatformWindow * parent() const
Returns the parent platform window (or \nullptr if orphan).
virtual WId winId() const
Reimplement in subclasses to return a handle to the native window.
\inmodule QtCore\reentrant
Definition qrect.h:484
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString left(qsizetype n) const &
Definition qstring.h:363
int toInt(bool *ok=nullptr, int base=10) const
Returns the string converted to an int using base base, which is 10 by default and must be between 2 ...
Definition qstring.h:731
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
qsizetype length() const noexcept
Returns the number of characters in this string.
Definition qstring.h:191
void setBackground(const QBrush &brush)
Sets the brush use to paint the document's background to the brush specified.
\inmodule QtCore
Definition qvariant.h:65
QMap< QString, QVariant > toMap() const
Returns the variant as a QVariantMap if the variant has type() \l QMetaType::QVariantMap.
bool toBool() const
Returns the variant as a bool if the variant has userType() Bool.
static bool handleKeyEvent(QWindow *window, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString &text=QString(), bool autorep=false, ushort count=1)
EGLImageKHR int int EGLuint64KHR * modifiers
QString text
direction
instancetype positionWithIndex:(NSUInteger index)
instancetype rangeWithNSRange:(NSRange range)
Definition qcompare.h:63
InputMethodQuery
@ ImPlatformData
@ ImSurroundingText
@ ImCursorPosition
@ ImEnterKeyType
@ ImReadOnly
@ ImFont
@ ImAnchorPosition
@ ImHints
@ ImEnabled
@ ImQueryAll
@ RightToLeft
@ ImhUrlCharactersOnly
@ ImhFormattedNumbersOnly
@ ImhMultiLine
@ ImhLatinOnly
@ ImhUppercaseOnly
@ ImhNoPredictiveText
@ ImhDigitsOnly
@ ImhEmailCharactersOnly
@ ImhHiddenText
@ ImhNoAutoUppercase
@ ImhDialableCharactersOnly
@ ImhPreferNumbers
@ Key_Right
Definition qnamespace.h:679
@ Key_Left
Definition qnamespace.h:677
@ Key_Up
Definition qnamespace.h:678
@ Key_Down
Definition qnamespace.h:680
@ Key_unknown
@ ShiftModifier
@ ControlModifier
@ NoModifier
@ AltModifier
EnterKeyType
@ EnterKeyNext
@ EnterKeySearch
@ EnterKeyGo
@ EnterKeyDone
@ EnterKeyReturn
@ EnterKeySend
QString self
Definition language.cpp:58
static jboolean cut(JNIEnv *, jobject)
static jboolean copy(JNIEnv *, jobject)
static jboolean paste(JNIEnv *, jobject)
static jboolean selectAll(JNIEnv *, jobject)
long NSInteger
unsigned long NSUInteger
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
static struct AttrInfo attrs[]
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qImDebug
Definition qiosglobal.h:20
const char kImePlatformDataInputView[]
const char kImePlatformDataInputAccessoryView[]
const char kImePlatformDataHideShortcutsBar[]
static bool hasSelection()
BOOL m_inSelectionChange
BOOL m_inSendEventToFocusObject
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLuint64 key
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLboolean r
[2]
GLuint GLuint end
GLenum GLuint GLenum GLsizei length
GLenum GLuint id
[7]
GLint GLint GLint GLint GLsizei GLsizei GLsizei GLboolean commit
GLfloat GLfloat f
GLsizei range
GLuint start
GLenum GLuint GLintptr offset
GLdouble s
[6]
Definition qopenglext.h:235
GLboolean reset
GLenum query
GLenum array
GLdouble GLdouble t
Definition qopenglext.h:243
GLfloat GLfloat p
[1]
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define Q_UNUSED(x)
unsigned int uint
Definition qtypes.h:34
const char property[13]
Definition qwizard.cpp:101
QFileSelector selector
[1]
QSharedPointer< T > other(t)
[5]
selection select(topLeft, bottomRight)
aWidget window() -> setWindowTitle("New Window Title")
[2]
stack undo()
QQuickView * view
[0]
QT_BEGIN_NAMESPACE bool toBool(const QString &str)
Definition utils.h:14