ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h
 |   28 +
 
ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
 |  240 ++++++++++
 ios/Mobile.xcodeproj/project.pbxproj                                           
   |    6 
 ios/Mobile/DocumentViewController.mm                                           
   |    5 
 4 files changed, 279 insertions(+)

New commits:
commit 391c1bbebac005e7e6e5ca1ab706ab946f117f2e
Author:     Tor Lillqvist <t...@collabora.com>
AuthorDate: Mon Aug 24 12:03:40 2020 +0300
Commit:     Tor Lillqvist <t...@collabora.com>
CommitDate: Wed Sep 2 19:13:08 2020 +0200

    Use CollaboraOnlineWebViewKeyboardManager
    
    For now, just copy its source files here. When/if I figure out what is
    the appropriate way to package that framework for use in other
    products (like the Collabora Office iOS app) I will use that instead.
    
    Change-Id: If808f96b6a72c80e54dc84fce80a551503c96335
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101268
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Tor Lillqvist <t...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101951

diff --git 
a/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h
 
b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h
new file mode 100644
index 000000000..c62f54c48
--- /dev/null
+++ 
b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h
@@ -0,0 +1,28 @@
+// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*-
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <WebKit/WebKit.h>
+
+@interface CollaboraOnlineWebViewKeyboardManager : NSObject
+
+/**
+ * @param webView The WKWebView that displays Collabora Online's 
loleaflet.html. Will not do
+ * anything useful for WKWebViews not displaying that. The loleaflet.html can 
be in an arbitrarily
+ * deeply nested iframe.
+ */
+- (nonnull CollaboraOnlineWebViewKeyboardManager *)initForWebView:(nonnull 
WKWebView *)webView;
+
+@end
+
+// vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git 
a/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
 
b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
new file mode 100644
index 000000000..0e7d3208f
--- /dev/null
+++ 
b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
@@ -0,0 +1,240 @@
+// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*-
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import 
<CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h>
+
+@interface _COWVKMKeyInputControl : UITextView<UITextViewDelegate> {
+    WKWebView *webView;
+}
+
+- (instancetype)initForWebView:(nonnull WKWebView *)webView;
+
+@end
+
+@implementation _COWVKMKeyInputControl
+
+- (instancetype)initForWebView:(nonnull WKWebView *)webView {
+    self = [super init];
+
+    self->webView = webView;
+    self.delegate = self;
+
+    return self;
+}
+
+- (void)postMessage:(NSString *)message {
+
+    NSMutableString *js = [NSMutableString string];
+
+    [js appendString:@""
+       "{"
+       "     const message = "];
+
+    [js appendString:message];
+
+    // We check if window.COKbdMgrCallback is a function, and in that case 
call that directly.
+    // Otherwise we iterate over iframes and post a message that the event 
listener that we install
+    // will receive and handle, and recurse.
+
+    [js appendString:@";"
+        "     if (typeof window.COKbdMgrCallback === 'function') {"
+        "         window.COKbdMgrCallback(message);"
+        "     } else {"
+        "         const iframes = document.getElementsByTagName('iframe');"
+        "         for (let i = 0; i < iframes.length; i++) {"
+        "             iframes[i].contentWindow.postMessage(message, '*');"
+        "         };"
+        "     }"
+        "}"];
+
+    [webView evaluateJavaScript:js
+              completionHandler:^(id _Nullable obj, NSError *_Nullable error) {
+                if (error) {
+                    if (error.userInfo[@"WKJavaScriptExceptionMessage"])
+                        NSLog(@"Error when executing JavaScript: %@: %@", 
error.localizedDescription, error.userInfo[@"WKJavaScriptExceptionMessage"]);
+                    else
+                        NSLog(@"Error when executing JavaScript: %@", 
error.localizedDescription);
+                }
+              }];
+}
+
+- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range 
replacementText:(NSString *)text {
+    NSLog(@"COKbdMgr: shouldChangeTextInRange({%lu, %lu}, '%@')", (unsigned 
long)range.location, (unsigned long)range.length, text);
+    NSLog(@"self.text is now length %lu '%@'", self.text.length, self.text);
+
+    NSMutableString *quotedText = [NSMutableString string];
+
+    for (unsigned i = 0; i < text.length; i++) {
+        const unichar c = [text characterAtIndex:i];
+        if (c == '\'' || c == '\\') {
+            [quotedText appendString:@"\\"];
+            [quotedText appendFormat:@"%c", c];
+        } else if (c < ' ' || c >= 0x7F) {
+            [quotedText appendFormat:@"\\u%04X", c];
+        } else {
+            [quotedText appendFormat:@"%c", c];
+        }
+    }
+
+    NSMutableString *message = [NSMutableString string];
+
+    [message appendFormat:@"{id: 'COKbdMgr', command: 'replaceText', location: 
%lu, length: %lu, text: '", range.location, range.length];
+    [message appendString:quotedText];
+    [message appendString:@"'}"];
+
+    [self postMessage:message];
+
+    return YES;
+}
+
+- (BOOL)canBecomeFirstResponder {
+    return YES;
+}
+
+@synthesize hasText;
+
+@end
+
+@interface CollaboraOnlineWebViewKeyboardManager () <WKScriptMessageHandler> {
+    WKWebView *webView;
+    _COWVKMKeyInputControl *control;
+}
+
+@end
+
+@implementation CollaboraOnlineWebViewKeyboardManager
+
+- (CollaboraOnlineWebViewKeyboardManager *)initForWebView:(nonnull WKWebView 
*)webView {
+    self->webView = webView;
+
+    [webView.configuration.userContentController
+        addScriptMessageHandler:self
+                           name:@"CollaboraOnlineWebViewKeyboardManager"];
+
+    NSString *script = @"window.addEventListener('message', function(event) {"
+        "    if (event.data.id === 'COKbdMgr') {"
+        "        if (typeof window.COKbdMgrCallback === 'function') {"
+        "            window.COKbdMgrCallback(event.data);"
+        "         } else {"
+        "             const iframes = document.getElementsByTagName('iframe');"
+        "             for (let i = 0; i < iframes.length; i++) {"
+        "                 iframes[i].contentWindow.postMessage(event.data, 
'*');"
+        "             };"
+        "          }"
+        "    }"
+        "});";
+
+    WKUserScript *userScript = [[WKUserScript alloc] initWithSource:script
+                                                      
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
+                                                   forMainFrameOnly:NO];
+
+    [webView.configuration.userContentController addUserScript:userScript];
+
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             
selector:@selector(keyboardDidHide:)
+                                                 
name:UIKeyboardDidHideNotification
+                                               object:nil];
+
+    return self;
+}
+
+- (void)displayKeyboardOfType:(NSString *)type withText:(NSString *)text 
at:(NSUInteger)location {
+    if (control == nil) {
+        control = [[_COWVKMKeyInputControl alloc] 
initForWebView:self->webView];
+        if (type != nil) {
+            UIKeyboardType keyboardType = UIKeyboardTypeDefault;
+            if ([type caseInsensitiveCompare:@"default"] == NSOrderedSame)
+                ;
+            else if ([type caseInsensitiveCompare:@"asciicapable"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypeASCIICapable;
+            else if ([type caseInsensitiveCompare:@"numbersandpunctuation"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypeNumbersAndPunctuation;
+            else if ([type caseInsensitiveCompare:@"url"] == NSOrderedSame)
+                keyboardType = UIKeyboardTypeURL;
+            else if ([type caseInsensitiveCompare:@"numberpad"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypeNumberPad;
+            else if ([type caseInsensitiveCompare:@"phonepad"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypePhonePad;
+            else if ([type caseInsensitiveCompare:@"namephonepad"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypeNamePhonePad;
+            else if ([type caseInsensitiveCompare:@"emailaddress"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypeEmailAddress;
+            else if ([type caseInsensitiveCompare:@"decimalpad"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypeDecimalPad;
+            else if ([type caseInsensitiveCompare:@"asciicapablenumberpad"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypeASCIICapableNumberPad;
+            else if ([type caseInsensitiveCompare:@"alphabet"] == 
NSOrderedSame)
+                keyboardType = UIKeyboardTypeAlphabet;
+            else
+                NSLog(@"COKbdMgr: Unrecognized keyboard type %@", type);
+            if (keyboardType != UIKeyboardTypeDefault)
+                control.keyboardType = keyboardType;
+        }
+        // Don't auto-capitalize start of input as we have no idea about the 
context into which it
+        // will be added.
+        control.autocapitalizationType = UITextAutocapitalizationTypeNone;
+
+        control.text = text;
+        control.selectedRange = NSMakeRange(location, 0);
+
+        [self->webView addSubview:control];
+        // NSLog(@"COKbdMgr: added _COWVKMKeyInputControl to webView");
+        [control becomeFirstResponder];
+    }
+}
+
+- (void)hideKeyboard {
+    if (control != nil) {
+        [control removeFromSuperview];
+        // NSLog(@"COKbdMgr: removed _COWVKMKeyInputControl from webView");
+        control = nil;
+    }
+}
+
+- (void)userContentController:(nonnull WKUserContentController 
*)userContentController
+      didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
+    if (![message.name 
isEqualToString:@"CollaboraOnlineWebViewKeyboardManager"]) {
+        NSLog(@"Received unrecognized script message name: %@ %@", 
message.name, message.body);
+        return;
+    }
+
+    if ([message.body isKindOfClass:[NSDictionary class]]) {
+        NSString *stringCommand = message.body[@"command"];
+        if ([stringCommand isEqualToString:@"display"]) {
+            NSString *type = message.body[@"type"];
+            NSString *text = message.body[@"text"];
+            NSNumber *location = message.body[@"location"];
+            [self displayKeyboardOfType:type withText:text at:(location != nil 
? [location unsignedIntegerValue] : UINT_MAX)];
+        } else if ([stringCommand isEqualToString:@"hide"]) {
+            [self hideKeyboard];
+        } else if (stringCommand == nil) {
+            NSLog(@"No 'command' in %@", message.body);
+        } else {
+            NSLog(@"Received unrecognized command:%@", stringCommand);
+        }
+    } else {
+        NSLog(@"Received unrecognized message body of type %@: %@, should be a 
dictionary (JS object)", [message.body class], message.body);
+    }
+}
+
+- (void)keyboardDidHide:(NSNotification *)notification {
+    if (control != nil) {
+        [control removeFromSuperview];
+        control = nil;
+    }
+}
+
+@end
+
+// vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/ios/Mobile.xcodeproj/project.pbxproj 
b/ios/Mobile.xcodeproj/project.pbxproj
index bedc02c3b..68966947d 100644
--- a/ios/Mobile.xcodeproj/project.pbxproj
+++ b/ios/Mobile.xcodeproj/project.pbxproj
@@ -15,6 +15,7 @@
                BE00F8B5213ED543001CE2D4 /* libiconv.tbd in Frameworks */ = 
{isa = PBXBuildFile; fileRef = BE00F8B4213ED543001CE2D4 /* libiconv.tbd */; };
                BE00F8B7213ED573001CE2D4 /* libz.tbd in Frameworks */ = {isa = 
PBXBuildFile; fileRef = BE00F8B6213ED573001CE2D4 /* libz.tbd */; };
                BE18C7DE226DE09A001AD27E /* Branding in Resources */ = {isa = 
PBXBuildFile; fileRef = BE18C7DD226DE09A001AD27E /* Branding */; };
+               BE2FB29E24F3B146006E18B1 /* 
CollaboraOnlineWebViewKeyboardManager.m in Sources */ = {isa = PBXBuildFile; 
fileRef = BE2FB29D24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.m 
*/; };
                BE5EB5C1213FE29900E0826C /* Log.cpp in Sources */ = {isa = 
PBXBuildFile; fileRef = BE5EB5B9213FE29900E0826C /* Log.cpp */; };
                BE5EB5C2213FE29900E0826C /* SpookyV2.cpp in Sources */ = {isa = 
PBXBuildFile; fileRef = BE5EB5BA213FE29900E0826C /* SpookyV2.cpp */; };
                BE5EB5C3213FE29900E0826C /* Session.cpp in Sources */ = {isa = 
PBXBuildFile; fileRef = BE5EB5BB213FE29900E0826C /* Session.cpp */; };
@@ -103,6 +104,8 @@
                BE28F896228CE04700C00C48 /* langselect.cxx */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = 
langselect.cxx; path = "../../ios-device/desktop/source/app/langselect.cxx"; 
sourceTree = "<group>"; };
                BE28F897228CE04700C00C48 /* lockfile2.cxx */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = lockfile2.cxx; 
path = "../../ios-device/desktop/source/app/lockfile2.cxx"; sourceTree = 
"<group>"; };
                BE28F898228CE04700C00C48 /* userinstall.cxx */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = 
userinstall.cxx; path = "../../ios-device/desktop/source/app/userinstall.cxx"; 
sourceTree = "<group>"; };
+               BE2FB29C24F3B146006E18B1 /* 
CollaboraOnlineWebViewKeyboardManager.h */ = {isa = PBXFileReference; 
fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = 
CollaboraOnlineWebViewKeyboardManager.h; path = 
CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h; 
sourceTree = SOURCE_ROOT; };
+               BE2FB29D24F3B146006E18B1 /* 
CollaboraOnlineWebViewKeyboardManager.m */ = {isa = PBXFileReference; 
fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = 
CollaboraOnlineWebViewKeyboardManager.m; path = 
CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m; 
sourceTree = SOURCE_ROOT; };
                BE34D10F218B66B600815297 /* docsh.cxx */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = docsh.cxx; 
path = "../../ios-device/sw/source/uibase/app/docsh.cxx"; sourceTree = 
"<group>"; };
                BE34D110218B66B600815297 /* docstyle.cxx */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = docstyle.cxx; 
path = "../../ios-device/sw/source/uibase/app/docstyle.cxx"; sourceTree = 
"<group>"; };
                BE34D111218B66B600815297 /* docshdrw.cxx */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = docshdrw.cxx; 
path = "../../ios-device/sw/source/uibase/app/docshdrw.cxx"; sourceTree = 
"<group>"; };
@@ -2263,6 +2266,8 @@
                BE8D77292136762500AC58EA /* Mobile */ = {
                        isa = PBXGroup;
                        children = (
+                               BE2FB29C24F3B146006E18B1 /* 
CollaboraOnlineWebViewKeyboardManager.h */,
+                               BE2FB29D24F3B146006E18B1 /* 
CollaboraOnlineWebViewKeyboardManager.m */,
                                BE58E13021874A2E00249358 /* Mobile.entitlements 
*/,
                                BE5EB5D92140363100E0826C /* ios.mm */,
                                BE00F8922139494E001CE2D4 /* Resources */,
@@ -3048,6 +3053,7 @@
                                BE5EB5C7213FE29900E0826C /* Protocol.cpp in 
Sources */,
                                BE8D772F2136762500AC58EA /* 
DocumentBrowserViewController.mm in Sources */,
                                BE5EB5D0213FE2D000E0826C /* TileCache.cpp in 
Sources */,
+                               BE2FB29E24F3B146006E18B1 /* 
CollaboraOnlineWebViewKeyboardManager.m in Sources */,
                                BE5EB5C5213FE29900E0826C /* MessageQueue.cpp in 
Sources */,
                                BE7228E22417BC9F000ADABD /* StringVector.cpp in 
Sources */,
                                BE5EB5D621401E0F00E0826C /* Storage.cpp in 
Sources */,
diff --git a/ios/Mobile/DocumentViewController.mm 
b/ios/Mobile/DocumentViewController.mm
index c94161c02..cefd23c9e 100644
--- a/ios/Mobile/DocumentViewController.mm
+++ b/ios/Mobile/DocumentViewController.mm
@@ -19,6 +19,7 @@
 #import <sys/stat.h>
 
 #import "ios.h"
+#import "CollaboraOnlineWebViewKeyboardManager.h"
 #import "FakeSocket.hpp"
 #import "LOOLWSD.hpp"
 #import "Log.hpp"
@@ -32,6 +33,7 @@ static DocumentViewController* theSingleton = nil;
 @interface DocumentViewController() <WKNavigationDelegate, WKUIDelegate, 
WKScriptMessageHandler, UIScrollViewDelegate, UIDocumentPickerDelegate> {
     int closeNotificationPipeForForwardingThread[2];
     NSURL *downloadAsTmpURL;
+    CollaboraOnlineWebViewKeyboardManager *keyboardManager;
 }
 
 @end
@@ -103,6 +105,9 @@ 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];
+
     [self.view addSubview:self.webView];
 
     self.webView.navigationDelegate = self;
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to