diff options
Diffstat (limited to 'src/plugins/platforms/ios')
-rw-r--r-- | src/plugins/platforms/ios/qiosdocumentpickercontroller.h | 4 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosdocumentpickercontroller.mm | 15 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosinputcontext.mm | 32 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosmessagedialog.mm | 20 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosscreen.mm | 2 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiostextresponder.mm | 23 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiosviewcontroller.mm | 14 | ||||
-rw-r--r-- | src/plugins/platforms/ios/quiaccessibilityelement.mm | 24 | ||||
-rw-r--r-- | src/plugins/platforms/ios/quiview.mm | 12 | ||||
-rw-r--r-- | src/plugins/platforms/ios/quiview_accessibility.mm | 9 |
10 files changed, 133 insertions, 22 deletions
diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.h b/src/plugins/platforms/ios/qiosdocumentpickercontroller.h index dba6f24fc5..2fe3c9e382 100644 --- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.h +++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.h @@ -41,6 +41,8 @@ #include "qiosfiledialog.h" -@interface QIOSDocumentPickerController : UIDocumentPickerViewController <UIDocumentPickerDelegate, UINavigationControllerDelegate> +@interface QIOSDocumentPickerController : UIDocumentPickerViewController <UIDocumentPickerDelegate, + UINavigationControllerDelegate, + UIAdaptivePresentationControllerDelegate> - (instancetype)initWithQIOSFileDialog:(QIOSFileDialog *)fileDialog; @end diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm index c1b641e839..da78d41115 100644 --- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm +++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm @@ -72,6 +72,7 @@ m_fileDialog = fileDialog; self.modalPresentationStyle = UIModalPresentationFormSheet; self.delegate = self; + self.presentationController.delegate = self; if (m_fileDialog->options()->fileMode() == QFileDialogOptions::ExistingFiles) self.allowsMultipleSelection = YES; @@ -100,4 +101,18 @@ emit m_fileDialog->reject(); } +- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController +{ + Q_UNUSED(presentationController); + + // "Called on the delegate when the user has taken action to dismiss the + // presentation successfully, after all animations are finished. + // This is not called if the presentation is dismissed programatically." + + // So if document picker's view was dismissed, for example by swiping it away, + // we got this method called. But not if the dialog was cancelled or a file + // was selected. + emit m_fileDialog->reject(); +} + @end diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm index 985eecdb1d..ac75367d7f 100644 --- a/src/plugins/platforms/ios/qiosinputcontext.mm +++ b/src/plugins/platforms/ios/qiosinputcontext.mm @@ -160,7 +160,7 @@ static QUIView *focusView() return; // Enable hide-keyboard gesture - self.enabled = YES; + self.enabled = m_context->isInputPanelVisible(); m_context->scrollToCursor(); } @@ -402,7 +402,7 @@ void QIOSInputContext::updateKeyboardState(NSNotification *notification) // The isInputPanelVisible() property is based on whether or not the virtual keyboard // is visible on screen, and does not follow the logic of the iOS WillShow and WillHide // notifications which are not emitted for undocked keyboards, and are buggy when dealing - // with input-accesosory-views. The reason for using frameEnd here (the future state), + // with input-accessory-views. The reason for using frameEnd here (the future state), // instead of the current state reflected in frameBegin, is that QInputMethod::isVisible() // is documented to reflect the future state in the case of animated transitions. m_keyboardState.keyboardVisible = CGRectIntersectsRect(frameEnd, [UIScreen mainScreen].bounds); @@ -727,12 +727,34 @@ bool QIOSInputContext::inputMethodAccepted() const */ void QIOSInputContext::reset() { - qImDebug("updating Qt::ImQueryAll and unmarking text"); + qImDebug("releasing text responder"); + + // UIKit will sometimes, for unknown reasons, unset the input delegate on the + // current text responder. This seems to happen as a result of us calling + // [self.inputDelegate textDidChange:self] from [m_textResponder reset]. + // But it won't be set to nil directly, only after a character is typed on + // the input panel after the reset. This strange behavior seems to be related + // to us overriding [QUIView setInteraction] to ignore UITextInteraction. If we + // didn't do that, the delegate would be kept. But not overriding that function + // has its own share of issues, so it seems better to keep that way for now. + // Instead, we choose to recreate the text responder as a brute-force solution + // until we have better knowledge of what is going on (or implement the new + // UITextInteraction protocol). + const auto oldResponder = m_textResponder; + [m_textResponder setMarkedText:@"" selectedRange:NSMakeRange(0, 0)]; + [m_textResponder notifyInputDelegate:Qt::ImQueryInput]; + [m_textResponder autorelease]; + m_textResponder = nullptr; update(Qt::ImQueryAll); - [m_textResponder setMarkedText:@"" selectedRange:NSMakeRange(0, 0)]; - [m_textResponder notifyInputDelegate:Qt::ImQueryInput]; + // If update() didn't end up creating a new text responder, oldResponder will still be + // the first responder. In that case we need to resign it, so that the input panel hides. + // (the input panel will apparently not hide if the first responder is only released). + if ([oldResponder isFirstResponder]) { + qImDebug("IM not enabled, resigning autoreleased text responder as first responder"); + [oldResponder resignFirstResponder]; + } } /*! diff --git a/src/plugins/platforms/ios/qiosmessagedialog.mm b/src/plugins/platforms/ios/qiosmessagedialog.mm index 254922701a..773b034e2a 100644 --- a/src/plugins/platforms/ios/qiosmessagedialog.mm +++ b/src/plugins/platforms/ios/qiosmessagedialog.mm @@ -47,6 +47,7 @@ #include "qiosglobal.h" #include "quiview.h" +#include "qiosscreen.h" #include "qiosmessagedialog.h" QIOSMessageDialog::QIOSMessageDialog() @@ -147,6 +148,25 @@ bool QIOSMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality win } UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window : qt_apple_sharedApplication().keyWindow; + if (!window) { + qCDebug(lcQpaWindow, "Attempting to exec a dialog without any window/widget visible."); + + auto *primaryScreen = static_cast<QIOSScreen*>(QGuiApplication::primaryScreen()->handle()); + Q_ASSERT(primaryScreen); + + window = primaryScreen->uiWindow(); + if (window.hidden) { + // With a window hidden, an attempt to present view controller + // below fails with a warning, that a view "is not a part of + // any view hierarchy". The UIWindow is initially hidden, + // as unhiding it is what hides the splash screen. + window.hidden = NO; + } + } + + if (!window) + return false; + [window.rootViewController presentViewController:m_alertController animated:YES completion:nil]; return true; } diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm index a83d495043..a5eadca38d 100644 --- a/src/plugins/platforms/ios/qiosscreen.mm +++ b/src/plugins/platforms/ios/qiosscreen.mm @@ -289,7 +289,7 @@ QIOSScreen::QIOSScreen(UIScreen *screen) if (!qt_apple_isApplicationExtension()) { for (UIWindow *existingWindow in qt_apple_sharedApplication().windows) { if (existingWindow.screen == m_uiScreen) { - m_uiWindow = [m_uiWindow retain]; + m_uiWindow = [existingWindow retain]; break; } } diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index a7f94dd31f..8b6537f4a7 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -514,15 +514,23 @@ // from within a undo callback. NSUndoManager *undoMgr = self.undoManager; [undoMgr removeAllActions]; + + [undoMgr beginUndoGrouping]; + [undoMgr registerUndoWithTarget:self selector:@selector(undo) object:nil]; + [undoMgr endUndoGrouping]; [undoMgr beginUndoGrouping]; [undoMgr registerUndoWithTarget:self selector:@selector(undo) object:nil]; [undoMgr endUndoGrouping]; - // Schedule an operation that we immediately pop off to be able to schedule a redo + // Schedule operations that we immediately pop off to be able to schedule redos + [undoMgr beginUndoGrouping]; + [undoMgr registerUndoWithTarget:self selector:@selector(registerRedo) object:nil]; + [undoMgr endUndoGrouping]; [undoMgr beginUndoGrouping]; [undoMgr registerUndoWithTarget:self selector:@selector(registerRedo) object:nil]; [undoMgr endUndoGrouping]; [undoMgr undo]; + [undoMgr undo]; // Note that, perhaps because of a bug in UIKit, the buttons on the shortcuts bar ends up // disabled if a undo/redo callback doesn't lead to a [UITextInputDelegate textDidChange]. @@ -530,6 +538,11 @@ // become disabled when there is nothing more to undo (Qt didn't change anything upon receiving // an undo request). This seems to be OK behavior, so we let it stay like that unless it shows // to cause problems. + + // QTBUG-63393: Having two operations on the rebuilt undo stack keeps the undo/redo widgets + // always enabled on the shortcut bar. This workaround was found by experimenting with + // removing the removeAllActions call, and is related to the unknown internal implementation + // details of how the shortcut bar updates the dimming of its buttons. }); } @@ -873,20 +886,20 @@ [self sendEventToFocusObject:e]; } -- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range +- (void)setBaseWritingDirection:(NSWritingDirection)writingDirection forRange:(UITextRange *)range { Q_UNUSED(writingDirection); Q_UNUSED(range); // Writing direction is handled by QLocale } -- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction +- (NSWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction { Q_UNUSED(position); Q_UNUSED(direction); if (QLocale::system().textDirection() == Qt::RightToLeft) - return UITextWritingDirectionRightToLeft; - return UITextWritingDirectionLeftToRight; + return NSWritingDirectionRightToLeft; + return NSWritingDirectionLeftToRight; } - (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm index cd4af46ef7..cf8324505a 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.mm +++ b/src/plugins/platforms/ios/qiosviewcontroller.mm @@ -145,9 +145,13 @@ UIWindow *uiWindow = self.window; if (uiWindow.screen != [UIScreen mainScreen] && self.subviews.count == 1) { - // Removing the last view of an external screen, go back to mirror mode - uiWindow.screen = [UIScreen mainScreen]; - uiWindow.hidden = YES; + // We're about to remove the last view of an external screen, so go back + // to mirror mode, but defer it until after the view has been removed, + // to ensure that we don't try to layout the view that's being removed. + dispatch_async(dispatch_get_main_queue(), ^{ + uiWindow.hidden = YES; + uiWindow.screen = [UIScreen mainScreen]; + }); } } @@ -246,6 +250,7 @@ @implementation QIOSViewController { BOOL m_updatingProperties; QMetaObject::Connection m_focusWindowChangeConnection; + QMetaObject::Connection m_appStateChangedConnection; } #ifndef Q_OS_TVOS @@ -274,7 +279,7 @@ }); QIOSApplicationState *applicationState = &QIOSIntegration::instance()->applicationState; - QObject::connect(applicationState, &QIOSApplicationState::applicationStateDidChange, + m_appStateChangedConnection = QObject::connect(applicationState, &QIOSApplicationState::applicationStateDidChange, [self](Qt::ApplicationState oldState, Qt::ApplicationState newState) { if (oldState == Qt::ApplicationSuspended && newState != Qt::ApplicationSuspended) { // We may have ignored an earlier layout because the application was suspended, @@ -294,6 +299,7 @@ - (void)dealloc { QObject::disconnect(m_focusWindowChangeConnection); + QObject::disconnect(m_appStateChangedConnection); [super dealloc]; } diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.mm b/src/plugins/platforms/ios/quiaccessibilityelement.mm index 3154536aad..3ebded0e00 100644 --- a/src/plugins/platforms/ios/quiaccessibilityelement.mm +++ b/src/plugins/platforms/ios/quiaccessibilityelement.mm @@ -123,8 +123,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement); if (val) { return val->currentValue().toString().toNSString(); } else if (QAccessibleTextInterface *text = iface->textInterface()) { - // FIXME doesn't work? - return text->text(0, text->characterCount() - 1).toNSString(); + return text->text(0, text->characterCount()).toNSString(); } return [super accessibilityHint]; @@ -158,8 +157,27 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement); if (state.searchEdit) traits |= UIAccessibilityTraitSearchField; - if (iface->role() == QAccessible::Button) + if (state.selected) + traits |= UIAccessibilityTraitSelected; + + const auto accessibleRole = iface->role(); + if (accessibleRole == QAccessible::Button) { traits |= UIAccessibilityTraitButton; + } else if (accessibleRole == QAccessible::EditableText) { + static auto defaultTextFieldTraits = []{ + auto *textField = [[[UITextField alloc] initWithFrame:CGRectZero] autorelease]; + return textField.accessibilityTraits; + }(); + traits |= defaultTextFieldTraits; + } else if (accessibleRole == QAccessible::Graphic) { + traits |= UIAccessibilityTraitImage; + } else if (accessibleRole == QAccessible::Heading) { + traits |= UIAccessibilityTraitHeader; + } else if (accessibleRole == QAccessible::Link) { + traits |= UIAccessibilityTraitLink; + } else if (accessibleRole == QAccessible::StaticText) { + traits |= UIAccessibilityTraitStaticText; + } if (iface->valueInterface()) traits |= UIAccessibilityTraitAdjustable; diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index 6ff495ab85..9289cc68c9 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -465,7 +465,10 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>( self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values()); } else { - QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>( + // Send the touch event asynchronously, as the application might spin a recursive + // event loop in response to the touch event (a dialog e.g.), which will deadlock + // the UIKit event delivery system (QTBUG-98651). + QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>( self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values()); } } @@ -571,7 +574,12 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime]; QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration()); - QWindowSystemInterface::handleTouchCancelEvent(self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice()); + + // Send the touch event asynchronously, as the application might spin a recursive + // event loop in response to the touch event (a dialog e.g.), which will deadlock + // the UIKit event delivery system (QTBUG-98651). + QWindowSystemInterface::handleTouchCancelEvent<QWindowSystemInterface::AsynchronousDelivery>( + self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice()); } - (int)mapPressTypeToKey:(UIPress*)press diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm index 6612dc131e..3e2d4c59ae 100644 --- a/src/plugins/platforms/ios/quiview_accessibility.mm +++ b/src/plugins/platforms/ios/quiview_accessibility.mm @@ -59,13 +59,20 @@ if (!iface) return; - [self createAccessibleElement: iface]; for (int i = 0; i < iface->childCount(); ++i) [self createAccessibleContainer: iface->child(i)]; + + // The container element must go last, so that it underlays all its children + [self createAccessibleElement:iface]; } - (void)initAccessibility { + // The window may have gone away, but with the view + // temporarily caught in the a11y subsystem. + if (!self.platformWindow) + return; + static bool init = false; if (!init) QGuiApplicationPrivate::platformIntegration()->accessibility()->setActive(true); |