tdf#133284: Improve hardware and on-screen keyboard in the iOS app
This is a quite complicated change that should both fix tdf#133284
(cursor keys on a hardware keyboard do not work in a spreadsheet
document) and also improve the interaction with
CollaboraOnlineWebViewKeyboardManager that manages the on-screen
keyboard. We need to jump through complicated hoops in order to get
the hardware cursor keys handled right after loading a spreadsheet
document.
In the CollaboraOnlineWebViewKeyboardManager case we try harder to
keep loleaflet's _textArea buffer in sync with what the UITextView in
CollaboraOnlineWebViewKeyboardManager uses to provide suggestions
above the on-screen keyboard.
Also merges in related changes from today to
CollaboraOnlineWebViewKeyboardManager.
Change-Id: Ic4acb54bd4e815aa8bfb2bf40b08493446ae5ab0
Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101878
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Tor Lillqvist <tml@collabora.com>
diff --git a/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
index 561b726..fd1bcaf 100644
--- a/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
+++ b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
@@ -79,6 +79,18 @@
NSMutableString *quotedText = [NSMutableString string];
int location = range.location;
if (location < self.text.length && location + range.length == self.text.length) {
// To guard against possible mismatch between our self.text and the _textArea.value in
// TextInput.js, we indicate deletion or replacement from the end with negative location.
location = location - self.text.length;
}
else if (range.location == 0 && range.length == 0 && text.length == 0) {
// Backspace without anything known about preceding text
location = -1;
}
for (unsigned i = 0; i < text.length; i++) {
const unichar c = [text characterAtIndex:i];
if (c == '\'' || c == '\\') {
@@ -93,7 +105,7 @@
NSMutableString *message = [NSMutableString string];
[message appendFormat:@"{id: 'COKbdMgr', command: 'replaceText', location: %lu, length: %lu, text: '", range.location, range.length];
[message appendFormat:@"{id: 'COKbdMgr', command: 'replaceText', location: %d, length: %lu, text: '", location, range.length];
[message appendString:quotedText];
[message appendString:@"'}"];
@@ -209,15 +221,15 @@
// will be added.
control.autocapitalizationType = UITextAutocapitalizationTypeNone;
control.text = text;
control.selectedRange = NSMakeRange(location, 0);
lastCommandIsHide = NO;
[self->webView addSubview:control];
NSLog(@"COKbdMgr: Added _COWVKMKeyInputControl to webView");
[control becomeFirstResponder];
}
control.text = text;
control.selectedRange = NSMakeRange(location, 0);
}
- (void)hideKeyboard {
@@ -253,6 +265,8 @@
NSString *text = message.body[@"text"];
NSNumber *location = message.body[@"location"];
NSLog(@"COKbdMgr: command=display type=%@ text=%@ location=%@", type, text, location);
if (text == nil)
text = @"";
[self displayKeyboardOfType:type withText:text at:(location != nil ? [location unsignedIntegerValue] : UINT_MAX)];
} else if ([stringCommand isEqualToString:@"hide"]) {
lastCommandIsHide = YES;
diff --git a/ios/Mobile/DocumentViewController.mm b/ios/Mobile/DocumentViewController.mm
index 2a2d870..5be21cd 100644
--- a/ios/Mobile/DocumentViewController.mm
+++ b/ios/Mobile/DocumentViewController.mm
@@ -102,8 +102,10 @@ static IMP standardImpOfInputAccessoryView = nil;
// contents is handled fully in JavaScript, the WebView has no knowledge of that.)
self.webView.scrollView.delegate = self;
keyboardManager =
[[CollaboraOnlineWebViewKeyboardManager alloc] initForWebView:self.webView];
if (!isExternalKeyboardAttached()) {
keyboardManager =
[[CollaboraOnlineWebViewKeyboardManager alloc] initForWebView:self.webView];
}
[self.view addSubview:self.webView];
@@ -442,6 +444,27 @@ static IMP standardImpOfInputAccessoryView = nil;
}];
return;
} else if ([message.body isEqualToString:@"FOCUSIFHWKBD"]) {
if (isExternalKeyboardAttached()) {
NSString *hwKeyboardMagic = @"{"
" if (window.MagicToGetHWKeyboardWorking) {"
" window.MagicToGetHWKeyboardWorking();"
" }"
"}";
[self.webView evaluateJavaScript:hwKeyboardMagic
completionHandler:^(id _Nullable obj, NSError * _Nullable error)
{
if (error) {
LOG_ERR("Error after " << [hwKeyboardMagic UTF8String] << ": " << [[error localizedDescription] UTF8String]);
NSString *jsException = error.userInfo[@"WKJavaScriptExceptionMessage"];
if (jsException != nil)
LOG_ERR("JavaScript exception: " << [jsException UTF8String]);
}
}
];
}
return;
} else if ([message.body hasPrefix:@"HYPERLINK"]) {
NSArray *messageBodyItems = [message.body componentsSeparatedByString:@" "];
if ([messageBodyItems count] >= 2) {
diff --git a/loleaflet/html/loleaflet.html.m4 b/loleaflet/html/loleaflet.html.m4
index 8ec3439..31820d1 100644
--- a/loleaflet/html/loleaflet.html.m4
+++ b/loleaflet/html/loleaflet.html.m4
@@ -70,7 +70,20 @@ m4_ifelse(ANDROIDAPP,[true],
)
if (window.ThisIsTheiOSApp) {
window.addEventListener("keydown", function(e) { e.preventDefault(); });
window.addEventListener('keydown', function(e) {
if (e.metaKey) {
e.preventDefault();
}
if (window.MagicKeyDownHandler)
window.MagicKeyDownHandler(e);
});
window.addEventListener('keyup', function(e) {
if (e.metaKey) {
e.preventDefault();
}
if (window.MagicKeyUpHandler)
window.MagicKeyUpHandler(e);
});
}
var Base64ToArrayBuffer = function(base64Str) {
diff --git a/loleaflet/src/layer/marker/TextInput.js b/loleaflet/src/layer/marker/TextInput.js
index bae93c2..7ce02fa 100644
--- a/loleaflet/src/layer/marker/TextInput.js
+++ b/loleaflet/src/layer/marker/TextInput.js
@@ -99,10 +99,28 @@ L.TextInput = L.Layer.extend({
this._onFocusBlur({ type: 'focus' });
}
if (window.ThisIsTheiOSApp) {
var that = this;
window.MagicToGetHWKeyboardWorking = function() {
var that2 = that;
window.MagicKeyDownHandler = function(e) {
that2._onKeyDown(e);
};
window.MagicKeyUpHandler = function(e) {
that2._onKeyUp(e);
};
};
window.postMobileMessage('FOCUSIFHWKBD');
}
L.DomEvent.on(this._map.getContainer(), 'mousedown touchstart', this._abortComposition, this);
},
onRemove: function() {
window.MagicToGetHWKeyboardWorking = null;
window.MagicKeyDownHandler = null;
window.MagicKeyUpHandler = null;
if (this._container) {
this.getPane().removeChild(this._container);
}
@@ -223,10 +241,16 @@ L.TextInput = L.Layer.extend({
throw errorMessage;
}
if (that._textArea.value.length == 2 && message.length == 0 && message.text.length == 0) {
that._removeTextContent(1, 0);
} else {
that._textArea.value = that._textArea.value.slice(0, message.location + 1) + message.text + that._textArea.value.slice(message.location + 1 + message.length);
if (message.location < 0) {
if (that._textArea.value.length > 2) {
that._textArea.value = that._textArea.value.slice(0, message.location - 1) + that._textArea.value.slice(-1);
that._onInput({});
} else {
that._removeTextContent(-message.location, 0);
}
}
if (message.text.length > 0) {
that._textArea.value = that._textArea.value.slice(0, -1) + message.text + that._textArea.value.slice(-1);
that._onInput({});
}
} else {
@@ -236,8 +260,7 @@ L.TextInput = L.Layer.extend({
}
};
// We don't know the seed text to feed CollaboraOnlineWebViewKeyboardManager
window.webkit.messageHandlers.CollaboraOnlineWebViewKeyboardManager.postMessage({command: 'display'});
window.webkit.messageHandlers.CollaboraOnlineWebViewKeyboardManager.postMessage({command: 'display', text: this._textArea.value.slice(1, -1)});
this._onFocusBlur({type: 'focus'});
return;