Qt 6.x
The Qt SDK
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
4#include "qiosinputcontext.h"
5
6#import <UIKit/UIGestureRecognizerSubclass.h>
7
8#include "qiosglobal.h"
9#include "qiosintegration.h"
10#include "qiosscreen.h"
11#include "qiostextresponder.h"
12#include "qiosviewcontroller.h"
13#include "qioswindow.h"
14#include "quiview.h"
15
16#include <QtCore/private/qcore_mac_p.h>
17
18#include <QGuiApplication>
19#include <QtGui/private/qwindow_p.h>
20
21// -------------------------------------------------------------------------
22
24{
25 return qApp->focusWindow() ?
26 reinterpret_cast<QUIView *>(qApp->focusWindow()->winId()) : 0;
27}
28
29// -------------------------------------------------------------------------
30
31@interface QIOSLocaleListener : NSObject
32@end
33
34@implementation QIOSLocaleListener
35
36- (instancetype)init
37{
38 if (self = [super init]) {
39 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
40 [notificationCenter addObserver:self
41 selector:@selector(localeDidChange:)
42 name:NSCurrentLocaleDidChangeNotification object:nil];
43 }
44
45 return self;
46}
47
48- (void)dealloc
49{
50 [[NSNotificationCenter defaultCenter] removeObserver:self];
51 [super dealloc];
52}
53
54- (void)localeDidChange:(NSNotification *)notification
55{
56 Q_UNUSED(notification);
58}
59
60@end
61
62// -------------------------------------------------------------------------
63
64@interface QIOSKeyboardListener : UIGestureRecognizer <UIGestureRecognizerDelegate>
66@end
67
68@implementation QIOSKeyboardListener {
70}
71
72- (instancetype)initWithQIOSInputContext:(QT_PREPEND_NAMESPACE(QIOSInputContext) *)context
73{
74 if (self = [super initWithTarget:self action:@selector(gestureStateChanged:)]) {
75
76 m_context = context;
77
78 self.hasDeferredScrollToCursor = NO;
79
80 // UIGestureRecognizer
81 self.enabled = NO;
82 self.cancelsTouchesInView = NO;
83 self.delaysTouchesEnded = NO;
84
85#ifndef Q_OS_TVOS
86 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
87
88 [notificationCenter addObserver:self
89 selector:@selector(keyboardWillShow:)
90 name:UIKeyboardWillShowNotification object:nil];
91 [notificationCenter addObserver:self
92 selector:@selector(keyboardWillOrDidChange:)
93 name:UIKeyboardDidShowNotification object:nil];
94 [notificationCenter addObserver:self
95 selector:@selector(keyboardWillHide:)
96 name:UIKeyboardWillHideNotification object:nil];
97 [notificationCenter addObserver:self
98 selector:@selector(keyboardWillOrDidChange:)
99 name:UIKeyboardDidHideNotification object:nil];
100 [notificationCenter addObserver:self
101 selector:@selector(keyboardDidChangeFrame:)
102 name:UIKeyboardDidChangeFrameNotification object:nil];
103#endif
104 }
105
106 return self;
107}
108
109- (void)dealloc
110{
111 [[NSNotificationCenter defaultCenter] removeObserver:self];
112
113 [super dealloc];
114}
115
116// -------------------------------------------------------------------------
117
118- (void)keyboardWillShow:(NSNotification *)notification
119{
120 [self keyboardWillOrDidChange:notification];
121
122 UIResponder *firstResponder = [UIResponder currentFirstResponder];
123 if (![firstResponder isKindOfClass:[QIOSTextInputResponder class]])
124 return;
125
126 // Enable hide-keyboard gesture
127 self.enabled = m_context->isInputPanelVisible();
128
129 m_context->scrollToCursor();
130}
131
132- (void)keyboardWillHide:(NSNotification *)notification
133{
134 [self keyboardWillOrDidChange:notification];
135
136 if (self.state != UIGestureRecognizerStateBegan) {
137 // Only disable the gesture if the hiding of the keyboard was not caused by it.
138 // Otherwise we need to await the final touchEnd callback for doing some clean-up.
139 self.enabled = NO;
140 }
141 m_context->scroll(0);
142}
143
144- (void)keyboardDidChangeFrame:(NSNotification *)notification
145{
146 [self keyboardWillOrDidChange:notification];
147
148 // If the keyboard was visible and docked from before, this is just a geometry
149 // change (normally caused by an orientation change). In that case, update scroll:
150 if (m_context->isInputPanelVisible())
151 m_context->scrollToCursor();
152}
153
154- (void)keyboardWillOrDidChange:(NSNotification *)notification
155{
156 m_context->updateKeyboardState(notification);
157}
158
159// -------------------------------------------------------------------------
160
161- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)other
162{
164 return NO;
165}
166
167- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)other
168{
170 return NO;
171}
172
173- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
174{
175 [super touchesBegan:touches withEvent:event];
176
177 if (!m_context->isInputPanelVisible()) {
178 qImDebug("keyboard was hidden by sliding it down, disabling hide-keyboard gesture");
179 self.enabled = NO;
180 return;
181 }
182
183 if ([touches count] != 1)
184 self.state = UIGestureRecognizerStateFailed;
185}
186
187- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
188{
189 [super touchesMoved:touches withEvent:event];
190
191 if (self.state != UIGestureRecognizerStatePossible)
192 return;
193
194 CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
195 if (CGRectContainsPoint(m_context->keyboardState().keyboardEndRect, touchPoint))
196 self.state = UIGestureRecognizerStateBegan;
197}
198
199- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
200{
201 [super touchesEnded:touches withEvent:event];
202
203 [self touchesEndedOrCancelled];
204}
205
206- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
207{
208 [super touchesCancelled:touches withEvent:event];
209
210 [self touchesEndedOrCancelled];
211}
212
213- (void)touchesEndedOrCancelled
214{
215 // Defer final state change until next runloop iteration, so that Qt
216 // has a chance to process the final touch events first, before we eg.
217 // scroll the view.
218 dispatch_async(dispatch_get_main_queue (), ^{
219 // iOS will transition from began to changed by itself
220 Q_ASSERT(self.state != UIGestureRecognizerStateBegan);
221
222 if (self.state == UIGestureRecognizerStateChanged)
223 self.state = UIGestureRecognizerStateEnded;
224 else
225 self.state = UIGestureRecognizerStateFailed;
226 });
227}
228
229- (void)gestureStateChanged:(id)sender
230{
231 Q_UNUSED(sender);
232
233 if (self.state == UIGestureRecognizerStateBegan) {
234 qImDebug("hide keyboard gesture was triggered");
235 UIResponder *firstResponder = [UIResponder currentFirstResponder];
236 Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]);
237 [firstResponder resignFirstResponder];
238 }
239}
240
241- (void)reset
242{
243 [super reset];
244
245 if (!m_context->isInputPanelVisible()) {
246 qImDebug("keyboard was hidden, disabling hide-keyboard gesture");
247 self.enabled = NO;
248 } else {
249 qImDebug("gesture completed without triggering");
250 if (self.hasDeferredScrollToCursor) {
251 qImDebug("applying deferred scroll to cursor");
252 m_context->scrollToCursor();
253 }
254 }
255
256 self.hasDeferredScrollToCursor = NO;
257}
258
259@end
260
261// -------------------------------------------------------------------------
262
264
265Qt::InputMethodQueries ImeState::update(Qt::InputMethodQueries properties)
266{
267 if (!properties)
268 return {};
269
271
272 // Update the focus object that the new state is based on
273 focusObject = qApp ? qApp->focusObject() : 0;
274
275 if (focusObject)
277
278 Qt::InputMethodQueries updatedProperties;
279 for (uint i = 0; i < (sizeof(Qt::ImQueryAll) * CHAR_BIT); ++i) {
281 if (newState.value(property) != currentState.value(property)) {
282 updatedProperties |= property;
283 currentState.setValue(property, newState.value(property));
284 }
285 }
286 }
287
288 return updatedProperties;
289}
290
291// -------------------------------------------------------------------------
292
294{
295 return static_cast<QIOSInputContext *>(QIOSIntegration::instance()->inputContext());
296}
297
300 , m_localeListener([QIOSLocaleListener new])
301 , m_keyboardHideGesture([[QIOSKeyboardListener alloc] initWithQIOSInputContext:this])
302 , m_textResponder(0)
303{
304 if (isQtApplication()) {
305 QIOSScreen *iosScreen = static_cast<QIOSScreen*>(QGuiApplication::primaryScreen()->handle());
306 [iosScreen->uiWindow() addGestureRecognizer:m_keyboardHideGesture];
307 }
308
310}
311
313{
314 [m_localeListener release];
315 [m_keyboardHideGesture.view removeGestureRecognizer:m_keyboardHideGesture];
316 [m_keyboardHideGesture release];
317
318 [m_textResponder release];
319}
320
322{
323 // No-op, keyboard controlled fully by platform based on focus
324 qImDebug("can't show virtual keyboard without a focus object, ignoring");
325}
326
328{
329 if (![m_textResponder isFirstResponder]) {
330 qImDebug("QIOSTextInputResponder is not first responder, ignoring");
331 return;
332 }
333
334 if (qGuiApp->focusObject() != m_imeState.focusObject) {
335 qImDebug("current focus object does not match IM state, likely hiding from focusOut event, so ignoring");
336 return;
337 }
338
339 qImDebug("hiding VKB as requested by QInputMethod::hide()");
340 [m_textResponder resignFirstResponder];
341}
342
344{
345 if (QWindow *focusWindow = qApp->focusWindow())
346 static_cast<QWindowPrivate *>(QObjectPrivate::get(focusWindow))->clearFocusObject();
347}
348
349// -------------------------------------------------------------------------
350
351void QIOSInputContext::updateKeyboardState(NSNotification *notification)
352{
353#ifdef Q_OS_TVOS
354 Q_UNUSED(notification);
355#else
356 static CGRect currentKeyboardRect = CGRectZero;
357
358 KeyboardState previousState = m_keyboardState;
359
360 if (notification) {
361 NSDictionary *userInfo = [notification userInfo];
362
363 CGRect frameBegin = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
364 CGRect frameEnd = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
365
366 bool atEndOfKeyboardTransition = [notification.name rangeOfString:@"Did"].location != NSNotFound;
367
368 currentKeyboardRect = atEndOfKeyboardTransition ? frameEnd : frameBegin;
369
370 // The isInputPanelVisible() property is based on whether or not the virtual keyboard
371 // is visible on screen, and does not follow the logic of the iOS WillShow and WillHide
372 // notifications which are not emitted for undocked keyboards, and are buggy when dealing
373 // with input-accessory-views. The reason for using frameEnd here (the future state),
374 // instead of the current state reflected in frameBegin, is that QInputMethod::isVisible()
375 // is documented to reflect the future state in the case of animated transitions.
376 m_keyboardState.keyboardVisible = 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)
405 if (m_keyboardState.keyboardAnimating != previousState.keyboardAnimating)
407 if (m_keyboardState.keyboardRect != previousState.keyboardRect)
409#endif
410}
411
413{
414 return m_keyboardState.keyboardVisible;
415}
416
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 (!isQtApplication())
444 return;
445
446 if (m_keyboardHideGesture.state == UIGestureRecognizerStatePossible && m_keyboardHideGesture.numberOfTouches == 1) {
447 // Don't scroll to the cursor if the user is touching the screen and possibly
448 // trying to trigger the hide-keyboard gesture.
449 qImDebug("deferring scrolling to cursor as we're still waiting for a possible gesture");
450 m_keyboardHideGesture.hasDeferredScrollToCursor = YES;
451 return;
452 }
453
454 UIView *rootView = scrollableRootView();
455 if (!rootView)
456 return;
457
458 if (!focusView())
459 return;
460
461 if (rootView.window != focusView().window)
462 return;
463
464 // We only support auto-scroll for docked keyboards for now, so make sure that's the case
465 if (CGRectGetMaxY(m_keyboardState.keyboardEndRect) != CGRectGetMaxY([UIScreen mainScreen].bounds)) {
466 qImDebug("Keyboard not docked, ignoring request to scroll to reveal cursor");
467 return;
468 }
469
470 QPlatformWindow *focusWindow = qApp->focusWindow()->handle();
472 QRect cursorRect = QRect(focusWindow->mapToGlobal(windowCurosorRect.topLeft()), windowCurosorRect.size());
473
474 // We explicitly ask for the geometry of the screen instead of the availableGeometry,
475 // as we hide the status bar when scrolling the screen, so the available geometry will
476 // include the space taken by the status bar at the moment.
477 QRect screenGeometry = focusWindow->screen()->geometry();
478
479 if (!cursorRect.isNull()) {
480 // Add some padding so that the cursor does not end up directly above the keyboard
481 static const int kCursorRectPadding = 20;
482 cursorRect.adjust(0, -kCursorRectPadding, 0, kCursorRectPadding);
483
484 // Make sure the cursor rect is still within the screen geometry after padding
485 cursorRect &= screenGeometry;
486 }
487
488 QRect keyboardGeometry = QRectF::fromCGRect(m_keyboardState.keyboardEndRect).toRect();
489 QRect availableGeometry = (QRegion(screenGeometry) - keyboardGeometry).boundingRect();
490
491 if (!cursorRect.isNull() && !availableGeometry.contains(cursorRect)) {
492 qImDebug() << "cursor rect" << cursorRect << "not fully within" << availableGeometry;
493 int scrollToCenter = -(availableGeometry.center() - cursorRect.center()).y();
494 int scrollToBottom = focusWindow->screen()->geometry().bottom() - availableGeometry.bottom();
495 scroll(qMin(scrollToCenter, scrollToBottom));
496 } else {
497 scroll(0);
498 }
499}
500
502{
503 Q_ASSERT(y >= 0);
504
505 UIView *rootView = scrollableRootView();
506 if (!rootView)
507 return;
508
510 qWarning() << "can't scroll root view in application extension";
511 return;
512 }
513
514 CATransform3D translationTransform = CATransform3DMakeTranslation(0.0, -y, 0.0);
515 if (CATransform3DEqualToTransform(translationTransform, rootView.layer.sublayerTransform))
516 return;
517
518 qImDebug() << "scrolling root view to y =" << -y;
519
521 [UIView animateWithDuration:m_keyboardState.animationDuration delay:0
522 options:(m_keyboardState.animationCurve << 16) | UIViewAnimationOptionBeginFromCurrentState
523 animations:^{
524 // The sublayerTransform property of CALayer is not implicitly animated for a
525 // layer-backed view, even inside a UIView animation block, so we need to set up
526 // an explicit CoreAnimation animation. Since there is no predefined media timing
527 // function that matches the custom keyboard animation curve we cheat by asking
528 // the view for an animation of another property, which will give us an animation
529 // that matches the parameters we passed to [UIView animateWithDuration] above.
530 // The reason we ask for the animation of 'backgroundColor' is that it's a simple
531 // property that will not return a compound animation, like eg. bounds will.
532 NSObject *action = (NSObject*)[rootView actionForLayer:rootView.layer forKey:@"backgroundColor"];
533
534 CABasicAnimation *animation;
535 if ([action isKindOfClass:[CABasicAnimation class]]) {
536 animation = static_cast<CABasicAnimation*>(action);
537 animation.keyPath = @"sublayerTransform"; // Instead of backgroundColor
538 } else {
539 animation = [CABasicAnimation animationWithKeyPath:@"sublayerTransform"];
540 }
541
542 CATransform3D currentSublayerTransform = static_cast<CALayer *>([rootView.layer presentationLayer]).sublayerTransform;
543 animation.fromValue = [NSValue valueWithCATransform3D:currentSublayerTransform];
544 animation.toValue = [NSValue valueWithCATransform3D:translationTransform];
545 [rootView.layer addAnimation:animation forKey:@"AnimateSubLayerTransform"];
546 rootView.layer.sublayerTransform = translationTransform;
547
548 bool keyboardScrollIsActive = y != 0;
549
550 // Raise all known windows to above the status-bar if we're scrolling the screen,
551 // while keeping the relative window level between the windows the same.
552 NSArray<UIWindow *> *applicationWindows = [qt_apple_sharedApplication() windows];
553 static QHash<UIWindow *, UIWindowLevel> originalWindowLevels;
554 for (UIWindow *window in applicationWindows) {
555 if (keyboardScrollIsActive && !originalWindowLevels.contains(window))
556 originalWindowLevels.insert(window, window.windowLevel);
557
558#ifndef Q_OS_TVOS
559 UIWindowLevel windowLevelAdjustment = keyboardScrollIsActive ? UIWindowLevelStatusBar : 0;
560#else
561 UIWindowLevel windowLevelAdjustment = 0;
562#endif
563 window.windowLevel = originalWindowLevels.value(window) + windowLevelAdjustment;
564
565 if (!keyboardScrollIsActive)
566 originalWindowLevels.remove(window);
567 }
568 }
569 completion:^(BOOL){
570 if (self) {
571 // Scrolling the root view results in the keyboard being moved
572 // relative to the focus window, so we need to re-evaluate the
573 // keyboard rectangle.
574 updateKeyboardState();
575 }
576 }
577 ];
578}
579
580// -------------------------------------------------------------------------
581
583{
584 Q_UNUSED(focusObject);
585
586 qImDebug() << "new focus object =" << focusObject;
587
589 && m_keyboardHideGesture.state == UIGestureRecognizerStateChanged) {
590 // A new focus object may be set as part of delivering touch events to
591 // application during the hide-keyboard gesture, but we don't want that
592 // to result in a new object getting focus and bringing the keyboard up
593 // again.
594 qImDebug() << "clearing focus object" << focusObject << "as hide-keyboard gesture is active";
596 return;
597 } else if (focusObject == m_imeState.focusObject) {
598 qImDebug("same focus object as last update, skipping reset");
599 return;
600 }
601
602 reset();
603
606}
607
609{
610 Q_UNUSED(focusWindow);
611
612 qImDebug() << "new focus window =" << focusWindow;
613
614 reset();
615
616 // The keyboard rectangle depend on the focus window, so
617 // we need to re-evaluate the keyboard state.
619
622}
623
630void QIOSInputContext::update(Qt::InputMethodQueries updatedProperties)
631{
632 qImDebug() << "fw =" << qApp->focusWindow() << "fo =" << qApp->focusObject();
633
634 // Changes to the focus object should always result in a call to setFocusObject(),
635 // triggering a reset() which will update all the properties based on the new
636 // focus object. We try to detect code paths that fail this assertion and smooth
637 // over the situation by doing a manual update of the focus object.
638 if (qApp->focusObject() != m_imeState.focusObject && updatedProperties != Qt::ImQueryAll) {
639 qWarning() << "stale focus object" << static_cast<void *>(m_imeState.focusObject)
640 << ", doing manual update";
641 setFocusObject(qApp->focusObject());
642 return;
643 }
644
645 // Mask for properties that we are interested in and see if any of them changed
647
648 // Perform update first, so we can trust the value of inputMethodAccepted()
649 Qt::InputMethodQueries changedProperties = m_imeState.update(updatedProperties);
650
651 const bool inputIsReadOnly = m_imeState.currentState.value(Qt::ImReadOnly).toBool();
652
653 if (inputMethodAccepted() || inputIsReadOnly) {
654 if (!m_textResponder || [m_textResponder needsKeyboardReconfigure:changedProperties]) {
655 [m_textResponder autorelease];
656 if (inputIsReadOnly) {
657 qImDebug("creating new read-only text responder");
658 m_textResponder = [[QIOSTextResponder alloc] initWithInputContext:this];
659 } else {
660 qImDebug("creating new read/write text responder");
661 m_textResponder = [[QIOSTextInputResponder alloc] initWithInputContext:this];
662 }
663 } else {
664 qImDebug("no need to reconfigure keyboard, just notifying input delegate");
665 [m_textResponder notifyInputDelegate:changedProperties];
666 }
667
668 if (![m_textResponder isFirstResponder]) {
669 qImDebug("IM enabled, making text responder first responder");
670 [m_textResponder becomeFirstResponder];
671 }
672
673 if (changedProperties & Qt::ImCursorRectangle)
675 } else if ([m_textResponder isFirstResponder]) {
676 qImDebug("IM not enabled, resigning text responder as first responder");
677 [m_textResponder resignFirstResponder];
678 }
679}
680
682{
683 // The IM enablement state is based on the last call to update()
684 bool lastKnownImEnablementState = m_imeState.currentState.value(Qt::ImEnabled).toBool();
685
686#if !defined(QT_NO_DEBUG)
687 // QPlatformInputContext keeps a cached value of the current IM enablement state that is
688 // updated by QGuiApplication when the current focus object changes, or by QInputMethod's
689 // update() function. If the focus object changes, but the change is not propagated as
690 // a signal to QGuiApplication due to bugs in the widget/graphicsview/qml stack, we'll
691 // end up with a stale value for QPlatformInputContext::inputMethodAccepted(). To be on
692 // the safe side we always use our own cached value to decide if IM is enabled, and try
693 // to detect the case where the two values are out of sync.
694 if (lastKnownImEnablementState != QPlatformInputContext::inputMethodAccepted())
695 qWarning("QPlatformInputContext::inputMethodAccepted() does not match actual focus object IM enablement!");
696#endif
697
698 return lastKnownImEnablementState;
699}
700
705{
706 qImDebug("releasing text responder");
707
708 // UIKit will sometimes, for unknown reasons, unset the input delegate on the
709 // current text responder. This seems to happen as a result of us calling
710 // [self.inputDelegate textDidChange:self] from [m_textResponder reset].
711 // But it won't be set to nil directly, only after a character is typed on
712 // the input panel after the reset. This strange behavior seems to be related
713 // to us overriding [QUIView setInteraction] to ignore UITextInteraction. If we
714 // didn't do that, the delegate would be kept. But not overriding that function
715 // has its own share of issues, so it seems better to keep that way for now.
716 // Instead, we choose to recreate the text responder as a brute-force solution
717 // until we have better knowledge of what is going on (or implement the new
718 // UITextInteraction protocol).
719 const auto oldResponder = m_textResponder;
720 [m_textResponder reset];
721 [m_textResponder autorelease];
722 m_textResponder = nullptr;
723
725
726 // If update() didn't end up creating a new text responder, oldResponder will still be
727 // the first responder. In that case we need to resign it, so that the input panel hides.
728 // (the input panel will apparently not hide if the first responder is only released).
729 if ([oldResponder isFirstResponder]) {
730 qImDebug("IM not enabled, resigning autoreleased text responder as first responder");
731 [oldResponder resignFirstResponder];
732 }
733}
734
744{
745 qImDebug("unmarking text");
746 [m_textResponder commit];
747}
748
750{
751 return QLocale(QString::fromNSString([[NSLocale currentLocale] objectForKey:NSLocaleIdentifier]));
752}
753
static bool sendEvent(QObject *receiver, QEvent *event)
Sends event event directly to receiver receiver, using the notify() function.
QScreen * primaryScreen
the primary (or default) screen of the application.
void focusWindowChanged(QWindow *focusWindow)
This signal is emitted when the focused window changes.
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()
QPlatformInputContext * inputContext() const override
Returns the platforms input context.
The QInputMethodQueryEvent class provides an event sent by the input context to input objects.
Definition qevent.h:678
QVariant value(Qt::InputMethodQuery query) const
Returns value of the property query.
Definition qevent.cpp:2378
static QObjectPrivate * get(QObject *o)
Definition qobject_p.h:153
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
The QPlatformInputContext class abstracts the input method dependent data and composing state.
bool inputMethodAccepted() const
Returns true if current focus object supports input method events.
void emitAnimatingChanged()
Active QPlatformInputContext is responsible for providing animating property to QInputMethod.
void emitInputPanelVisibleChanged()
Active QPlatformInputContext is responsible for providing visible property to QInputMethod.
void emitKeyboardRectChanged()
Active QPlatformInputContext is responsible for providing keyboardRectangle property to QInputMethod.
static QRectF cursorRectangle()
QPlatformInputContext::cursorRectangle.
virtual QRect geometry() const =0
Reimplement in subclass to return the pixel geometry of the screen.
The QPlatformWindow class provides an abstraction for top-level windows.
QPlatformScreen * screen() const override
Returns the platform screen handle corresponding to this platform window, or null if the window is no...
virtual QPoint mapToGlobal(const QPoint &pos) const
Translates the window coordinate pos to global screen coordinates using native methods.
\inmodule QtCore
Definition qpointer.h:18
\inmodule QtCore\reentrant
Definition qrect.h:483
constexpr QRect toRect() const noexcept
Returns a QRect based on the values of this rectangle.
Definition qrect.h:845
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr void adjust(int x1, int y1, int x2, int y2) noexcept
Adds dx1, dy1, dx2 and dy2 respectively to the existing coordinates of the rectangle.
Definition qrect.h:372
constexpr bool isNull() const noexcept
Returns true if the rectangle is a null rectangle, otherwise returns false.
Definition qrect.h:163
constexpr int bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:181
constexpr QPoint topLeft() const noexcept
Returns the position of the rectangle's top-left corner.
Definition qrect.h:220
bool contains(const QRect &r, bool proper=false) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qrect.cpp:851
constexpr QSize size() const noexcept
Returns the size of the rectangle.
Definition qrect.h:241
The QRegion class specifies a clip region for a painter.
Definition qregion.h:27
QPlatformScreen * handle() const
Get the platform screen handle.
Definition qscreen.cpp:83
bool toBool() const
Returns the variant as a bool if the variant has userType() Bool.
\inmodule QtGui
Definition qwindow.h:63
#define this
Definition dialogs.cpp:9
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
Combined button and popup list for selecting options.
InputMethodQuery
@ ImPlatformData
@ ImEnterKeyType
@ ImReadOnly
@ ImCursorRectangle
@ ImHints
@ ImQueryInput
@ ImEnabled
@ ImQueryAll
QTextStream & center(QTextStream &stream)
Calls QTextStream::setFieldAlignment(QTextStream::AlignCenter) on stream and returns stream.
QString self
Definition language.cpp:57
static void * context
bool qt_apple_isApplicationExtension()
Definition qcore_mac.mm:423
#define qApp
static const QCssKnownValue properties[NumProperties - 1]
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
#define qGuiApp
bool isQtApplication()
Definition qiosglobal.mm:17
#define qImDebug
Definition qiosglobal.h:19
static QUIView * focusView()
#define qWarning
Definition qlogging.h:162
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLenum GLuint id
[7]
GLenum GLenum GLsizei count
GLint GLint bottom
GLint y
struct _cl_event * event
GLboolean reset
static const QRectF boundingRect(const QPointF *points, int pointCount)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define qPrintable(string)
Definition qstring.h:1391
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define Q_UNUSED(x)
unsigned int uint
Definition qtypes.h:29
QT_END_NAMESPACE typedef QT_PREPEND_NAMESPACE(quintptr) WId
const char property[13]
Definition qwizard.cpp:101
QSharedPointer< T > other(t)
[5]
aWidget window() -> setWindowTitle("New Window Title")
[2]
Qt::InputMethodQueries update(Qt::InputMethodQueries properties)
QInputMethodQueryEvent currentState
QObject * focusObject
NSTimeInterval animationDuration
UIViewAnimationCurve animationCurve