Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qiosinputcontext.mm
Go to the documentation of this file.
1// Copyright (C) 2020 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// Qt-Security score:significant reason:default
4
6
7#import <UIKit/UIGestureRecognizerSubclass.h>
8
9#include "qiosglobal.h"
11#include "qiosscreen.h"
14#include "qioswindow.h"
15#include "quiview.h"
16
17#include <QtCore/private/qcore_mac_p.h>
18
19#include <QGuiApplication>
20#include <QtGui/private/qwindow_p.h>
21
22#include <QtCore/qpointer.h>
23
24// -------------------------------------------------------------------------
25
26static QUIView *focusView()
27{
28 return qApp->focusWindow() ?
29 reinterpret_cast<QUIView *>(qApp->focusWindow()->winId()) : 0;
30}
31
32// -------------------------------------------------------------------------
33
34@interface QIOSLocaleListener : NSObject
35@end
36
37@implementation QIOSLocaleListener
38
39- (instancetype)init
40{
41 if (self = [super init]) {
42 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
43 [notificationCenter addObserver:self
44 selector:@selector(localeDidChange:)
45 name:NSCurrentLocaleDidChangeNotification object:nil];
46 }
47
48 return self;
49}
50
51- (void)dealloc
52{
53 [[NSNotificationCenter defaultCenter] removeObserver:self];
54 [super dealloc];
55}
56
57- (void)localeDidChange:(NSNotification *)notification
58{
59 Q_UNUSED(notification);
60 QIOSInputContext::instance()->emitLocaleChanged();
61}
62
63@end
64
65// -------------------------------------------------------------------------
66
67@interface QIOSKeyboardListener : UIGestureRecognizer <UIGestureRecognizerDelegate>
68@property BOOL hasDeferredScrollToCursor;
69@end
70
71@implementation QIOSKeyboardListener {
72 QT_PREPEND_NAMESPACE(QIOSInputContext) *m_context;
73}
74
75- (instancetype)initWithQIOSInputContext:(QT_PREPEND_NAMESPACE(QIOSInputContext) *)context
76{
77 if (self = [super initWithTarget:self action:@selector(gestureStateChanged:)]) {
78
79 m_context = context;
80
81 self.hasDeferredScrollToCursor = NO;
82
83 // UIGestureRecognizer
84 self.enabled = NO;
85 self.cancelsTouchesInView = NO;
86 self.delaysTouchesEnded = NO;
87
88#ifndef Q_OS_TVOS
89 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
90
91 [notificationCenter addObserver:self
92 selector:@selector(keyboardWillShow:)
93 name:UIKeyboardWillShowNotification object:nil];
94 [notificationCenter addObserver:self
95 selector:@selector(keyboardWillOrDidChange:)
96 name:UIKeyboardDidShowNotification object:nil];
97 [notificationCenter addObserver:self
98 selector:@selector(keyboardWillHide:)
99 name:UIKeyboardWillHideNotification object:nil];
100 [notificationCenter addObserver:self
101 selector:@selector(keyboardWillOrDidChange:)
102 name:UIKeyboardDidHideNotification object:nil];
103 [notificationCenter addObserver:self
104 selector:@selector(keyboardDidChangeFrame:)
105 name:UIKeyboardDidChangeFrameNotification object:nil];
106#endif
107 }
108
109 return self;
110}
111
112- (void)dealloc
113{
114 [[NSNotificationCenter defaultCenter] removeObserver:self];
115
116 [super dealloc];
117}
118
119// -------------------------------------------------------------------------
120
121- (void)keyboardWillShow:(NSNotification *)notification
122{
123 [self keyboardWillOrDidChange:notification];
124
125 UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
126 if (![firstResponder isKindOfClass:[QIOSTextInputResponder class]])
127 return;
128
129 // Enable hide-keyboard gesture
130 self.enabled = m_context->isInputPanelVisible();
131
132 m_context->scrollToCursor();
133}
134
135- (void)keyboardWillHide:(NSNotification *)notification
136{
137 [self keyboardWillOrDidChange:notification];
138
139 if (self.state != UIGestureRecognizerStateBegan) {
140 // Only disable the gesture if the hiding of the keyboard was not caused by it.
141 // Otherwise we need to await the final touchEnd callback for doing some clean-up.
142 self.enabled = NO;
143 }
144 m_context->scroll(0);
145}
146
147- (void)keyboardDidChangeFrame:(NSNotification *)notification
148{
149 [self keyboardWillOrDidChange:notification];
150
151 // If the keyboard was visible and docked from before, this is just a geometry
152 // change (normally caused by an orientation change). In that case, update scroll:
153 if (m_context->isInputPanelVisible())
154 m_context->scrollToCursor();
155}
156
157- (void)keyboardWillOrDidChange:(NSNotification *)notification
158{
159 m_context->updateKeyboardState(notification);
160}
161
162// -------------------------------------------------------------------------
163
164- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)other
165{
166 Q_UNUSED(other);
167 return NO;
168}
169
170- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)other
171{
172 Q_UNUSED(other);
173 return NO;
174}
175
176- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
177{
178 [super touchesBegan:touches withEvent:event];
179
180 if (!m_context->isInputPanelVisible()) {
181 qImDebug("keyboard was hidden by sliding it down, disabling hide-keyboard gesture");
182 self.enabled = NO;
183 return;
184 }
185
186 if ([touches count] != 1)
187 self.state = UIGestureRecognizerStateFailed;
188}
189
190- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
191{
192 [super touchesMoved:touches withEvent:event];
193
194 if (self.state != UIGestureRecognizerStatePossible)
195 return;
196
197 CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
198 if (CGRectContainsPoint(m_context->keyboardState().keyboardEndRect, touchPoint))
199 self.state = UIGestureRecognizerStateBegan;
200}
201
202- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
203{
204 [super touchesEnded:touches withEvent:event];
205
206 [self touchesEndedOrCancelled];
207}
208
209- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
210{
211 [super touchesCancelled:touches withEvent:event];
212
213 [self touchesEndedOrCancelled];
214}
215
216- (void)touchesEndedOrCancelled
217{
218 // Defer final state change until next runloop iteration, so that Qt
219 // has a chance to process the final touch events first, before we eg.
220 // scroll the view.
221 dispatch_async(dispatch_get_main_queue (), ^{
222 // iOS will transition from began to changed by itself
223 Q_ASSERT(self.state != UIGestureRecognizerStateBegan);
224
225 if (self.state == UIGestureRecognizerStateChanged)
226 self.state = UIGestureRecognizerStateEnded;
227 else
228 self.state = UIGestureRecognizerStateFailed;
229 });
230}
231
232- (void)gestureStateChanged:(id)sender
233{
234 Q_UNUSED(sender);
235
236 if (self.state == UIGestureRecognizerStateBegan) {
237 qImDebug("hide keyboard gesture was triggered");
238 UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
239 Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]);
240 [firstResponder resignFirstResponder];
241 }
242}
243
244- (void)reset
245{
246 [super reset];
247
248 if (!m_context->isInputPanelVisible()) {
249 qImDebug("keyboard was hidden, disabling hide-keyboard gesture");
250 self.enabled = NO;
251 } else {
252 qImDebug("gesture completed without triggering");
253 if (self.hasDeferredScrollToCursor) {
254 qImDebug("applying deferred scroll to cursor");
255 m_context->scrollToCursor();
256 }
257 }
258
259 self.hasDeferredScrollToCursor = NO;
260}
261
262@end
263
264// -------------------------------------------------------------------------
265
266QT_BEGIN_NAMESPACE
267
268Qt::InputMethodQueries ImeState::update(Qt::InputMethodQueries properties)
269{
270 if (!properties)
271 return {};
272
273 QInputMethodQueryEvent newState(properties);
274
275 // Update the focus object that the new state is based on
276 focusObject = qApp ? qApp->focusObject() : 0;
277
278 if (focusObject)
279 QCoreApplication::sendEvent(focusObject, &newState);
280
281 Qt::InputMethodQueries updatedProperties;
282 for (uint i = 0; i < (sizeof(Qt::ImQueryAll) * CHAR_BIT); ++i) {
283 if (Qt::InputMethodQuery property = Qt::InputMethodQuery(int(properties & (1 << i)))) {
284 if (newState.value(property) != currentState.value(property)) {
285 updatedProperties |= property;
286 currentState.setValue(property, newState.value(property));
287 }
288 }
289 }
290
291 return updatedProperties;
292}
293
294// -------------------------------------------------------------------------
295
297{
298 return static_cast<QIOSInputContext *>(QIOSIntegration::instance()->inputContext());
299}
300
302 : QPlatformInputContext()
303 , m_localeListener([QIOSLocaleListener new])
304 , m_keyboardHideGesture([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this])
305 , m_textResponder(0)
306{
307 Q_ASSERT(!qGuiApp->focusWindow());
308 connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &QIOSInputContext::focusWindowChanged);
309}
310
312{
313 [m_localeListener release];
314 [m_keyboardHideGesture.view removeGestureRecognizer:m_keyboardHideGesture];
315 [m_keyboardHideGesture release];
316
317 [m_textResponder release];
318}
319
321{
322 // No-op, keyboard controlled fully by platform based on focus
323 qImDebug("can't show virtual keyboard without a focus object, ignoring");
324}
325
327{
328 if (![m_textResponder isFirstResponder]) {
329 qImDebug("QIOSTextInputResponder is not first responder, ignoring");
330 return;
331 }
332
333 if (qGuiApp->focusObject() != m_imeState.focusObject) {
334 qImDebug("current focus object does not match IM state, likely hiding from focusOut event, so ignoring");
335 return;
336 }
337
338 qImDebug("hiding VKB as requested by QInputMethod::hide()");
339 [m_textResponder resignFirstResponder];
340}
341
343{
344 if (QWindow *focusWindow = qApp->focusWindow())
345 static_cast<QWindowPrivate *>(QObjectPrivate::get(focusWindow))->clearFocusObject();
346}
347
348// -------------------------------------------------------------------------
349
350void QIOSInputContext::updateKeyboardState(NSNotification *notification)
351{
352#if defined(Q_OS_TVOS) || defined(Q_OS_VISIONOS)
353 Q_UNUSED(notification);
354#else
355 static CGRect currentKeyboardRect = CGRectZero;
356
357 KeyboardState previousState = m_keyboardState;
358
359 if (notification) {
360 NSDictionary *userInfo = [notification userInfo];
361
362 CGRect frameBegin = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
363 CGRect frameEnd = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
364
365 bool atEndOfKeyboardTransition = [notification.name rangeOfString:@"Did"].location != NSNotFound;
366
367 currentKeyboardRect = atEndOfKeyboardTransition ? frameEnd : frameBegin;
368
369 // The isInputPanelVisible() property is based on whether or not the virtual keyboard
370 // is visible on screen, and does not follow the logic of the iOS WillShow and WillHide
371 // notifications which are not emitted for undocked keyboards, and are buggy when dealing
372 // with input-accessory-views. The reason for using frameEnd here (the future state),
373 // instead of the current state reflected in frameBegin, is that QInputMethod::isVisible()
374 // is documented to reflect the future state in the case of animated transitions.
375 m_keyboardState.keyboardVisible = !CGRectIsEmpty(UIScreen.mainScreen.bounds) &&
376 !CGRectIsEmpty(frameEnd) && CGRectIntersectsRect(frameEnd, UIScreen.mainScreen.bounds);
377
378 // Used for auto-scroller, and will be used for animation-signal in the future
379 m_keyboardState.keyboardEndRect = frameEnd;
380
381 if (m_keyboardState.animationCurve < 0) {
382 // We only set the animation curve the first time it has a valid value, since iOS will sometimes report
383 // an invalid animation curve even if the keyboard is animating, and we don't want to overwrite the
384 // curve in that case.
385 m_keyboardState.animationCurve = UIViewAnimationCurve([[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]);
386 }
387
388 m_keyboardState.animationDuration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
389 m_keyboardState.keyboardAnimating = m_keyboardState.animationDuration > 0 && !atEndOfKeyboardTransition;
390
391 qImDebug() << qPrintable(QString::fromNSString(notification.name)) << "from" << QRectF::fromCGRect(frameBegin) << "to" << QRectF::fromCGRect(frameEnd)
392 << "(curve =" << m_keyboardState.animationCurve << "duration =" << m_keyboardState.animationDuration << "s)";
393 } else {
394 qImDebug("No notification to update keyboard state based on, just updating keyboard rect");
395 }
396
397 if (!focusView() || CGRectIsEmpty(currentKeyboardRect))
398 m_keyboardState.keyboardRect = QRectF();
399 else // QInputmethod::keyboardRectangle() is documented to be in window coordinates.
400 m_keyboardState.keyboardRect = QRectF::fromCGRect([focusView() convertRect:currentKeyboardRect fromView:nil]);
401
402 // Emit for all changed properties
403 if (m_keyboardState.keyboardVisible != previousState.keyboardVisible)
404 emitInputPanelVisibleChanged();
405 if (m_keyboardState.keyboardAnimating != previousState.keyboardAnimating)
406 emitAnimatingChanged();
407 if (m_keyboardState.keyboardRect != previousState.keyboardRect)
408 emitKeyboardRectChanged();
409#endif
410}
411
413{
414 return m_keyboardState.keyboardVisible;
415}
416
417bool QIOSInputContext::isAnimating() const
418{
419 return m_keyboardState.keyboardAnimating;
420}
421
423{
424 return m_keyboardState.keyboardRect;
425}
426
427// -------------------------------------------------------------------------
428
429UIView *QIOSInputContext::scrollableRootView()
430{
431 if (!m_keyboardHideGesture.view)
432 return 0;
433
434 UIWindow *window = static_cast<UIWindow*>(m_keyboardHideGesture.view);
435 if (![window.rootViewController isKindOfClass:[QIOSViewController class]])
436 return 0;
437
438 return window.rootViewController.view;
439}
440
442{
443#if !defined(Q_OS_VISIONOS)
445 return;
446
447 if (m_keyboardHideGesture.state == UIGestureRecognizerStatePossible && m_keyboardHideGesture.numberOfTouches == 1) {
448 // Don't scroll to the cursor if the user is touching the screen and possibly
449 // trying to trigger the hide-keyboard gesture.
450 qImDebug("deferring scrolling to cursor as we're still waiting for a possible gesture");
451 m_keyboardHideGesture.hasDeferredScrollToCursor = YES;
452 return;
453 }
454
455 UIView *rootView = scrollableRootView();
456 if (!rootView)
457 return;
458
459 if (!focusView())
460 return;
461
462 if (rootView.window != focusView().window)
463 return;
464
465 // We only support auto-scroll for docked keyboards for now, so make sure that's the case
466 if (CGRectGetMaxY(m_keyboardState.keyboardEndRect) != CGRectGetMaxY([UIScreen mainScreen].bounds)) {
467 qImDebug("Keyboard not docked, ignoring request to scroll to reveal cursor");
468 return;
469 }
470
471 QPlatformWindow *focusWindow = qApp->focusWindow()->handle();
472 QRect windowCurosorRect = QPlatformInputContext::cursorRectangle().toRect();
473 QRect cursorRect = QRect(focusWindow->mapToGlobal(windowCurosorRect.topLeft()), windowCurosorRect.size());
474
475 // We explicitly ask for the geometry of the screen instead of the availableGeometry,
476 // as we hide the status bar when scrolling the screen, so the available geometry will
477 // include the space taken by the status bar at the moment.
478 QRect screenGeometry = focusWindow->screen()->geometry();
479
480 if (!cursorRect.isNull()) {
481 // Add some padding so that the cursor does not end up directly above the keyboard
482 static const int kCursorRectPadding = 20;
483 cursorRect.adjust(0, -kCursorRectPadding, 0, kCursorRectPadding);
484
485 // Make sure the cursor rect is still within the screen geometry after padding
486 cursorRect &= screenGeometry;
487 }
488
489 QRect keyboardGeometry = QRectF::fromCGRect(m_keyboardState.keyboardEndRect).toRect();
490 QRect availableGeometry = (QRegion(screenGeometry) - keyboardGeometry).boundingRect();
491
492 if (!cursorRect.isNull() && !availableGeometry.contains(cursorRect)) {
493 qImDebug() << "cursor rect" << cursorRect << "not fully within" << availableGeometry;
494 int scrollToCenter = -(availableGeometry.center() - cursorRect.center()).y();
495 int scrollToBottom = focusWindow->screen()->geometry().bottom() - availableGeometry.bottom();
496 scroll(qMin(scrollToCenter, scrollToBottom));
497 } else {
498 scroll(0);
499 }
500#endif
501}
502
503void QIOSInputContext::scroll(int y)
504{
505 Q_ASSERT(y >= 0);
506
507 UIView *rootView = scrollableRootView();
508 if (!rootView)
509 return;
510
511 if (qt_apple_isApplicationExtension()) {
512 qWarning() << "can't scroll root view in application extension";
513 return;
514 }
515
516 CATransform3D translationTransform = CATransform3DMakeTranslation(0.0, -y, 0.0);
517 if (CATransform3DEqualToTransform(translationTransform, rootView.layer.sublayerTransform))
518 return;
519
520 qImDebug() << "scrolling root view to y =" << -y;
521
522 QPointer<QIOSInputContext> self = this;
523 [UIView animateWithDuration:m_keyboardState.animationDuration delay:0
524 options:(m_keyboardState.animationCurve << 16) | UIViewAnimationOptionBeginFromCurrentState
525 animations:^{
526 // The sublayerTransform property of CALayer is not implicitly animated for a
527 // layer-backed view, even inside a UIView animation block, so we need to set up
528 // an explicit CoreAnimation animation. Since there is no predefined media timing
529 // function that matches the custom keyboard animation curve we cheat by asking
530 // the view for an animation of another property, which will give us an animation
531 // that matches the parameters we passed to [UIView animateWithDuration] above.
532 // The reason we ask for the animation of 'backgroundColor' is that it's a simple
533 // property that will not return a compound animation, like eg. bounds will.
534 NSObject *action = (NSObject*)[rootView actionForLayer:rootView.layer forKey:@"backgroundColor"];
535
536 CABasicAnimation *animation;
537 if ([action isKindOfClass:[CABasicAnimation class]]) {
538 animation = static_cast<CABasicAnimation*>(action);
539 animation.keyPath = @"sublayerTransform"; // Instead of backgroundColor
540 } else {
541 animation = [CABasicAnimation animationWithKeyPath:@"sublayerTransform"];
542 }
543
544 CATransform3D currentSublayerTransform = static_cast<CALayer *>([rootView.layer presentationLayer]).sublayerTransform;
545 animation.fromValue = [NSValue valueWithCATransform3D:currentSublayerTransform];
546 animation.toValue = [NSValue valueWithCATransform3D:translationTransform];
547 [rootView.layer addAnimation:animation forKey:@"AnimateSubLayerTransform"];
548 rootView.layer.sublayerTransform = translationTransform;
549
550 bool keyboardScrollIsActive = y != 0;
551
552 // Raise all known windows to above the status-bar if we're scrolling the screen,
553 // while keeping the relative window level between the windows the same.
554 NSArray<UIWindow *> *applicationWindows = [qt_apple_sharedApplication() windows];
555 static QHash<UIWindow *, UIWindowLevel> originalWindowLevels;
556 for (UIWindow *window in applicationWindows) {
557 if (keyboardScrollIsActive && !originalWindowLevels.contains(window))
558 originalWindowLevels.insert(window, window.windowLevel);
559
560#ifndef Q_OS_TVOS
561 UIWindowLevel windowLevelAdjustment = keyboardScrollIsActive ? UIWindowLevelStatusBar : 0;
562#else
563 UIWindowLevel windowLevelAdjustment = 0;
564#endif
565 window.windowLevel = originalWindowLevels.value(window) + windowLevelAdjustment;
566
567 if (!keyboardScrollIsActive)
568 originalWindowLevels.remove(window);
569 }
570 }
571 completion:^(BOOL){
572 if (self) {
573 // Scrolling the root view results in the keyboard being moved
574 // relative to the focus window, so we need to re-evaluate the
575 // keyboard rectangle.
576 updateKeyboardState();
577 }
578 }
579 ];
580}
581
582// -------------------------------------------------------------------------
583
584void QIOSInputContext::setFocusObject(QObject *focusObject)
585{
586 Q_UNUSED(focusObject);
587
588 qImDebug() << "new focus object =" << focusObject;
589
590 if (QPlatformInputContext::inputMethodAccepted()
591 && m_keyboardHideGesture.state == UIGestureRecognizerStateChanged) {
592 // A new focus object may be set as part of delivering touch events to
593 // application during the hide-keyboard gesture, but we don't want that
594 // to result in a new object getting focus and bringing the keyboard up
595 // again.
596 qImDebug() << "clearing focus object" << focusObject << "as hide-keyboard gesture is active";
598 return;
599 } else if (focusObject == m_imeState.focusObject) {
600 qImDebug("same focus object as last update, skipping reset");
601 return;
602 }
603
604 reset();
605
608}
609
610void QIOSInputContext::focusWindowChanged(QWindow *focusWindow)
611{
612 qImDebug() << "new focus window =" << focusWindow;
613
614 reset();
615
616 if (isQtApplication()) {
617 [m_keyboardHideGesture.view removeGestureRecognizer:m_keyboardHideGesture];
618 [focusView().window addGestureRecognizer:m_keyboardHideGesture];
619 }
620
621 // The keyboard rectangle depend on the focus window, so
622 // we need to re-evaluate the keyboard state.
623 updateKeyboardState();
624
627}
628
629/*!
630 Called by the input item to inform the platform input methods when there has been
631 state changes in editor's input method query attributes. When calling the function
632 \a queries parameter has to be used to tell what has changes, which input method
633 can use to make queries for attributes it's interested with QInputMethodQueryEvent.
634*/
635void QIOSInputContext::update(Qt::InputMethodQueries updatedProperties)
636{
637 qImDebug() << "fw =" << qApp->focusWindow() << "fo =" << qApp->focusObject();
638
639 // Changes to the focus object should always result in a call to setFocusObject(),
640 // triggering a reset() which will update all the properties based on the new
641 // focus object. We try to detect code paths that fail this assertion and smooth
642 // over the situation by doing a manual update of the focus object.
643 if (qApp->focusObject() != m_imeState.focusObject && updatedProperties != Qt::ImQueryAll) {
644 qCWarning(lcQpaInputMethods).verbosity(0) << "Updating input context" << updatedProperties
645 << "with last reported focus object" << m_imeState.focusObject
646 << "but qGuiApp reports" << qApp->focusObject()
647 << "which means someone failed to call QPlatformInputContext::setFocusObject()";
648 setFocusObject(qApp->focusObject());
649 return;
650 }
651
652 // Mask for properties that we are interested in and see if any of them changed
653 updatedProperties &= (Qt::ImEnabled | Qt::ImHints | Qt::ImQueryInput | Qt::ImEnterKeyType | Qt::ImPlatformData | Qt::ImReadOnly);
654
655 // Perform update first, so we can trust the value of inputMethodAccepted()
656 Qt::InputMethodQueries changedProperties = m_imeState.update(updatedProperties);
657
658 const bool inputIsReadOnly = m_imeState.currentState.value(Qt::ImReadOnly).toBool();
659
660 if (inputMethodAccepted() || inputIsReadOnly) {
661 if (!m_textResponder || [m_textResponder needsKeyboardReconfigure:changedProperties]) {
662 [m_textResponder autorelease];
663 if (inputIsReadOnly) {
664 qImDebug("creating new read-only text responder");
665 m_textResponder = [[QIOSTextResponder alloc] initWithInputContext:this];
666 } else {
667 qImDebug("creating new read/write text responder");
668 m_textResponder = [[QIOSTextInputResponder alloc] initWithInputContext:this];
669 }
670 } else {
671 qImDebug("no need to reconfigure keyboard, just notifying input delegate");
672 [m_textResponder notifyInputDelegate:changedProperties];
673 }
674
675 if (![m_textResponder isFirstResponder]) {
676 qImDebug("IM enabled, making text responder first responder");
677 [m_textResponder becomeFirstResponder];
678 }
679
680 if (changedProperties & Qt::ImCursorRectangle)
681 scrollToCursor();
682 } else if ([m_textResponder isFirstResponder]) {
683 qImDebug("IM not enabled, resigning text responder as first responder");
684 [m_textResponder resignFirstResponder];
685 }
686}
687
689{
690 // The IM enablement state is based on the last call to update()
691 bool lastKnownImEnablementState = m_imeState.currentState.value(Qt::ImEnabled).toBool();
692
693#if !defined(QT_NO_DEBUG)
694 // QPlatformInputContext keeps a cached value of the current IM enablement state that is
695 // updated by QGuiApplication when the current focus object changes, or by QInputMethod's
696 // update() function. If the focus object changes, but the change is not propagated as
697 // a signal to QGuiApplication due to bugs in the widget/graphicsview/qml stack, we'll
698 // end up with a stale value for QPlatformInputContext::inputMethodAccepted(). To be on
699 // the safe side we always use our own cached value to decide if IM is enabled, and try
700 // to detect the case where the two values are out of sync.
701 if (lastKnownImEnablementState != QPlatformInputContext::inputMethodAccepted())
702 qWarning("QPlatformInputContext::inputMethodAccepted() does not match actual focus object IM enablement!");
703#endif
704
705 return lastKnownImEnablementState;
706}
707
708/*!
709 Called by the input item to reset the input method state.
710*/
712{
713 qImDebug("releasing text responder");
714
715 // UIKit will sometimes, for unknown reasons, unset the input delegate on the
716 // current text responder. This seems to happen as a result of us calling
717 // [self.inputDelegate textDidChange:self] from [m_textResponder reset].
718 // But it won't be set to nil directly, only after a character is typed on
719 // the input panel after the reset. This strange behavior seems to be related
720 // to us overriding [QUIView setInteraction] to ignore UITextInteraction. If we
721 // didn't do that, the delegate would be kept. But not overriding that function
722 // has its own share of issues, so it seems better to keep that way for now.
723 // Instead, we choose to recreate the text responder as a brute-force solution
724 // until we have better knowledge of what is going on (or implement the new
725 // UITextInteraction protocol).
726 const auto oldResponder = m_textResponder;
727 [m_textResponder reset];
728 [m_textResponder autorelease];
729 m_textResponder = nullptr;
730
731 update(Qt::ImQueryAll);
732
733 // If update() didn't end up creating a new text responder, oldResponder will still be
734 // the first responder. In that case we need to resign it, so that the input panel hides.
735 // (the input panel will apparently not hide if the first responder is only released).
736 if ([oldResponder isFirstResponder]) {
737 qImDebug("IM not enabled, resigning autoreleased text responder as first responder");
738 [oldResponder resignFirstResponder];
739 }
740}
741
742/*!
743 Commits the word user is currently composing to the editor. The function is
744 mostly needed by the input methods with text prediction features and by the
745 methods where the script used for typing characters is different from the
746 script that actually gets appended to the editor. Any kind of action that
747 interrupts the text composing needs to flush the composing state by calling the
748 commit() function, for example when the cursor is moved elsewhere.
749*/
751{
752 qImDebug("unmarking text");
753 [m_textResponder commit];
754}
755
757{
758 return QLocale(QString::fromNSString([[NSLocale currentLocale] objectForKey:NSLocaleIdentifier]));
759}
760
761QT_END_NAMESPACE
void setFocusObject(QObject *object) override
This virtual method gets called to notify updated focus to object.
void clearCurrentFocusObject()
QRectF keyboardRect() const override
This function can be reimplemented to return virtual keyboard rectangle in currently active window co...
void updateKeyboardState(NSNotification *notification=nullptr)
void scroll(int y)
static QIOSInputContext * instance()
QLocale locale() const override
void reset() override
Method to be called when input method needs to be reset.
void commit() override
bool isAnimating() const override
This function can be reimplemented to return true whenever input method is animating shown or hidden.
void showInputPanel() override
Request to show input panel.
void focusWindowChanged(QWindow *focusWindow)
void hideInputPanel() override
Request to hide input panel.
bool isInputPanelVisible() const override
Returns input panel visibility status.
bool inputMethodAccepted() const
void update(Qt::InputMethodQueries) override
Notification on editor updates.
static QIOSIntegration * instance()
bool isQtApplication()
Definition qiosglobal.mm:21
#define qImDebug
Definition qiosglobal.h:21
#define qPrintable(string)
Definition qstring.h:1685
connect(manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished)