From 06be9f026f042f994d7c3548a331babca8d03545 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Wed, 19 Aug 2015 14:36:07 +0200 Subject: iOS: send key events through QPA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far we have chosen to send key events directly to the focus object since we do already do that for IM events. But Qt expects key events (especially control keys) to be sendt through QPA. This means that key events can end up somewhere else than at the focus object, which is expected and needed if shortcut propagation is to work. Change-Id: I160bf3309572719eda352cdb11b46c4b5a455e0d Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/ios/qiostextresponder.mm | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'src/plugins/platforms/ios/qiostextresponder.mm') diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index be9c3b9e27..68364d3c77 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -324,10 +324,8 @@ - (void)sendKeyPressRelease:(Qt::Key)key modifiers:(Qt::KeyboardModifiers)modifiers { - QKeyEvent press(QEvent::KeyPress, key, modifiers); - QKeyEvent release(QEvent::KeyRelease, key, modifiers); - [self sendEventToFocusObject:press]; - [self sendEventToFocusObject:release]; + QWindowSystemInterface::handleKeyEvent(qApp->focusWindow(), QEvent::KeyPress, key, modifiers); + QWindowSystemInterface::handleKeyEvent(qApp->focusWindow(), QEvent::KeyRelease, key, modifiers); } - (void)cut:(id)sender @@ -711,10 +709,10 @@ - (void)deleteBackward { - // Since we're posting im events directly to the focus object, we should do the - // same for key events. Otherwise they might end up in a different place or out - // of sync with im events. + // UITextInput selects the text to be deleted before calling this method. To avoid + // drawing the selection, we flush after posting the key press/release. [self sendKeyPressRelease:Qt::Key_Backspace modifiers:Qt::NoModifier]; + QWindowSystemInterface::flushWindowSystemEvents(); } @end -- cgit v1.2.3 From 82538aebe4b22978a6f7735924f5451304c3e2a8 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Mon, 17 Aug 2015 12:36:14 +0200 Subject: iOS: send arrow keys to Qt to handle cursor movement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Controlling cursor position through input methods in Qt is very limited. You cannot e.g move the cursor vertically, or select text that spans several paragraphs. The reason for the latter is that Qt works with input methods on a per-paragraph basis, which effectively locks UIKit to only being able to move the cursor and select text within a single paragraph. Fixing up input methods to support more advanced navigation means changing the whole design (working on a whole document, instead of per paragraph), and is out of scope. Instead we choose to listen for arrow keys and forward them to Qt in the same fashing as we already do for backspace and enter. This will make the iOS port on-par with the other platforms, which also sends control characters like these on the side of IM events. Note that registering shortcuts and overriding default IM navigation behavior will only take effect when a hardware keyboard is connected. Only then will [UIresponder keyCommands] be called from UIKit. Change-Id: I634205e0578447c4aa6e9fdff342ee3b281901ea Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/ios/qiostextresponder.mm | 65 ++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) (limited to 'src/plugins/platforms/ios/qiostextresponder.mm') diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index 68364d3c77..d16ad40414 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -328,6 +328,8 @@ QWindowSystemInterface::handleKeyEvent(qApp->focusWindow(), QEvent::KeyRelease, key, modifiers); } +#ifndef QT_NO_SHORTCUT + - (void)cut:(id)sender { Q_UNUSED(sender); @@ -360,6 +362,69 @@ // ------------------------------------------------------------------------- +- (void)keyCommandTriggered:(UIKeyCommand *)keyCommand +{ + Qt::Key key = Qt::Key_unknown; + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + + if (keyCommand.input == UIKeyInputLeftArrow) + key = Qt::Key_Left; + else if (keyCommand.input == UIKeyInputRightArrow) + key = Qt::Key_Right; + else if (keyCommand.input == UIKeyInputUpArrow) + key = Qt::Key_Up; + else if (keyCommand.input == UIKeyInputDownArrow) + key = Qt::Key_Down; + else + Q_UNREACHABLE(); + + if (keyCommand.modifierFlags & UIKeyModifierAlternate) + modifiers |= Qt::AltModifier; + if (keyCommand.modifierFlags & UIKeyModifierShift) + modifiers |= Qt::ShiftModifier; + if (keyCommand.modifierFlags & UIKeyModifierCommand) + modifiers |= Qt::ControlModifier; + + [self sendKeyPressRelease:key modifiers:modifiers]; +} + +- (void)addKeyCommandsToArray:(NSMutableArray *)array key:(NSString *)key +{ + SEL s = @selector(keyCommandTriggered:); + [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:0 action:s]]; + [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierShift action:s]]; + [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierAlternate action:s]]; + [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierAlternate|UIKeyModifierShift action:s]]; + [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierCommand action:s]]; + [array addObject:[UIKeyCommand keyCommandWithInput:key modifierFlags:UIKeyModifierCommand|UIKeyModifierShift action:s]]; +} + +- (NSArray *)keyCommands +{ + // Since keyCommands is called for every key + // press/release, we cache the result + static dispatch_once_t once; + static NSMutableArray *array; + + dispatch_once(&once, ^{ + // We let Qt move the cursor around when the arrow keys are being used. This + // is normally implemented through UITextInput, but since IM in Qt have poor + // support for moving the cursor vertically, and even less support for selecting + // text across multiple paragraphs, we do this through key events. + array = [NSMutableArray new]; + [self addKeyCommandsToArray:array key:UIKeyInputUpArrow]; + [self addKeyCommandsToArray:array key:UIKeyInputDownArrow]; + [self addKeyCommandsToArray:array key:UIKeyInputLeftArrow]; + [self addKeyCommandsToArray:array key:UIKeyInputRightArrow]; + }); + + return array; +} + +#endif // QT_NO_SHORTCUT + +// ------------------------------------------------------------------------- + - (void)notifyInputDelegate:(Qt::InputMethodQueries)updatedProperties { // As documented, we should not report textWillChange/textDidChange unless the text -- cgit v1.2.3 From dbb24ce477702768534d64b260e269d2abc13e41 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Tue, 18 Aug 2015 15:21:30 +0200 Subject: iOS: listen for standard text edit shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Catch the missing shortcuts sent by UIKit, and forward them to Qt as key events so that the app can respond to them. Change-Id: If63c4b05466e64843982fce32fbd594d2a0ef312 Reviewed-by: Richard Moe Gustavsen Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/ios/qiostextresponder.mm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/plugins/platforms/ios/qiostextresponder.mm') diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index d16ad40414..fdcc703267 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -360,6 +360,24 @@ [self sendKeyPressRelease:Qt::Key_Delete modifiers:Qt::ControlModifier]; } +- (void)toggleBoldface:(id)sender +{ + Q_UNUSED(sender); + [self sendKeyPressRelease:Qt::Key_B modifiers:Qt::ControlModifier]; +} + +- (void)toggleItalics:(id)sender +{ + Q_UNUSED(sender); + [self sendKeyPressRelease:Qt::Key_I modifiers:Qt::ControlModifier]; +} + +- (void)toggleUnderline:(id)sender +{ + Q_UNUSED(sender); + [self sendKeyPressRelease:Qt::Key_U modifiers:Qt::ControlModifier]; +} + // ------------------------------------------------------------------------- - (void)keyCommandTriggered:(UIKeyCommand *)keyCommand -- cgit v1.2.3 From af5b62844ca62bf30b47a8ecc9ed1f8b24dbff3c Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Fri, 21 Aug 2015 15:39:35 +0200 Subject: iOS: add undo/redo support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable the undo/redo buttons on the keyboard, as well as the Cmd-Z/Cmd-Shift-Z shortcuts. For UIKit to support this, we need to add undo actions to first responders undo manager. Since we don't know if Qt has anything to undo/redo, we just enable the shortcuts all the time. To do that, we trick NSUndoManager to always have both an undo operation and a redo operation in the stack. Change-Id: I3294a962cc24f56585e7e515856142f3dda56d0a Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/ios/qiostextresponder.mm | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'src/plugins/platforms/ios/qiostextresponder.mm') diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index fdcc703267..624411ebbc 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -209,6 +209,9 @@ if (UIView *accessoryView = static_cast(platformData.value(kImePlatformDataInputAccessoryView).value())) self.inputAccessoryView = [[[WrapperView alloc] initWithView:accessoryView] autorelease]; + self.undoManager.groupsByEvent = NO; + [self rebuildUndoStack]; + return self; } @@ -380,6 +383,56 @@ // ------------------------------------------------------------------------- +- (void)undo +{ + [self sendKeyPressRelease:Qt::Key_Z modifiers:Qt::ControlModifier]; + [self rebuildUndoStack]; +} + +- (void)redo +{ + [self sendKeyPressRelease:Qt::Key_Z modifiers:Qt::ControlModifier|Qt::ShiftModifier]; + [self rebuildUndoStack]; +} + +- (void)registerRedo +{ + NSUndoManager *undoMgr = self.undoManager; + [undoMgr beginUndoGrouping]; + [undoMgr registerUndoWithTarget:self selector:@selector(redo) object:nil]; + [undoMgr endUndoGrouping]; +} + +- (void)rebuildUndoStack +{ + dispatch_async(dispatch_get_main_queue (), ^{ + // Register dummy undo/redo operations to enable Cmd-Z and Cmd-Shift-Z + // Ensure we do this outside any undo/redo callback since NSUndoManager + // will treat registerUndoWithTarget as registering a redo when called + // from within a undo callback. + NSUndoManager *undoMgr = self.undoManager; + [undoMgr removeAllActions]; + [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 + [undoMgr beginUndoGrouping]; + [undoMgr registerUndoWithTarget:self selector:@selector(registerRedo) object:nil]; + [undoMgr endUndoGrouping]; + [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]. + // And we only call that method if Qt made changes to the text. The effect is that the buttons + // 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. + }); +} + +// ------------------------------------------------------------------------- + - (void)keyCommandTriggered:(UIKeyCommand *)keyCommand { Qt::Key key = Qt::Key_unknown; -- cgit v1.2.3 From 21e1354d42d1bb3967dedb62facc684a8ab702ce Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Mon, 3 Aug 2015 12:59:01 +0200 Subject: iOS: handle all directions when calculating positionFromPosition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The method is currently a bit buggy, since it does not take UITextLayoutDirectionUp and UITextLayoutDirectionDown into account. This patch is mostly for fixing something "just in case", since we so far have not seen UIKit calling this method for those directions unless a hardware keyboard is connected. And in that case, we anyway override IM navigation by dealing with the arrow keys explicit. Since IM in Qt does not support getting the position above or below the current position, we just return the current position, making it a no-op. Change-Id: I4bcb9e2a00ab4e3d785058d7ff7f4855142dabbc Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/ios/qiostextresponder.mm | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src/plugins/platforms/ios/qiostextresponder.mm') diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index 624411ebbc..685ff8ff47 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -652,7 +652,17 @@ - (UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset { int p = static_cast(position).index; - return [QUITextPosition positionWithIndex:(direction == UITextLayoutDirectionRight ? p + offset : p - offset)]; + + switch (direction) { + case UITextLayoutDirectionLeft: + return [QUITextPosition positionWithIndex:p - offset]; + case UITextLayoutDirectionRight: + return [QUITextPosition positionWithIndex:p + offset]; + default: + // Qt doesn't support getting the position above or below the current position, so + // for those cases we just return the current position, making it a no-op. + return position; + } } - (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction -- cgit v1.2.3 From d4799d02385ffd9bb4c0ba2c757059a70305daf0 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Fri, 28 Aug 2015 15:46:07 +0200 Subject: iOS: compose key events from QKeySequences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of hard-coding the key and modifier that should trigger a shortcut, we read it out from the QKeySequence that corresponds to a StandardKey instead. Change-Id: I6325534d3ff91c788d7e660d9009954e437b8534 Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/ios/qiostextresponder.mm | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) (limited to 'src/plugins/platforms/ios/qiostextresponder.mm') diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index 685ff8ff47..4e109f6921 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -333,65 +333,73 @@ #ifndef QT_NO_SHORTCUT +- (void)sendShortcut:(QKeySequence::StandardKey)standardKey +{ + const int keys = QKeySequence(standardKey)[0]; + Qt::Key key = Qt::Key(keys & 0x0000FFFF); + Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keys & 0xFFFF0000); + [self sendKeyPressRelease:key modifiers:modifiers]; +} + - (void)cut:(id)sender { Q_UNUSED(sender); - [self sendKeyPressRelease:Qt::Key_X modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::Cut]; } - (void)copy:(id)sender { Q_UNUSED(sender); - [self sendKeyPressRelease:Qt::Key_C modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::Copy]; } - (void)paste:(id)sender { Q_UNUSED(sender); - [self sendKeyPressRelease:Qt::Key_V modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::Paste]; } - (void)selectAll:(id)sender { Q_UNUSED(sender); - [self sendKeyPressRelease:Qt::Key_A modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::SelectAll]; } - (void)delete:(id)sender { Q_UNUSED(sender); - [self sendKeyPressRelease:Qt::Key_Delete modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::Delete]; } - (void)toggleBoldface:(id)sender { Q_UNUSED(sender); - [self sendKeyPressRelease:Qt::Key_B modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::Bold]; } - (void)toggleItalics:(id)sender { Q_UNUSED(sender); - [self sendKeyPressRelease:Qt::Key_I modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::Italic]; } - (void)toggleUnderline:(id)sender { Q_UNUSED(sender); - [self sendKeyPressRelease:Qt::Key_U modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::Underline]; } // ------------------------------------------------------------------------- - (void)undo { - [self sendKeyPressRelease:Qt::Key_Z modifiers:Qt::ControlModifier]; + [self sendShortcut:QKeySequence::Undo]; [self rebuildUndoStack]; } - (void)redo { - [self sendKeyPressRelease:Qt::Key_Z modifiers:Qt::ControlModifier|Qt::ShiftModifier]; + [self sendShortcut:QKeySequence::Redo]; [self rebuildUndoStack]; } -- cgit v1.2.3 From d75505faccc9a86d9e76932ae022b660e01ad1a5 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Tue, 8 Sep 2015 13:38:41 +0200 Subject: iOS: silence compiler warning about unimplemented 'selectionRectsForRange' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement a dummy method to silence the compiler. After testing, this method seems to never be called. Which is good, since the current IM API in Qt have little to offer to resolve what is being asked. Until a need arise, we just return an empty array. Change-Id: I573eb8205a7e635a46d487ae175fb46e3a602001 Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/ios/qiostextresponder.mm | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/plugins/platforms/ios/qiostextresponder.mm') diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index 4e109f6921..c9120e848c 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -738,6 +738,15 @@ return toCGRect(startRect.united(endRect)); } +- (NSArray *)selectionRectsForRange:(UITextRange *)range +{ + Q_UNUSED(range); + // This method is supposed to return a rectangle for each line with selection. Since we don't + // expose an API in Qt/IM for getting this information, and since we never seems to be getting + // a call from UIKit for this, we return an empty array until a need arise. + return [[NSArray new] autorelease]; +} + - (CGRect)caretRectForPosition:(UITextPosition *)position { Q_UNUSED(position); -- cgit v1.2.3