mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 00:45:32 +00:00
483 lines
22 KiB
Objective-C
483 lines
22 KiB
Objective-C
//
|
|
// KMInputViewController.m
|
|
// Keyman Engine
|
|
//
|
|
// Created by Serkan Kurt on 20/06/2014.
|
|
// Copyright (c) 2017 SIL International. All rights reserved.
|
|
//
|
|
|
|
#import "KMInputViewController.h"
|
|
#import "KMManager+Internal.h"
|
|
#import <AudioToolbox/AudioToolbox.h>
|
|
|
|
@interface KMInputViewController ()
|
|
@property (weak, nonatomic) UIView *kmInputView;
|
|
@property (strong, nonatomic) NSArray<__kindof NSLayoutConstraint *> *bView_constraint_H;
|
|
@property (strong, nonatomic) NSArray<__kindof NSLayoutConstraint *> *bView_constraint_POS_HR;
|
|
@property (strong, nonatomic) UIView *containerView;
|
|
@property (strong, nonatomic) NSArray<__kindof NSLayoutConstraint *> *cView_constraint_H;
|
|
@property (strong, nonatomic) NSArray<__kindof NSLayoutConstraint *> *cView_constraint_POS_HR;
|
|
@property (strong, nonatomic) NSLayoutConstraint *heightConstraint;
|
|
//@property (strong, nonatomic) UIButton *debugButton;
|
|
@property (assign) BOOL isTopBarEnabled;
|
|
@end
|
|
|
|
@implementation KMInputViewController
|
|
@synthesize bView_constraint_H, bView_constraint_POS_HR;
|
|
@synthesize cView_constraint_H, cView_constraint_POS_HR;
|
|
|
|
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
|
|
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
|
if (self) {
|
|
// Custom initialization
|
|
self.isInputClickSoundEnabled = YES;
|
|
self.isTopBarEnabled = [[KMManager sharedInstance] isSystemKeyboardTopBarEnabled];
|
|
self.kmInputView = [KMManager inputView];
|
|
//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardLoaded:) name:kKeymanKeyboardLoadedNotification object:nil];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)updateViewConstraints {
|
|
// Add custom view sizing constraints here
|
|
if (self.view.frame.size.width == 0 || self.view.frame.size.height == 0) {
|
|
[super updateViewConstraints];
|
|
return;
|
|
}
|
|
|
|
[[KMManager sharedInstance] updateViewConstraints];
|
|
//NSDictionary *viewsDict = @{@"bView":topBarImageView, @"cView":containerView};
|
|
|
|
NSLayoutConstraint *bViewConstraintH = (NSLayoutConstraint *)[bView_constraint_H objectAtIndex:0];
|
|
bViewConstraintH.constant = (unsigned long)(self.isTopBarEnabled?[[self class] topBarHeight]:0);
|
|
|
|
/*
|
|
[self.view removeConstraints:bView_constraint_H];
|
|
NSString *vFormat = [NSString stringWithFormat:@"V:[bView(%lu)]", (unsigned long)(self.isTopBarEnabled?[[self class] topBarHeight]:0)];
|
|
bView_constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
[self.view addConstraints:bView_constraint_H];*/
|
|
|
|
for ( __kindof NSLayoutConstraint * bConstraint in bView_constraint_H) {
|
|
if (![self.view.constraints containsObject:bConstraint])
|
|
[self.view addConstraint:bConstraint];
|
|
}
|
|
|
|
CGSize size = self.kmInputView.frame.size;
|
|
NSLayoutConstraint *cViewConstraintH = (NSLayoutConstraint *)[cView_constraint_H objectAtIndex:0];
|
|
cViewConstraintH.constant = (int)size.height%1000;
|
|
/*
|
|
[self.view removeConstraints:cView_constraint_H];
|
|
CGSize size = self.kmInputView.frame.size;
|
|
vFormat = [NSString stringWithFormat:@"V:[cView(%d)]", (int)size.height%1000];
|
|
cView_constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
[self.view addConstraints:cView_constraint_H];*/
|
|
for ( __kindof NSLayoutConstraint * cConstraint in cView_constraint_H) {
|
|
if (![self.view.constraints containsObject:cConstraint])
|
|
[self.view addConstraint:cConstraint];
|
|
}
|
|
|
|
CGFloat expandedHeight = [self expandedHeight];
|
|
if (![self.view.constraints containsObject:self.heightConstraint]) {
|
|
self.heightConstraint.constant = expandedHeight;
|
|
[self.view addConstraint:self.heightConstraint];
|
|
}
|
|
else if (self.heightConstraint.constant != expandedHeight) {
|
|
self.heightConstraint.constant = expandedHeight;
|
|
}
|
|
|
|
[super updateViewConstraints];
|
|
}
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
|
|
UIColor *bgColor = [UIColor colorWithRed:210.0/255.0 green:214.0/255.0 blue:220.0/255.0 alpha:1.0];
|
|
self.view.backgroundColor = bgColor;
|
|
|
|
[[KMManager sharedInstance] inputViewDidLoad];
|
|
[[KMManager sharedInstance] setInputDelegate:self];
|
|
|
|
[self.topBarImageView removeFromSuperview];
|
|
_topBarImageView = [UIImageView new];
|
|
self.topBarImageView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
self.topBarImageView.backgroundColor = [UIColor grayColor];
|
|
[self.view addSubview:self.topBarImageView];
|
|
|
|
/*
|
|
if ([[KMManager sharedInstance] debugPrintingOn]) {
|
|
_debugButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
self.debugButton.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.debugButton addTarget:self action:@selector(viewDebugLog:) forControlEvents:UIControlEventTouchUpInside];
|
|
[self.debugButton setTitle:@"\u24B9" forState:UIControlStateNormal];
|
|
[self.debugButton sizeToFit];
|
|
[self.view addSubview:self.debugButton];
|
|
}*/
|
|
|
|
[self.containerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
|
[self.containerView removeFromSuperview];
|
|
_containerView = [UIView new];
|
|
self.containerView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
self.containerView.backgroundColor = bgColor;
|
|
[self.containerView addSubview:self.kmInputView];
|
|
[self.view addSubview:self.containerView];
|
|
|
|
//[self setConstraints]; /**/
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated {
|
|
[super viewDidAppear:animated];
|
|
[self setConstraints]; /**/
|
|
[self.inputView setNeedsUpdateConstraints]; /**/
|
|
//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(subKeysMenuWillShow:) name:kKeymanSubKeysMenuWillShowNotification object:nil];
|
|
//[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(subKeysMenuDismissed:) name:kKeymanSubKeysMenuDismissedNotification object:nil];
|
|
}
|
|
|
|
- (void)viewDidDisappear:(BOOL)animated {
|
|
[super viewDidDisappear:animated];
|
|
}
|
|
|
|
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
|
|
[[KMManager sharedInstance] inputViewWillRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
//[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
- (void)didReceiveMemoryWarning {
|
|
[super didReceiveMemoryWarning];
|
|
// Dispose of any resources that can be recreated.
|
|
}
|
|
|
|
- (void)textDidChange:(id<UITextInput>)textInput {
|
|
NSString *cxBeforeInput;
|
|
if ([self.textDocumentProxy documentContextBeforeInput] != nil)
|
|
cxBeforeInput = [NSString stringWithString:[self.textDocumentProxy documentContextBeforeInput]];
|
|
else
|
|
cxBeforeInput = @"";
|
|
|
|
NSString *cxAfterInput;
|
|
if ([self.textDocumentProxy documentContextAfterInput] != nil)
|
|
cxAfterInput = [NSString stringWithString:[self.textDocumentProxy documentContextAfterInput]];
|
|
else
|
|
cxAfterInput = @"";
|
|
|
|
NSString *context = [NSString stringWithFormat:@"%@%@", cxBeforeInput, cxAfterInput];
|
|
NSRange range = [context rangeOfString:cxBeforeInput];
|
|
NSRange newRange;
|
|
if (range.location != NSNotFound)
|
|
newRange = NSMakeRange(range.location + range.length, 0);
|
|
else
|
|
newRange = NSMakeRange(0, 0);
|
|
|
|
[KMManager setKMText:context];
|
|
[KMManager setKMSelectionRange:newRange manually:NO];
|
|
|
|
/*
|
|
if (textInput != nil) {
|
|
[KMManager setKMText:[textInput textInRange:[textInput textRangeFromPosition:[textInput beginningOfDocument] toPosition:[textInput endOfDocument]]]];
|
|
UITextRange *textRange = textInput.selectedTextRange;
|
|
NSRange newRange = NSMakeRange([textInput offsetFromPosition:[textInput beginningOfDocument] toPosition:textRange.start], [textInput offsetFromPosition:textRange.start toPosition:textRange.end]);
|
|
[KMManager setKMSelectionRange:newRange manually:NO];
|
|
}
|
|
*/
|
|
}
|
|
|
|
- (void)selectionDidChange:(id<UITextInput>)textInput {
|
|
/*
|
|
if (textInput != nil) {
|
|
UITextRange *textRange = textInput.selectedTextRange;
|
|
NSRange newRange = NSMakeRange([textInput offsetFromPosition:[textInput beginningOfDocument] toPosition:textRange.start], [textInput offsetFromPosition:textRange.start toPosition:textRange.end]);
|
|
[KMManager setKMSelectionRange:newRange manually:NO];
|
|
}
|
|
*/
|
|
}
|
|
|
|
#pragma mark - KMWebViewDelegate
|
|
|
|
- (void)updatedFragment:(NSString *)fragment {
|
|
if (fragment == nil || [fragment length] == 0)
|
|
return;
|
|
|
|
if ([fragment rangeOfString:@"insertText"].location != NSNotFound) {
|
|
if ([[KMManager sharedInstance] isSubKeysMenuVisible])
|
|
return;
|
|
|
|
if (self.isInputClickSoundEnabled) {
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
AudioServicesPlaySystemSound(0x450);
|
|
});
|
|
self.isInputClickSoundEnabled = NO; // Disable input click sound for 0.1 second to ensure it plays for single key stroke.
|
|
[self performSelector:@selector(enableInputClickSound) withObject:nil afterDelay:0.1f];
|
|
}
|
|
|
|
NSRange range1 = [fragment rangeOfString:@"+dn="];
|
|
NSRange range2 = [fragment rangeOfString:@"+s="];
|
|
range1.location += range1.length;
|
|
range1.length = range2.location - range1.location;
|
|
NSInteger dn = [[fragment substringWithRange:range1] integerValue];
|
|
|
|
range2.location += range2.length;
|
|
range2.length = [fragment length] - range2.location;
|
|
NSString *s = [fragment substringWithRange:range2];
|
|
|
|
NSMutableString *text = nil;
|
|
NSArray *unicodes = nil;
|
|
if ([s length])
|
|
unicodes = [s componentsSeparatedByString: @","];
|
|
NSScanner *scanner = nil;
|
|
for (int i=0; i<[unicodes count]; i++) {
|
|
scanner = [NSScanner scannerWithString:[unicodes objectAtIndex:i]];
|
|
unsigned int unicode;
|
|
[scanner scanHexInt:&unicode];
|
|
if(text == nil)
|
|
text = [NSMutableString stringWithFormat:@"%C", (unsigned short) unicode];
|
|
else
|
|
[text appendString:[NSString stringWithFormat:@"%C", (unsigned short) unicode]];
|
|
}
|
|
|
|
if (text == nil)
|
|
text = [NSMutableString stringWithString:@""];
|
|
|
|
if (dn <= 0) {
|
|
[self.textDocumentProxy insertText:text];
|
|
}
|
|
else {
|
|
if (![text length]) {
|
|
while (dn) {
|
|
NSString *xContext = [[self.textDocumentProxy documentContextBeforeInput] copy];
|
|
[self.textDocumentProxy deleteBackward];
|
|
NSString *nContext = [[self.textDocumentProxy documentContextBeforeInput] copy];
|
|
NSInteger dif = xContext.length - nContext.length;
|
|
if (dif > 1) {
|
|
unichar c = [xContext characterAtIndex:xContext.length - 1];
|
|
if (![self isSurrogate:c] && xContext.length >= 1)
|
|
[self.textDocumentProxy insertText:[xContext substringWithRange:NSMakeRange(nContext.length, dif - 1)]];
|
|
}
|
|
|
|
dn--;
|
|
}
|
|
}
|
|
else {
|
|
while (dn) {
|
|
NSString *xContext = [[self.textDocumentProxy documentContextBeforeInput] copy];
|
|
[self.textDocumentProxy deleteBackward];
|
|
NSString *nContext = [[self.textDocumentProxy documentContextBeforeInput] copy];
|
|
NSInteger dif = xContext.length - nContext.length;
|
|
if (dif > 1) {
|
|
unichar c = [xContext characterAtIndex:xContext.length - 1];
|
|
if (![self isSurrogate:c] && xContext.length >= 1)
|
|
[self.textDocumentProxy insertText:[xContext substringWithRange:NSMakeRange(nContext.length, dif - 1)]];
|
|
}
|
|
|
|
dn--;
|
|
}
|
|
|
|
[self.textDocumentProxy insertText:text];
|
|
}
|
|
}
|
|
}
|
|
else if ([fragment rangeOfString:@"menuKeyUp"].location != NSNotFound) {
|
|
if ([[KMManager sharedInstance] isKeyboardMenuVisible])
|
|
return;
|
|
|
|
if (self.globeKeyTapBehaviour == GKTapSwitchToNextKeyboard) {
|
|
NSInteger nextIndex = [[KMManager sharedInstance] switchToNextKeyboard];
|
|
if (nextIndex < 0 || nextIndex == 0)
|
|
[self advanceToNextInputMode];
|
|
}
|
|
else if (self.globeKeyTapBehaviour == GKTapSwitchToNextInputMethod) {
|
|
[self advanceToNextInputMode];
|
|
}
|
|
}
|
|
else if ([fragment rangeOfString:@"showKeyboardMenu"].location != NSNotFound) {
|
|
if (self.menuBehaviour == MenuShowAlways) {
|
|
[[KMManager sharedInstance] showKeyboardMenu:self closeButtonTitle:self.menuCloseButtonTitle];
|
|
}
|
|
else if (self.menuBehaviour == MenuShowIfMultipleKeyboards) {
|
|
if ([self keyboardListCount] > 1) {
|
|
[[KMManager sharedInstance] showKeyboardMenu:self closeButtonTitle:self.menuCloseButtonTitle];
|
|
}
|
|
}
|
|
}
|
|
else if ([fragment rangeOfString:@"hideKeyboard"].location != NSNotFound) {
|
|
[self dismissKeyboard];
|
|
}
|
|
}
|
|
|
|
- (void)setConstraints {
|
|
NSDictionary *viewsDict = @{@"bView":self.topBarImageView, @"cView":self.containerView};
|
|
NSString *vFormat = [NSString stringWithFormat:@"V:[bView(%lu)]", (unsigned long)(self.isTopBarEnabled?[[self class] topBarHeight]:0)];
|
|
bView_constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
|
|
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
|
|
vFormat = [NSString stringWithFormat:@"H:[bView(>=%lu)]", (unsigned long)screenWidth];
|
|
NSArray *bView_constraint_V = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
|
|
vFormat = @"V:|-0-[bView]";
|
|
NSArray *bView_constraint_POS_V = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
vFormat = @"H:|-0-[bView]";
|
|
NSArray *bView_constraint_POS_HL = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
vFormat = @"H:[bView]-0-|";
|
|
bView_constraint_POS_HR = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
[self.view addConstraints:bView_constraint_V];
|
|
[self.view addConstraints:bView_constraint_H];
|
|
[self.view addConstraints:bView_constraint_POS_V];
|
|
[self.view addConstraints:bView_constraint_POS_HL];
|
|
|
|
CGSize size = self.kmInputView.frame.size;
|
|
vFormat = [NSString stringWithFormat:@"V:[cView(%d)]", (int)size.height%1000]; // note this appears to relate to the rotation fix bug; may not work on iPads as 1000 is not enough
|
|
cView_constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
|
|
vFormat = [NSString stringWithFormat:@"H:[cView(>=%lu)]", (unsigned long)screenWidth];
|
|
NSArray *cView_constraint_V = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
|
|
vFormat = @"V:[cView]-0-|";
|
|
NSArray *cView_constraint_POS_V = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
vFormat = @"H:|-0-[cView]";
|
|
NSArray *cView_constraint_POS_HL = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
vFormat = @"H:[cView]-0-|";
|
|
cView_constraint_POS_HR = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:viewsDict];
|
|
[self.view addConstraints:cView_constraint_V];
|
|
[self.view addConstraints:cView_constraint_H];
|
|
[self.view addConstraints:cView_constraint_POS_V];
|
|
[self.view addConstraints:cView_constraint_POS_HL];
|
|
|
|
/*
|
|
if (self.debugButton != nil) {
|
|
vFormat = @"H:[dButton]-10-|";
|
|
NSArray *dButton_constraint_POS_HR = [NSLayoutConstraint constraintsWithVisualFormat:vFormat
|
|
options:0
|
|
metrics:nil
|
|
views:@{@"dButton":self.debugButton}];
|
|
[self.view addConstraints:dButton_constraint_POS_HR];
|
|
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.debugButton
|
|
attribute:NSLayoutAttributeCenterY
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:self.topBarImageView
|
|
attribute:NSLayoutAttributeCenterY
|
|
multiplier:1.0
|
|
constant:0.0]];
|
|
}*/
|
|
|
|
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.view
|
|
attribute:NSLayoutAttributeHeight
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:nil
|
|
attribute:NSLayoutAttributeNotAnAttribute
|
|
multiplier:0.0
|
|
constant:[self expandedHeight]];
|
|
[self.heightConstraint setPriority:999];
|
|
}
|
|
|
|
/*
|
|
- (void)advanceToNextInputMode {
|
|
self.startIndex = [self getStartIndex];
|
|
[super advanceToNextInputMode];
|
|
}
|
|
|
|
- (void)keyboardLoaded:(NSNotification *)notification {
|
|
self.startIndex = [self getStartIndex];
|
|
}
|
|
|
|
- (NSUInteger)getStartIndex {
|
|
NSUInteger startIndex = 0;
|
|
NSUserDefaults *userData = [[KMManager sharedInstance] activeUserDefaults];
|
|
NSMutableArray *keyboardList = [[userData arrayForKey:kKeymanUserKeyboardsListKey] mutableCopy];
|
|
for (int i = 0; i < keyboardList.count; i++) {
|
|
NSString *languageID = [[keyboardList objectAtIndex:i] objectForKey:kKeymanLanguageIdKey];
|
|
NSString *keyboardID = [[keyboardList objectAtIndex:i] objectForKey:kKeymanKeyboardIdKey];
|
|
if ([[KMManager sharedInstance].languageID isEqualToString:languageID] && [[KMManager sharedInstance].keyboardID isEqualToString:keyboardID]) {
|
|
startIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return startIndex;
|
|
}
|
|
*/
|
|
|
|
- (NSUInteger)keyboardListCount {
|
|
NSUserDefaults *userData = [[KMManager sharedInstance] activeUserDefaults];
|
|
NSArray *keyboardList = [userData arrayForKey:kKeymanUserKeyboardsListKey];
|
|
return keyboardList.count;
|
|
}
|
|
|
|
+ (NSUInteger)topBarHeight {
|
|
UIUserInterfaceIdiom uiIdiom = [[UIDevice currentDevice] userInterfaceIdiom];
|
|
if ([[self class] isPortrait])
|
|
return (uiIdiom == UIUserInterfaceIdiomPhone?41:41);
|
|
else
|
|
return (uiIdiom == UIUserInterfaceIdiomPhone?34:39);
|
|
}
|
|
|
|
- (CGFloat)expandedHeight {
|
|
CGSize size = self.kmInputView.frame.size;
|
|
return (int)size.height%1000 + (self.isTopBarEnabled?[[self class] topBarHeight]:0);
|
|
}
|
|
|
|
- (BOOL)hasFullAccess {
|
|
return [[KMManager sharedInstance] canAccessSharedContainer];
|
|
}
|
|
|
|
+ (BOOL)isPortrait {
|
|
if ([UIScreen mainScreen].bounds.size.width < [UIScreen mainScreen].bounds.size.height)
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
|
|
- (void)enableInputClickSound {
|
|
self.isInputClickSoundEnabled = YES;
|
|
}
|
|
|
|
/*
|
|
- (void)viewDebugLog:(id)sender {
|
|
UIPasteboard *pb = [UIPasteboard generalPasteboard];
|
|
[pb setString:[KMManager sharedInstance].debugLog];
|
|
}*/
|
|
|
|
- (BOOL)isSurrogate:(unichar)c {
|
|
if (c >= 0xD800 && c <= 0xDFFF)
|
|
return YES;
|
|
|
|
return NO;
|
|
}
|
|
|
|
@end
|