spiegel-keyman/ios/engine/KMEI/KeymanEngine/Classes/KMManager.m
2017-08-21 13:54:29 +07:00

4099 lines
192 KiB
Objective-C

//
// KMManager.m
// Keyman Engine
//
// Created by Patrick Weekes on 12-05-16.
// Updated by Serkan Kurt on 17/05/2013.
// Copyright (c) 2017 SIL International. All rights reserved.
//
#import "KMManager.h"
#import "KMManager+Internal.h"
#import "KMInputViewController.h"
#import "KMURLProtocol.h"
#import "KMTextField.h"
#import "KMTextView.h"
#import "KMPopoverView.h"
#import "KMKeyPreviewView.h"
#import "KMSubKeysView.h"
#import "KMKeyboardMenuView.h"
#import "KeyboardPickerViewController.h"
#import "UIView+Helpers.h"
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>
#import <CommonCrypto/CommonDigest.h>
#import "UIButton+Helpers.h"
#import <objc/message.h>
#pragma mark - Constants
static BOOL enableWebKit = YES;
// Keyman system-wide keyboard
static BOOL kIsKeymanSystemKeyboard = NO;
// Group Identifier
static NSString *applicationGroupIdentifier = @"";
// Notifications
NSString *const kKeymanLanguagesUpdatedNotification = @"KeymanLanguagesUpdatedNotification";
NSString *const kKeymanLanguagesDownloadFailedNotification = @"kKeymanLanguagesDownloadFailedNotification";
NSString *const kKeymanKeyboardDownloadStartedNotification = @"kKeymanKeyboardDownloadStartedNotification";
NSString *const kKeymanKeyboardDownloadCompletedNotification = @"kKeymanKeyboardDownloadCompletedNotification";
NSString *const kKeymanKeyboardDownloadFailedNotification = @"kKeymanKeyboardDownloadFailedNotification";
NSString *const kKeymanKeyboardChangedNotification = @"kKeymanKeyboardChangedNotification";
NSString *const kKeymanKeyboardLoadedNotification = @"kKeymanKeyboardLoadedNotification";
NSString *const kKeymanKeyboardPickerDismissedNotification = @"kKeymanKeyboardPickerDismissedNotification";
NSString *const kKeymanKeyboardRemovedNotification = @"kKeymanKeyboardRemovedNotification";
NSString *const kKeymanSubKeysMenuWillShowNotification = @"kKeymanSubKeysMenuWillShowNotification";
NSString *const kKeymanSubKeysMenuDismissedNotification = @"kKeymanSubKeysMenuDismissedNotification";
NSString *const kKeymanDebugLogNotification = @"kKeymanDebugLogNotification";
// External dictionary keys
NSString *const kKeymanIdKey = @"id";
NSString *const kKeymanNameKey = @"name";
NSString *const kKeymanKeyboardIdKey = @"kbId";
NSString *const kKeymanLanguageIdKey = @"langId";
NSString *const kKeymanKeyboardNameKey = @"kbName";
NSString *const kKeymanLanguageNameKey = @"langName";
NSString *const kKeymanKeyboardVersionKey = @"version";
NSString *const kKeymanKeyboardKey = @"keyboard";
NSString *const kKeymanLanguageKeyboardsKey = @"keyboards";
NSString *const kKeymanKeyboardFileSizeKey = @"fileSize";
NSString *const kKeymanKeyboardGZipFileSizeKey = @"fileSizeGzip";
NSString *const kKeymanFontKey = @"font";
NSString *const kKeymanOskFontKey = @"oskFont";
NSString *const kKeymanFontFamilyKey = @"family";
NSString *const kKeymanFontSourceKey = @"source";
NSString *const kKeymanFontFilesKey = @"files";
NSString *const kKeymanFontFilenameKey = @"filename"; // Font filename is deprecated
NSString *const kKeymanFontNameKey = @"fontname";
NSString *const kKeymanFontRegisteredKey = @"fontregistered";
NSString *const kKeymanKeyboardFilenameKey = @"filename";
NSString *const kKeymanKeyboardModifiedKey = @"lastModified";
NSString *const kKeymanKeyboardRTLKey = @"rtl";
NSString *const kKeymanKeyboardInfoKey = @"keyboardInfo";
NSString *const kKeymanCustomKeyboardKey = @"CustomKeyboard";
// External user defaults keys
NSString *const kKeymanUserKeyboardsListKey = @"UserKeyboardsList";
NSString *const kKeymanUserCurrentKeyboardKey = @"UserCurrentKeyboard";
// Internal user defaults keys
NSString *const kKeymanEngineVersion = @"KeymanEngineVersion";
NSString *const kKeymanKeyboardPickerDisplayedKey = @"KeyboardPickerDisplayed"; // stores YES if the keyboard picker has been displayed at least once > BOOL
NSString *const kKeymanSynchronizeSWKeyboardKey = @"KeymanSynchronizeSWKeyboard";
// Views
static id webView = nil;
static KMPopoverView *helpBubbleView = nil;
static KMKeyPreviewView *keyPreviewView = nil;
static KMSubKeysView *subKeysView = nil;
static KMKeyboardMenuView *keyboardMenuView = nil;
// Arrays
static NSMutableArray *subKeyIDs = nil;
static NSMutableArray *subKeyTexts = nil;
static NSMutableArray *subKeys = nil;
// Key frames
static CGRect keyFrame;
static CGRect menuKeyFrame;
// Dictionaries
NSMutableDictionary *options_ = nil;
// Strings
static NSString *const kKeymanKeyboardChangeHelpText = @"Tap here to change keyboard";
static NSString *specialOSKFont = nil;
// URLs and Filenames
NSString *const kKeymanApiBaseURL = @"https://r.keymanweb.com/api/3.0/";
static NSString *const kKeymanApiRemoteURL = @"https://r.keymanweb.com/api/2.0/remote?url=";
static NSString *const kKeymanFileName = @"keyboard";
static NSString *const kKeymanFileExtension = @"html";
static NSString *const kKeymaniOSCodeFileName = @"keymanios.js";
static NSString *const kKeymanHostName = @"r.keymanweb.com";
#define kKeymanFullFileName [NSString stringWithFormat:@"%@.%@", kKeymanFileName, kKeymanFileExtension]
// External Default Keyboard Info
NSString *const kKeymanDefaultKeyboardID = @"european2";
NSString *const kKeymanDefaultLanguageID = @"eng";
NSString *const kKeymanDefaultKeyboardName = @"EuroLatin2 Keyboard";
NSString *const kKeymanDefaultLanguageName = @"English";
NSString *const kKeymanDefaultKeyboardRTL = @"N";
NSString *const kKeymanDefaultKeyboardFont = @"{\"family\":\"LatinWeb\",\"files\":[\"DejaVuSans.ttf\",\"DejaVuSans.mobileconfig\"]}";
// JSON keys for language REST calls
NSString *const kKeymanOptionsKey = @"options";
NSString *const kKeymanLanguageKey = @"language";
// *** TO DO: Check if it matches with the key in Keyman Cloud API ***
NSString *const kKeymanKeyboardCopyrightKey = @"copyright";
//
static NSString *const kKeymanLanguagesKey = @"languages";
static NSString *const kKeymanKeyboardBaseURIKey = @"keyboardBaseUri";
static NSString *const kKeymanFontBaseURIKey = @"fontBaseUri";
static NSString *const kKeymanKeyboardURIKey = @"uri";
// Other keys
static NSString *const kKeymanUpdateKey = @"update";
// UI In-App Keyboard Constants
static CGFloat kKeymanPhonePortraitInAppKeyboardHeight = 183.0f;
static CGFloat kKeymanPhoneLandscapeInAppKeyboardHeight = 183.0f;
static CGFloat kKeymanPadPortraitInAppKeyboardHeight = 385.0f;
static CGFloat kKeymanPadLandscapeInAppKeyboardHeight = 385.0f;
// UI System Keyboard Constants
static CGFloat kKeymanPhonePortraitSystemKeyboardHeight = 216.0f;
static CGFloat kKeymanPhoneLandscapeSystemKeyboardHeight = 162.0f;
static CGFloat kKeymanPadPortraitSystemKeyboardHeight = 264.0f;
static CGFloat kKeymanPadLandscapeSystemKeyboardHeight = 352.0f;
// Sub key colors
static UIColor *subKeyColor;
static UIColor *subKeyColorHighlighted;
BOOL didResizeToOrientation = NO;
#pragma mark - Private Interface
@interface KMManager ()
// Keyboard Info
+ (CGFloat)keyboardHeight;
+ (CGFloat)keyboardHeightWithOrientation:(UIInterfaceOrientation)orientation;
+ (CGFloat)keyboardWidth;
// Local file storage
- (void)copyWebFilesToLibrary;
- (NSString *)defaultKeymanDirectory;
- (NSString *)defaultLanguageDirectory;
- (NSString *)defaultFontDirectory;
- (NSString *)keyboardPathForID:(NSString *)keyboardID keyboardVersion:(NSString *)kbVersion;
// Keyman interaction
- (void)resizeKeyboard;
- (UIScrollView *)keymanScrollView;
// Misc
- (void)createKeyboardsInfo;
//- (NSDate *)dateFromKeymanDateString:(NSString *)dateString; // No longer used
@end
#pragma mark - Implementation
@implementation KMManager
CWL_SYNTHESIZE_SINGLETON_FOR_CLASS_WITH_ACCESSOR(KMManager, sharedInstance);
@synthesize debugPrintingOn = debugPrintingOn_;
@synthesize keymanHelpOn = keymanHelpOn_;
@synthesize canAddNewKeyboards = canAddNewKeyboards_;
@synthesize canRemoveKeyboards = canRemoveKeyboards_;
@synthesize canRemoveDefaultKeyboard = canRemoveDefaultKeyboard_;
@synthesize languageID = languageID_;
@synthesize keyboardID = keyboardID_;
@synthesize webDelegate = webDelegate_;
@synthesize inputDelegate = inputDelegate_;
@synthesize downloadQueue = downloadQueue_;
@synthesize sharedQueue = sharedQueue_;
@synthesize currentRequest = currentRequest_;
@synthesize reachability = reachability_;
@synthesize languages = languages_;
@synthesize options = options_;
@synthesize keyboardsInfo = keyboardsInfo_;
@synthesize keyboardsDictionary = keyboardsDictionary_;
@synthesize keymanFonts = keymanFonts_;
@synthesize shouldReloadKeyboard;
@synthesize didSynchronize;
#pragma mark - Object Admin
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
if(currentRequest_.userInfo) {
[currentRequest_.userInfo setValue:nil forKey:@"completionBlock"];
}
webView = nil;
subKeys = nil;
}
- (id)init {
if (self = [super init]) {
self.debugPrintingOn = NO;
self.keymanHelpOn = YES;
self.canAddNewKeyboards = YES;
self.canRemoveKeyboards = YES;
self.canRemoveDefaultKeyboard = NO;
subKeyColor = [UIColor colorWithRed:244.0/255.0 green:244.0/255.0 blue:244.0/255.0 alpha:1.0];
subKeyColorHighlighted = [UIColor colorWithRed:136.0/255.0 green:136.0/255.0 blue:255.0/255.0 alpha:1.0];
[NSURLProtocol registerClass:[KMURLProtocol class]];
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSDictionary *extensionInfo = [infoDict objectForKey:@"NSExtension"];
NSString *extensionID = [extensionInfo objectForKey:@"NSExtensionPointIdentifier"];
if ([extensionID isEqualToString:@"com.apple.keyboard-service"])
kIsKeymanSystemKeyboard = YES;
int iosVersion = [[[UIDevice currentDevice] systemVersion] intValue];
if (iosVersion < 9)
enableWebKit = NO;
if (!kIsKeymanSystemKeyboard) {
[self copyUserDefaultsToSharedContainer];
[self copyKeymanFilesToSharedContainer];
NSUserDefaults *userData = [self activeUserDefaults];
BOOL isKPDisplayed = [userData boolForKey:kKeymanKeyboardPickerDisplayedKey];
if (isKPDisplayed) {
self.keymanHelpOn = NO;
}
}
else {
self.keymanHelpOn = NO;
}
[self copyWebFilesToLibrary];
[self renameOldKeyboardFiles];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
/* No longer used
dateFormatter_ = [[NSDateFormatter alloc] init];
[self.dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZ"];
[self.dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
*/
NSString *kbVersion = [self latestKeyboardFileVersionWithID:kKeymanDefaultKeyboardID];
[self updateKeyboardVersionForID:kKeymanDefaultKeyboardID newKeyboardVersion:kbVersion];
[[self class] inputView]; // Pre-load keyboard
// Set UILongPressGestureRecognizer to show sub keys
UILongPressGestureRecognizer *hold = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:@selector(holdAction:)];
hold.minimumPressDuration = 0.5f;
hold.delegate = (id)self;
[[[self class] inputView] addGestureRecognizer:hold];
self.reachability = [Reachability reachabilityWithHostName:kKeymanHostName];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:self.reachability];
[self.reachability startNotifier];
/* KMHTTPDownloader only uses this for its delegate methods. So long as we don't
* set the queue running, this should be perfectly fine.
*/
self.sharedQueue = [[KMHTTPDownloader alloc] init:self];
[self registerCustomFonts];
}
return self;
}
+ (BOOL)isKeymanSystemKeyboard {
return kIsKeymanSystemKeyboard;
}
+ (BOOL)isWebKitEnabled {
return (NSClassFromString(@"WKWebView") && enableWebKit);
}
#pragma mark - Keyboard Notifications
- (void)keyboardWillShow:(NSNotification *)notification {
[self dismissSubKeys]; // dismiss sub keys window
[self dismissKeyPreview]; // dismiss key preview window
[self resizeKeyboard];
if (self.keymanHelpOn) {
[helpBubbleView removeFromSuperview];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showHelpBubble) object:nil];
[self performSelector:@selector(showHelpBubble) withObject:nil afterDelay:1.5];
}
}
- (void)keyboardWillHide:(NSNotification *)notification {
[self dismissHelpBubble];
[self dismissSubKeys]; // dismiss sub keys view
[self dismissKeyPreview]; // dismiss key preview window
}
#pragma mark - Public Methods
- (BOOL)setKeyboardWithID:(NSString *)keyboardID languageID:(NSString *)languageID {
if (languageID != languageID_ || keyboardID != keyboardID_) {
[self KMLog:[NSString stringWithFormat:@"Setting language: %@_%@", languageID, keyboardID] checkDebugPrinting:YES];
if ([self usingTempFolder])
[self copyKeymanFilesToTemp];
if ([keyboardID length] && [languageID length]) {
eKMKeyboardState kbState = [[KMManager sharedInstance] stateForKeyboardWithID:keyboardID];
if (kbState == kKMKeyboardStateNeedsDownload) {
[self KMLog:[NSString stringWithFormat:@"Could not set keyboardID to %@ because the keyboard file does not exist", keyboardID] checkDebugPrinting:NO];
if ((keyboardID_ == nil || languageID_ == nil) && ![keyboardID isEqualToString:kKeymanDefaultKeyboardID]) {
[self setKeyboardWithID:kKeymanDefaultKeyboardID
languageID:kKeymanDefaultLanguageID
keyboardName:kKeymanDefaultKeyboardName
languageName:kKeymanDefaultLanguageName
font:kKeymanDefaultKeyboardFont
oskFont:nil];
}
return NO;
}
languageID_ = [languageID copy];
keyboardID_ = [keyboardID copy];
NSString *key = [NSString stringWithFormat:@"%@_%@", self.languageID, self.keyboardID];
NSUserDefaults *userData = [self activeUserDefaults];
NSArray *userKeyboards = [userData arrayForKey:kKeymanUserKeyboardsListKey];
NSInteger index = [self indexForUserKeyboardWithID:self.keyboardID
languageID:self.languageID];
NSDictionary *kbDict = nil;
if (index >= 0)
kbDict = [userKeyboards objectAtIndex:index];
else
kbDict = [self.keyboardsDictionary objectForKey:key];
NSString *langName = [kbDict objectForKey:kKeymanLanguageNameKey];
NSString *kbName = [kbDict objectForKey:kKeymanKeyboardNameKey];
NSString *kbVersion = [self latestKeyboardFileVersionWithID:keyboardID];
if (kbVersion == nil)
kbVersion = @"1.0";
NSString *isRTL = [kbDict objectForKey:kKeymanKeyboardRTLKey];
if ([key isEqualToString:[NSString stringWithFormat:@"%@_%@", kKeymanDefaultLanguageID, kKeymanDefaultKeyboardID]]) {
if (langName == nil || kbName == nil) {
langName = kKeymanDefaultLanguageName;
kbName = kKeymanDefaultKeyboardName;
}
}
NSString *jsFont = [self jsFontForKeyboardID:keyboardID languageID:languageID];
NSString *jsOskFont = [self jsOskFontForKeyboardID:keyboardID languageID:languageID];
if (jsOskFont == nil || [jsOskFont isEqualToString:@"''"])
jsOskFont = jsFont;
langName = [langName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
kbName = [kbName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
if ([jsFont isEqualToString:@"''"])
jsFont = @"undefined";
if ([jsOskFont isEqualToString:@"''"])
jsOskFont = @"undefined";
NSString *jsString = [NSString stringWithFormat:@"setKeymanLanguage('%@','%@','%@','%@','%@',%@,%@);", kbName, self.keyboardID, langName, self.languageID, kbVersion, jsFont, jsOskFont];
[self KMLog:[NSString stringWithFormat:@"Evaluating JavaScript: %@", jsString] checkDebugPrinting:YES];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
if (kIsKeymanSystemKeyboard)
userData = [NSUserDefaults standardUserDefaults];
else
userData = [self activeUserDefaults];
[userData setObject:[NSDictionary dictionaryWithObjectsAndKeys:
self.keyboardID, kKeymanKeyboardIdKey,
self.languageID, kKeymanLanguageIdKey,
[kbName stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"], kKeymanKeyboardNameKey,
[langName stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"], kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
(isRTL != nil)?isRTL:@"N", kKeymanKeyboardRTLKey,
jsFont, kKeymanFontKey,
nil] forKey:kKeymanUserCurrentKeyboardKey];
[userData synchronize];
if (self.keymanHelpOn) {
[helpBubbleView removeFromSuperview];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showHelpBubble) object:nil];
[self performSelector:@selector(showHelpBubble) withObject:nil afterDelay:1.5];
}
NSDictionary *kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:
keyboardID, kKeymanKeyboardIdKey,
languageID, kKeymanLanguageIdKey,
kbName, kKeymanKeyboardNameKey,
langName, kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
jsFont, kKeymanFontKey,
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardChangedNotification object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:kbInfo, kKeymanKeyboardInfoKey, nil]];
return YES;
}
}
return NO;
}
- (BOOL)setKeyboardWithID:(NSString *)keyboardID languageID:(NSString *)languageID keyboardName:(NSString *)keyboardName languageName:(NSString *)languageName {
// if keyboardName or languageName is nil call setKeyboardWithID without these parameters
if (keyboardName == nil || languageName == nil)
return [self setKeyboardWithID:keyboardID languageID:languageID];
if (languageID != languageID_ || keyboardID != keyboardID_) {
[self KMLog:[NSString stringWithFormat:@"Setting language: %@_%@", languageID, keyboardID] checkDebugPrinting:YES];
if ([self usingTempFolder])
[self copyKeymanFilesToTemp];
if ([keyboardID length] && [languageID length]) {
eKMKeyboardState kbState = [[KMManager sharedInstance] stateForKeyboardWithID:keyboardID];
if (kbState == kKMKeyboardStateNeedsDownload) {
[self KMLog:[NSString stringWithFormat:@"Could not set keyboardID to %@ because the keyboard file does not exist", keyboardID] checkDebugPrinting:NO];
if ((keyboardID_ == nil || languageID_ == nil) && ![keyboardID isEqualToString:kKeymanDefaultKeyboardID]) {
[self setKeyboardWithID:kKeymanDefaultKeyboardID
languageID:kKeymanDefaultLanguageID
keyboardName:kKeymanDefaultKeyboardName
languageName:kKeymanDefaultLanguageName
font:kKeymanDefaultKeyboardFont
oskFont:nil];
}
return NO;
}
languageID_ = [languageID copy];
keyboardID_ = [keyboardID copy];
NSString *key = [NSString stringWithFormat:@"%@_%@", self.languageID, self.keyboardID];
NSUserDefaults *userData = [self activeUserDefaults];
NSArray *userKeyboards = [userData arrayForKey:kKeymanUserKeyboardsListKey];
NSInteger index = [self indexForUserKeyboardWithID:self.keyboardID
languageID:self.languageID];
NSDictionary *kbDict = nil;
if (index >= 0)
kbDict = [userKeyboards objectAtIndex:index];
else
kbDict = [self.keyboardsDictionary objectForKey:key];
NSString *kbVersion = [self latestKeyboardFileVersionWithID:keyboardID];
if (kbVersion == nil)
kbVersion = @"1.0";
NSString *isRTL = [kbDict objectForKey:kKeymanKeyboardRTLKey];
NSString *jsFont = [self jsFontForKeyboardID:keyboardID languageID:languageID];
NSString *jsOskFont = [self jsOskFontForKeyboardID:keyboardID languageID:languageID];
if (jsOskFont == nil || [jsOskFont isEqualToString:@"''"])
jsOskFont = jsFont;
NSString *langName = [NSString stringWithString:[languageName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]];
NSString *kbName = [NSString stringWithString:[keyboardName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]];
if ([jsFont isEqualToString:@"''"])
jsFont = @"undefined";
if ([jsOskFont isEqualToString:@"''"])
jsOskFont = @"undefined";
NSString *jsString = [NSString stringWithFormat:@"setKeymanLanguage('%@','%@','%@','%@','%@',%@,%@);", kbName, self.keyboardID, langName, self.languageID, kbVersion, jsFont, jsOskFont];
[self KMLog:[NSString stringWithFormat:@"Evaluating JavaScript: %@", jsString] checkDebugPrinting:YES];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
if (kIsKeymanSystemKeyboard)
userData = [NSUserDefaults standardUserDefaults];
else
userData = [self activeUserDefaults];
[userData setObject:[NSDictionary dictionaryWithObjectsAndKeys:
self.keyboardID, kKeymanKeyboardIdKey,
self.languageID, kKeymanLanguageIdKey,
[kbName stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"], kKeymanKeyboardNameKey,
[langName stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"], kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
(isRTL != nil)?isRTL:@"N", kKeymanKeyboardRTLKey,
jsFont, kKeymanFontKey,
nil] forKey:kKeymanUserCurrentKeyboardKey];
[userData synchronize];
if (self.keymanHelpOn) {
[helpBubbleView removeFromSuperview];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showHelpBubble) object:nil];
[self performSelector:@selector(showHelpBubble) withObject:nil afterDelay:1.5];
}
NSDictionary *kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:
keyboardID, kKeymanKeyboardIdKey,
languageID, kKeymanLanguageIdKey,
keyboardName, kKeymanKeyboardNameKey,
langName, kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
jsFont, kKeymanFontKey,
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardChangedNotification
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:kbInfo, kKeymanKeyboardInfoKey, nil]];
return YES;
}
}
return NO;
}
- (BOOL)setKeyboardWithID:(NSString *)keyboardID languageID:(NSString *)languageID keyboardName:(NSString *)keyboardName languageName:(NSString *)languageName font:(NSString *)font oskFont:(NSString *)oskFont {
// if the font and the oskFont are both nil call setKeyboardWithID without these parameters
if (font == nil && oskFont == nil)
return [self setKeyboardWithID:keyboardID languageID:languageID keyboardName:keyboardName languageName:languageName];
if (keyboardID != keyboardID_ || languageID != languageID_) {
[self KMLog:[NSString stringWithFormat:@"Setting language: %@_%@", languageID, keyboardID] checkDebugPrinting:YES];
if ([self usingTempFolder])
[self copyKeymanFilesToTemp];
if ([keyboardID length] && [languageID length]) {
eKMKeyboardState kbState = [[KMManager sharedInstance] stateForKeyboardWithID:keyboardID];
if (kbState == kKMKeyboardStateNeedsDownload) {
[self KMLog:[NSString stringWithFormat:@"Could not set keyboardID to %@ because the keyboard file does not exist", keyboardID] checkDebugPrinting:NO];
if ((keyboardID_ == nil || languageID_ == nil) && ![keyboardID isEqualToString:kKeymanDefaultKeyboardID]) {
[self setKeyboardWithID:kKeymanDefaultKeyboardID
languageID:kKeymanDefaultLanguageID
keyboardName:kKeymanDefaultKeyboardName
languageName:kKeymanDefaultLanguageName
font:kKeymanDefaultKeyboardFont
oskFont:nil
];
}
return NO;
}
keyboardID_ = [keyboardID copy];
languageID_ = [languageID copy];
NSString *key = [NSString stringWithFormat:@"%@_%@", self.languageID, self.keyboardID];
NSUserDefaults *userData = [self activeUserDefaults];
NSArray *userKeyboards = [userData arrayForKey:kKeymanUserKeyboardsListKey];
NSInteger index = [self indexForUserKeyboardWithID:self.keyboardID
languageID:self.languageID];
NSDictionary *kbDict = nil;
if (index >= 0)
kbDict = [userKeyboards objectAtIndex:index];
else
kbDict = [self.keyboardsDictionary objectForKey:key];
NSString *kbVersion = [self latestKeyboardFileVersionWithID:keyboardID];
if (kbVersion == nil)
kbVersion = @"1.0";
NSString *jsFont = nil;
if ([font rangeOfString:@".ttf"].location != NSNotFound || [font rangeOfString:@".otf"].location != NSNotFound) {
if ([[font substringFromIndex:font.length-4] isEqualToString:@".ttf"] || [[font substringFromIndex:font.length-4] isEqualToString:@".otf"]) {
NSString *familyName = [NSString stringWithFormat:@"font_family_%@", [font substringToIndex:font.length-4]];
jsFont = ([font length])?[NSString stringWithFormat:@"{\"family\":\"%@\",\"files\":[\"%@\"]}", familyName, font]:@"''";
}
else
jsFont = ([font length])?font:@"''";
}
else
jsFont = @"''";
NSString *jsOskFont = nil;
if (oskFont == nil || [oskFont isEqualToString:@"''"]) {
jsOskFont = jsFont;
}
else {
if ([oskFont rangeOfString:@".ttf"].location != NSNotFound || [oskFont rangeOfString:@".otf"].location != NSNotFound) {
if ([[oskFont substringFromIndex:oskFont.length-4] isEqualToString:@".ttf"] || [[oskFont substringFromIndex:oskFont.length-4] isEqualToString:@".otf"]) {
NSString *familyName = [NSString stringWithFormat:@"font_family_%@", [oskFont substringToIndex:oskFont.length-4]];
jsOskFont = ([oskFont length])?[NSString stringWithFormat:@"{\"family\":\"%@\",\"files\":[\"%@\"]}", familyName, oskFont]:@"''";
}
else
jsOskFont = ([oskFont length])?oskFont:@"''";
}
else
jsOskFont = @"''";
}
NSString *isRTL = [kbDict objectForKey:kKeymanKeyboardRTLKey];
NSString *langName = [NSString stringWithString:[languageName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]];
NSString *kbName = [NSString stringWithString:[keyboardName stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]];
if ([jsFont isEqualToString:@"''"])
jsFont = @"undefined";
if ([jsOskFont isEqualToString:@"''"])
jsOskFont = @"undefined";
NSString *jsString = [NSString stringWithFormat:@"setKeymanLanguage('%@','%@','%@','%@','%@',%@,%@);", kbName, self.keyboardID, langName, self.languageID, kbVersion, jsFont, jsOskFont];
[self KMLog:[NSString stringWithFormat:@"Evaluating JavaScript: %@", jsString] checkDebugPrinting:YES];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
if (kIsKeymanSystemKeyboard)
userData = [NSUserDefaults standardUserDefaults];
else
userData = [self activeUserDefaults];
[userData setObject:[NSDictionary dictionaryWithObjectsAndKeys:
self.keyboardID, kKeymanKeyboardIdKey,
self.languageID, kKeymanLanguageIdKey,
[kbName stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"], kKeymanKeyboardNameKey,
[langName stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"], kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
(isRTL != nil)?isRTL:@"N", kKeymanKeyboardRTLKey,
jsFont, kKeymanFontKey,
nil] forKey:kKeymanUserCurrentKeyboardKey];
[userData synchronize];
if (self.keymanHelpOn) {
[helpBubbleView removeFromSuperview];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showHelpBubble) object:nil];
[self performSelector:@selector(showHelpBubble) withObject:nil afterDelay:1.5];
}
NSDictionary *kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:
keyboardID, kKeymanKeyboardIdKey,
languageID, kKeymanLanguageIdKey,
keyboardName, kKeymanKeyboardNameKey,
langName, kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
jsFont, kKeymanFontKey,
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardChangedNotification
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:kbInfo, kKeymanKeyboardInfoKey, nil]];
return YES;
}
}
return NO;
}
- (NSInteger)switchToNextKeyboard {
NSInteger index = [self currentKeyboardIndex];
if (index < 0)
return index;
NSUserDefaults *userData = [self activeUserDefaults];
NSArray *userKeyboards = [userData arrayForKey:kKeymanUserKeyboardsListKey];
NSUInteger len = [userKeyboards count];
if (len > 1) {
index++;
index = index%len;
NSDictionary *kb = [userKeyboards objectAtIndex:index];
NSString *kbId = [kb objectForKey:kKeymanKeyboardIdKey];
NSString *langId = [kb objectForKey:kKeymanLanguageIdKey];
NSString *kbName = [kb objectForKey:kKeymanKeyboardNameKey];
NSString *langName = [kb objectForKey:kKeymanLanguageNameKey];
NSString *font = [kb objectForKey:kKeymanFontKey];
NSString *oskFont = [kb objectForKey:kKeymanOskFontKey];
[self setKeyboardWithID:kbId languageID:langId keyboardName:kbName languageName:langName font:font oskFont:oskFont];
}
return index;
}
- (NSInteger)currentKeyboardIndex {
NSUserDefaults *userData = [self activeUserDefaults];
NSArray *userKeyboards = [userData arrayForKey:kKeymanUserKeyboardsListKey];
NSUInteger len = [userKeyboards count];
if (len < 1)
return -1;
NSDictionary *kb = nil;
NSInteger index = -1;
for (int i = 0; i < len; i++) {
kb = [userKeyboards objectAtIndex:i];
NSString *kbId = [kb objectForKey:kKeymanKeyboardIdKey];
NSString *langId = [kb objectForKey:kKeymanLanguageIdKey];
if ([keyboardID_ isEqualToString:kbId] && [languageID_ isEqualToString:langId]) {
index = i;
break;
}
}
return index;
}
- (void)setCanAddNewKeyboards:(BOOL)canAddNewKeyboards {
canAddNewKeyboards_ = canAddNewKeyboards;
if (!canAddNewKeyboards)
canRemoveKeyboards_ = NO;
}
- (BOOL)userKeyboardExistsWithID:(NSString *)kbID languageID:(NSString *)langID {
BOOL exists = NO;
NSArray *userKeyboards = [[self activeUserDefaults] arrayForKey:kKeymanUserKeyboardsListKey];
if (userKeyboards != nil && [userKeyboards count]) {
for (NSDictionary *kb in userKeyboards) {
if([kbID isEqualToString:[kb objectForKey:kKeymanKeyboardIdKey]] && [langID isEqualToString:[kb objectForKey:kKeymanLanguageIdKey]]) {
exists = YES;
break;
}
}
}
return exists;
}
- (NSInteger)indexForUserKeyboardWithID:(NSString *)kbID languageID:(NSString *)langID {
BOOL exists = NO;
NSInteger index = -1;
NSArray *userKeyboards = [[self activeUserDefaults] arrayForKey:kKeymanUserKeyboardsListKey];
if (userKeyboards != nil && [userKeyboards count]) {
for (NSDictionary *kb in userKeyboards) {
index++;
if([kbID isEqualToString:[kb objectForKey:kKeymanKeyboardIdKey]] && [langID isEqualToString:[kb objectForKey:kKeymanLanguageIdKey]]) {
exists = YES;
break;
}
}
}
if (exists)
return index;
else
return -1;
}
- (NSDictionary *)currentKeyboardInfo {
if (keyboardID_ == nil || languageID_ == nil)
return nil;
NSArray *userKeyboards = [[self activeUserDefaults] arrayForKey:kKeymanUserKeyboardsListKey];
NSDictionary *kbInfo = nil;
for (NSDictionary *kb in userKeyboards) {
if([keyboardID_ isEqualToString:[kb objectForKey:kKeymanKeyboardIdKey]] && [languageID_ isEqualToString:[kb objectForKey:kKeymanLanguageIdKey]]) {
kbInfo = [NSDictionary dictionaryWithDictionary:kb];
break;
}
}
if (kbInfo == nil && keyboardsDictionary_ != nil) {
NSString *kbKey = [NSString stringWithFormat:@"%@_%@", languageID_, keyboardID_];
NSDictionary *kb = [keyboardsDictionary_ objectForKey:kbKey];
if (kb != nil)
kbInfo = [NSDictionary dictionaryWithDictionary:kb];
}
if (kbInfo == nil)
kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:keyboardID_, kKeymanKeyboardIdKey,
languageID_, kKeymanLanguageIdKey, nil];
return kbInfo;
}
- (void)addKeyboardWithID:(NSString *)kbID languageID:(NSString *)langID keyboardName:(NSString *)kbName languageName:(NSString *)langName isRTL:(BOOL)isRTL isCustom:(BOOL)isCustom font:(NSString *)font oskFont:(NSString *)oskFont {
// Check if keyboard file exists
NSString *kbVersion = [self latestKeyboardFileVersionWithID:kbID];
if (kbVersion == nil) {
[self KMLog:[NSString stringWithFormat:@"Could not add keyboard with ID: %@ because the keyboard file does not exist", kbID] checkDebugPrinting:NO];
return;
}
// Get keyboards list if it exists in user defaults
NSMutableArray *userKeyboards = [NSMutableArray arrayWithArray:[[self activeUserDefaults] arrayForKey:kKeymanUserKeyboardsListKey]];
// If user defaults for keyboards list does not exist, start creating a new one.
if (userKeyboards == nil)
userKeyboards = [[NSMutableArray alloc] init];
// Get keyboard index if it exists in list.
NSInteger x = [self indexForUserKeyboardWithID:kbID languageID:langID];
// Add keyboard to the list if it doesn't exist, update otherwise.
if (x < 0) {
[userKeyboards addObject:[NSDictionary dictionaryWithObjectsAndKeys:
langID, kKeymanLanguageIdKey,
langName, kKeymanLanguageNameKey,
kbID, kKeymanKeyboardIdKey,
kbName, kKeymanKeyboardNameKey,
kbVersion, kKeymanKeyboardVersionKey,
isRTL?@"Y":@"N", kKeymanKeyboardRTLKey,
isCustom?@"Y":@"N", kKeymanCustomKeyboardKey,
font?font:@"''", kKeymanFontKey,
oskFont, kKeymanOskFontKey,
nil]];
}
else {
[userKeyboards replaceObjectAtIndex:x withObject:[NSDictionary dictionaryWithObjectsAndKeys:
langID, kKeymanLanguageIdKey,
langName, kKeymanLanguageNameKey,
kbID, kKeymanKeyboardIdKey,
kbName, kKeymanKeyboardNameKey,
kbVersion, kKeymanKeyboardVersionKey,
isRTL?@"Y":@"N", kKeymanKeyboardRTLKey,
isCustom?@"Y":@"N", kKeymanCustomKeyboardKey,
font?font:@"''", kKeymanFontKey,
oskFont, kKeymanOskFontKey,
nil]];
}
NSUserDefaults *userData = [self activeUserDefaults];
[userData setObject:userKeyboards forKey:kKeymanUserKeyboardsListKey];
[userData setObject:[NSArray arrayWithObject:[NSDate date]] forKey:kKeymanSynchronizeSWKeyboardKey];
[userData synchronize];
}
- (void)removeKeyboardWithID:(NSString *)kbID languageID:(NSString *)langID {
NSUserDefaults *userData = [self activeUserDefaults];
NSMutableArray *userKeyboards = [NSMutableArray arrayWithArray:[userData arrayForKey:kKeymanUserKeyboardsListKey]];
// If user defaults for keyboards list does not exist, do nothing.
if (userKeyboards == nil)
return;
// Check if the keyboard exists in list.
NSInteger index = [self indexForUserKeyboardWithID:kbID languageID:langID];
// Remove keyboard from the list if it exists
if (index >= 0) {
NSDictionary *kbDict = [NSDictionary dictionaryWithDictionary:[userKeyboards objectAtIndex:index]];
[userKeyboards removeObjectAtIndex:index];
[userData setObject:userKeyboards forKey:kKeymanUserKeyboardsListKey];
[userData setObject:[NSArray arrayWithObject:[NSDate date]] forKey:kKeymanSynchronizeSWKeyboardKey];
[userData synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardRemovedNotification object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:kbDict, kKeymanKeyboardInfoKey, nil]];
}
}
- (BOOL)removeKeyboardAtIndex:(NSUInteger)index {
NSUserDefaults *userData = [self activeUserDefaults];
NSMutableArray *userKeyboards = [[userData arrayForKey:kKeymanUserKeyboardsListKey] mutableCopy];
if (userKeyboards != nil) {
if (index < [userKeyboards count]) {
[userKeyboards removeObjectAtIndex:index];
[userData setObject:userKeyboards forKey:kKeymanUserKeyboardsListKey];
[userData setObject:[NSArray arrayWithObject:[NSDate date]] forKey:kKeymanSynchronizeSWKeyboardKey];
[userData synchronize];
return YES;
}
}
return NO;
}
- (void)updateKeyboardVersionForID:(NSString *)kbID newKeyboardVersion:(NSString *)kbVersion {
NSUserDefaults *userData = [self activeUserDefaults];
NSMutableArray *userKeyboards = [[userData arrayForKey:kKeymanUserKeyboardsListKey] mutableCopy];
if (userKeyboards == nil)
return;
NSUInteger len = [userKeyboards count];
for (int i = 0; i < len; i++) {
NSDictionary *kb = [userKeyboards objectAtIndex:i];
if ([kbID isEqualToString:[kb objectForKey:kKeymanKeyboardIdKey]]) {
NSMutableDictionary *kbInfo = [kb mutableCopy];
[kbInfo setObject:kbVersion forKey:kKeymanKeyboardVersionKey];
[userKeyboards replaceObjectAtIndex:i withObject:kbInfo];
}
}
[userData setObject:userKeyboards forKey:kKeymanUserKeyboardsListKey];
[userData synchronize];
if (kIsKeymanSystemKeyboard)
userData = [NSUserDefaults standardUserDefaults];
else
userData = [self activeUserDefaults];
NSMutableDictionary *userKb = [[userData objectForKey:kKeymanUserCurrentKeyboardKey] mutableCopy];
if ([kbID isEqualToString:[userKb objectForKey:kKeymanKeyboardIdKey]]) {
[userKb setObject:kbVersion forKey:kKeymanKeyboardVersionKey];
[userData setObject:userKb forKey:kKeymanUserCurrentKeyboardKey];
}
[userData synchronize];
}
- (void)showKeyboardPickerInViewController:(UIViewController *)viewController shouldAddKeyboard:(BOOL)shouldAddKeyboard {
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad && [[[UIDevice currentDevice] systemVersion] intValue] >= 7) {
id firstResponder = [[[UIApplication sharedApplication] keyWindow] findFirstResponder];
if ([firstResponder isKindOfClass:[KMTextView class]])
[(KMTextView *)firstResponder dismissKeyboard];
else if ([firstResponder isKindOfClass:[KMTextField class]])
[(KMTextField *)firstResponder dismissKeyboard];
}
KeyboardPickerViewController *vc = [[KeyboardPickerViewController alloc] init];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
nc.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
nc.modalPresentationStyle = UIModalPresentationPageSheet;
[viewController presentViewController:nc animated:YES completion:^{
if (shouldAddKeyboard) {
[vc showAddKeyboard];
}
else {
NSUserDefaults *userData = [self activeUserDefaults];
[userData setBool:YES forKey:kKeymanKeyboardPickerDisplayedKey];
[userData synchronize];
self.keymanHelpOn = NO;
}
}];
}
- (void)dismissKeyboardPicker:(UIViewController *)viewController {
[viewController dismissViewControllerAnimated:YES completion:nil];
if (shouldReloadKeyboard)
[self reloadKeyboard];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardPickerDismissedNotification object:self];
}
- (void)reloadKeyboard {
NSURL *codeURL = [NSURL fileURLWithPath:[[[KMManager sharedInstance] activeKeymanDirectory] stringByAppendingPathComponent:kKeymanFullFileName]];
if ([[self class] isWKWebView:webView]) {
/*
[(WKWebView *)webView loadHTMLString:@"" baseURL:nil];
[(WKWebView *)webView stopLoading];
[(WKWebView *)webView setNavigationDelegate:nil];
[(WKWebView *)webView setNavigationDelegate:[KMManager sharedInstance]];*/
SEL sel = NSSelectorFromString(@"loadFileURL:allowingReadAccessToURL:");
if ([(WKWebView *)webView respondsToSelector:sel]) {
((id (*)(id, SEL, id, id))objc_msgSend)((WKWebView *)webView, sel, codeURL, [NSURL URLWithString:[[codeURL absoluteString] stringByDeletingLastPathComponent]]);
}
else {
// Copy Keyman files to the temp folder and load from there
if ([self copyKeymanFilesToTemp]) {
codeURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"keyman/%@", kKeymanFullFileName]]];
[(WKWebView *)webView loadRequest:[NSURLRequest requestWithURL:codeURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]];
}
}
}
else {
/*
[(UIWebView *)webView loadHTMLString:@"" baseURL:nil];
[(UIWebView *)webView stopLoading];
[(UIWebView *)webView setDelegate:nil];
[(UIWebView *)webView setDelegate:[KMManager sharedInstance]];*/
[(UIWebView *)webView loadRequest:[NSURLRequest requestWithURL:codeURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]];
}
}
- (void)resetKeyboard {
NSArray *userKeyboards = [[self activeUserDefaults] arrayForKey:kKeymanUserKeyboardsListKey];
NSInteger index = [self indexForUserKeyboardWithID:self.keyboardID languageID:self.languageID];
if (index >= 0) {
keyboardID_ = nil;
languageID_ = nil;
NSDictionary *kb = [userKeyboards objectAtIndex:index];
NSString *kbId = [kb objectForKey:kKeymanKeyboardIdKey];
NSString *langId = [kb objectForKey:kKeymanLanguageIdKey];
NSString *kbName = [kb objectForKey:kKeymanKeyboardNameKey];
NSString *langName = [kb objectForKey:kKeymanLanguageNameKey];
NSString *font = [kb objectForKey:kKeymanFontKey];
NSString *oskFont = [kb objectForKey:kKeymanOskFontKey];
[self setKeyboardWithID:kbId languageID:langId keyboardName:kbName languageName:langName font:font oskFont:oskFont];
}
else {
NSDictionary *kb = [userKeyboards objectAtIndex:0];
if (kb != nil) {
keyboardID_ = nil;
languageID_ = nil;
NSString *kbId = [kb objectForKey:kKeymanKeyboardIdKey];
NSString *langId = [kb objectForKey:kKeymanLanguageIdKey];
NSString *kbName = [kb objectForKey:kKeymanKeyboardNameKey];
NSString *langName = [kb objectForKey:kKeymanLanguageNameKey];
NSString *font = [kb objectForKey:kKeymanFontKey];
NSString *oskFont = [kb objectForKey:kKeymanOskFontKey];
[self setKeyboardWithID:kbId languageID:langId keyboardName:kbName languageName:langName font:font oskFont:oskFont];
}
else {
keyboardID_ = nil;
languageID_ = nil;
[self setKeyboardWithID:kKeymanDefaultKeyboardID
languageID:kKeymanDefaultLanguageID
keyboardName:kKeymanDefaultKeyboardName
languageName:kKeymanDefaultLanguageName
font:kKeymanDefaultKeyboardFont
oskFont:nil];
}
}
}
- (BOOL)usingTempFolder {
if ([[self class] isWKWebView:webView]) {
SEL sel = NSSelectorFromString(@"loadFileURL:allowingReadAccessToURL:");
if (![(WKWebView *)webView respondsToSelector:sel]) {
return YES;
}
}
return NO;
}
- (void)preloadLanguageFileAtPath:(NSString *)languagePath shouldOverwrite:(BOOL)shouldOverwrite {
if (![languagePath length])
return;
NSFileManager *fileMan = [NSFileManager defaultManager];
NSError *error = nil;
NSString *langDir = [self activeLanguageDirectory];
if (![langDir length]) {
[self KMLog:@"Could not find/create the Keyman language directory" checkDebugPrinting:NO];
return;
}
NSString *destinationPath = [langDir stringByAppendingPathComponent:[languagePath lastPathComponent]];
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:languagePath toPath:destinationPath error:&error];
else if (shouldOverwrite) {
[fileMan removeItemAtPath:destinationPath error:&error];
if (error == nil)
[fileMan copyItemAtPath:languagePath toPath:destinationPath error:&error];
}
if(error==nil)
[self addSkipBackupAttributeToItemAtPath:destinationPath];
else
[self KMLog:[NSString stringWithFormat:@"Error copying language file: %@", error] checkDebugPrinting:NO];
}
- (void)preloadFontFileAtPath:(NSString *)fontPath shouldOverwrite:(BOOL)shouldOverwrite {
if (![fontPath length])
return;
NSFileManager *fileMan = [NSFileManager defaultManager];
NSError *error = nil;
NSString *fontDir = [self activeFontDirectory];
if (![fontDir length]) {
[self KMLog:@"Could not find/create the Keyman font directory" checkDebugPrinting:NO];
return;
}
NSString *destinationPath = [fontDir stringByAppendingPathComponent:[fontPath lastPathComponent]];
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:fontPath toPath:destinationPath error:&error];
else if (shouldOverwrite) {
[fileMan removeItemAtPath:destinationPath error:&error];
if (error == nil)
[fileMan copyItemAtPath:fontPath toPath:destinationPath error:&error];
}
if (error != nil)
[self KMLog:[NSString stringWithFormat:@"Error copying font file: %@", error] checkDebugPrinting:NO];
else
[self addSkipBackupAttributeToItemAtPath:destinationPath];
}
- (void)fetchKeyboardsList {
[[self class] fetchKeyboardsWithCompletionBlock:nil];
}
- (NSString *)sdkVersion {
NSBundle *keymanBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"Keyman" ofType:@"bundle"]];
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[keymanBundle pathForResource:@"KeymanEngine-Info" ofType:@"plist"]];
return [info objectForKey:@"CFBundleVersion"];
}
- (void)setKeymanHelpOn:(BOOL)keymanHelpOn {
// only in-app keyboard is allowed to enable/disable the help bubble, it is always disabled for system-wide keyboard
if (!kIsKeymanSystemKeyboard) {
keymanHelpOn_ = keymanHelpOn;
}
}
- (void)showHelpBubble {
// help bubble is always disabled for system-wide keyboard
if (kIsKeymanSystemKeyboard || keyboardMenuView != nil)
return;
NSString *jsString = [NSString stringWithFormat:@"langMenuPos();"];
if ([[self class] isWKWebView:webView]) {
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:^(id result, NSError *error) {
if (result) {
NSString *langMenuKeyPos = [NSString stringWithFormat:@"%@", result];
if ([langMenuKeyPos length]) {
NSArray *pos = [langMenuKeyPos componentsSeparatedByString: @","];
CGFloat px = [[pos objectAtIndex:0] floatValue];
CGFloat py = [[pos objectAtIndex:1] floatValue];
[self showHelpBubbleAtPoint:CGPointMake(px, py)];
}
}
}];
}
else {
NSString *langMenuKeyPos = [(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
if ([langMenuKeyPos length]) {
NSArray *pos = [langMenuKeyPos componentsSeparatedByString: @","];
CGFloat px = [[pos objectAtIndex:0] floatValue];
CGFloat py = [[pos objectAtIndex:1] floatValue];
[self showHelpBubbleAtPoint:CGPointMake(px, py)];
}
}
}
- (void)showHelpBubbleAtPoint:(CGPoint)point {
[helpBubbleView removeFromSuperview];
helpBubbleView = [[KMPopoverView alloc] initWithFrame:CGRectZero];
[helpBubbleView setBackgroundColor:[UIColor colorWithRed:253.0/255.0 green:244.0/255.0 blue:196.0/255.0 alpha:1.0]];
[helpBubbleView setBackgroundColor2:[UIColor colorWithRed:233.0/255.0 green:224.0/255.0 blue:176.0/255.0 alpha:1.0]];
[helpBubbleView setBorderColor:[UIColor colorWithRed:0.5 green:0.25 blue:0.25 alpha:1.0]];
CGFloat frameWidth = 90.0;
CGFloat frameHeight = 40.0 + helpBubbleView.arrowHeight;
CGFloat fontSize = 10.0;
BOOL isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad;
if (isPad) {
frameWidth *= 1.5;
frameHeight *= 1.5;
fontSize *= 1.5;
}
CGRect inputViewFrame = [[[self class] inputView] frame];
CGFloat screenWidth = inputViewFrame.size.width;
BOOL isPortrait = YES;
if (kIsKeymanSystemKeyboard)
isPortrait = [KMInputViewController isPortrait];
else {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
isPortrait = UIInterfaceOrientationIsPortrait(orientation);
}
CGFloat adjY;
if (isPortrait)
adjY = kIsKeymanSystemKeyboard?9.0:4.0;
else
adjY = kIsKeymanSystemKeyboard?3.0:4.0;
CGFloat px = point.x;
CGFloat py = point.y + adjY + (isPad?2.0:1.0);
CGFloat x = px - frameWidth/2;
CGFloat y = py - frameHeight;
if(x < 0)
x = 0;
else if((x+frameWidth) > screenWidth)
x = screenWidth - frameWidth;
[helpBubbleView setFrame:CGRectMake(x, y, frameWidth, frameHeight)];
if(x == 0)
[helpBubbleView setArrowPosX:px];
else if (x == (screenWidth - frameWidth))
[helpBubbleView setArrowPosX:(px - x)];
else
[helpBubbleView setArrowPosX:frameWidth/2];
UILabel *helpText = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, frameWidth-10, frameHeight - helpBubbleView.arrowHeight)];
[helpText setBackgroundColor:[UIColor clearColor]];
[helpText setFont:[helpText.font fontWithSize:fontSize]];
[helpText setTextAlignment:NSTextAlignmentCenter];
[helpText setTextColor:[UIColor darkTextColor]];
[helpText setLineBreakMode:NSLineBreakByWordWrapping];
[helpText setNumberOfLines:0];
[helpText setText:kKeymanKeyboardChangeHelpText];
[helpBubbleView addSubview:helpText];
[webView addSubview:helpBubbleView];
}
#pragma mark - Internal Methods
+ (UIView *)inputView {
if ([self WKWebViewClass] && enableWebKit) {
if (webView == nil) {
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKPreferences *prefs = [[WKPreferences alloc] init];
[prefs setJavaScriptEnabled:YES];
[config setPreferences:prefs];
[config setSuppressesIncrementalRendering:NO];
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:[self sharedInstance] name:@"keyman"];
[config setUserContentController:userContentController];
CGRect frame = CGRectMake(0.0f, 0.0f, [[self class] keyboardWidth], [[self class] keyboardHeight]);
webView = [[WKWebView alloc] initWithFrame:frame configuration:config];
//[(WKWebView *)webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[(WKWebView *)webView setOpaque:NO];
[(WKWebView *)webView setBackgroundColor:[UIColor clearColor]];
[(WKWebView *)webView setNavigationDelegate:[self sharedInstance]];
KMManager *km = [self sharedInstance];
[km keymanScrollView].scrollEnabled = NO;
NSURL *codeURL = [NSURL fileURLWithPath:[[km activeKeymanDirectory] stringByAppendingPathComponent:kKeymanFullFileName]];
SEL sel = NSSelectorFromString(@"loadFileURL:allowingReadAccessToURL:");
if ([(WKWebView *)webView respondsToSelector:sel]) {
((id (*)(id, SEL, id, id))objc_msgSend)((WKWebView *)webView, sel, codeURL, [NSURL URLWithString:[[codeURL absoluteString] stringByDeletingLastPathComponent]]);
}
else {
// Copy Keyman files to the temp folder and load from there
if ([[self sharedInstance] copyKeymanFilesToTemp]) {
codeURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"keyman/%@", kKeymanFullFileName]]];
[(WKWebView *)webView loadRequest:[NSURLRequest requestWithURL:codeURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]];
}
}
}
return webView;
}
else {
if (webView == nil) {
CGRect frame = CGRectMake(0.0f, 0.0f, [[self class] keyboardWidth], [[self class] keyboardHeight]);
webView = [[UIWebView alloc] initWithFrame:frame];
[(UIWebView *)webView setDataDetectorTypes:UIDataDetectorTypeNone];
[(UIWebView *)webView setOpaque:NO];
[(UIWebView *)webView setBackgroundColor:[UIColor clearColor]];
[(UIWebView *)webView setDelegate:[self sharedInstance]];
KMManager *km = [self sharedInstance];
[km keymanScrollView].scrollEnabled = NO;
NSURL *codeURL = [NSURL fileURLWithPath:[[km activeKeymanDirectory] stringByAppendingPathComponent:kKeymanFullFileName]];
[(UIWebView *)webView loadRequest:[NSURLRequest requestWithURL:codeURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]];
}
return webView;
}
}
+ (Class)WKWebViewClass {
return NSClassFromString(@"WKWebView");
}
+ (BOOL)isWKWebView:(id)webView {
if (![self WKWebViewClass])
return NO;
return [webView isKindOfClass:[self WKWebViewClass]];
}
+ (void)setKMSelectionRange:(NSRange)range manually:(BOOL)manually {
if (range.location != NSNotFound) {
NSString *jsString = [NSString stringWithFormat:@"setCursorRange(%lu,%lu)", (unsigned long)range.location, (unsigned long)range.length];
UIView *webView = [self inputView];
if ([self isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
}
}
+ (void)clearText {
[self setKMText:nil];
[self setKMSelectionRange:NSMakeRange(0, 0) manually:YES];
id firstResponder = [[[UIApplication sharedApplication] keyWindow] findFirstResponder];
[[self sharedInstance] KMLog:[NSString stringWithFormat:@"%@: 0x%x Cleared text.", [firstResponder class], (uint)firstResponder] checkDebugPrinting:YES];
}
+ (void)setKMText:(NSString *)text {
NSString *kmText = text?[text stringByReplacingOccurrencesOfString:@"\\" withString:@"\\u005C"]:@"";
kmText = [kmText stringByReplacingOccurrencesOfString:@"'" withString:@"\\u0027"];
kmText = [kmText stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
NSString *jsString = [NSString stringWithFormat:@"setKeymanVal('%@');", kmText];
UIView *webView = [self inputView];
if ([self isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
}
// This function appears to fetch the keyboard metadata from r.keymanweb.com.
+ (void)fetchKeyboardsWithCompletionBlock:(KMFetchKeyboardsBlock)completionBlock {
__weak KMManager *_self = [KMManager sharedInstance];
if (_self.currentRequest != nil) return;
NSString *deviceType = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)?@"iphone":@"ipad";
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@languages?device=%@", kKeymanApiBaseURL, deviceType]];
NSDictionary *userData = nil;
if(completionBlock != nil) {
userData = [NSDictionary dictionaryWithObjectsAndKeys:completionBlock, @"completionBlock", nil];
}
_self.currentRequest = [[KMHTTPDownloadRequest alloc] initWithURL:url downloadType:kmDownloadCachedData userInfo:userData];
[_self.sharedQueue addRequest:_self.currentRequest];
[_self.sharedQueue run];
}
- (NSArray *)keyboardsForIndex:(NSInteger)index {
NSArray *retVal = nil;
if (index < [self.languages count])
retVal = [[self.languages objectAtIndex:index] objectForKey:kKeymanLanguageKeyboardsKey];
return retVal;
}
- (NSDictionary *)keyboardInfoForLanguageIndex:(NSInteger)languageIndex keyboardIndex:(NSInteger)keyboardIndex {
NSDictionary *retVal = nil;
if (languageIndex < [self.languages count]) {
NSArray *keyboards = [[self.languages objectAtIndex:languageIndex] objectForKey:kKeymanLanguageKeyboardsKey];
if (keyboardIndex < [keyboards count])
retVal = [keyboards objectAtIndex:keyboardIndex];
}
return retVal;
}
- (void)downloadKeyboardFromUrl:(NSURL *)jsonUrl isDirect:(BOOL)isDirect {
NSDictionary *kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:
@"", kKeymanKeyboardIdKey,
@"", kKeymanLanguageIdKey,
@"", kKeymanKeyboardNameKey,
@"", kKeymanLanguageNameKey,
@"Y", kKeymanCustomKeyboardKey,
nil];
if ([self.reachability currentReachabilityStatus] == NotReachable) {
NSError *error = [NSError errorWithDomain:@"Keyman"
code:0
userInfo:[NSDictionary
dictionaryWithObject:@"No connection"
forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
NSError *error = nil;
BOOL failed = YES;
if (jsonUrl != nil) {
NSData *data = nil;
if (isDirect)
data = [[NSData alloc] initWithContentsOfURL:jsonUrl];
else {
NSString *deviceParam = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)?@"iphone":@"ipad";
NSString *encodedUrl = [[jsonUrl absoluteString] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *remoteURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@&device=%@", kKeymanApiRemoteURL, encodedUrl, deviceParam]];
data = [[NSData alloc] initWithContentsOfURL:remoteURL];
}
if (data != nil) {
id jsonObj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if (error == nil) {
NSDictionary *jsonDict = (NSDictionary *)jsonObj;
[self downloadKeyboardFromDictionary:jsonDict];
failed = NO;
}
}
else {
error = [NSError errorWithDomain:@"Keyman"
code:0
userInfo:[NSDictionary
dictionaryWithObject:@"Failed to fetch JSON file"
forKey:NSLocalizedDescriptionKey]];
}
}
if (failed) {
if (error == nil) {
error = [NSError errorWithDomain:@"Keyman"
code:0
userInfo:[NSDictionary
dictionaryWithObject:@"The keyboard could not be installed"
forKey:NSLocalizedDescriptionKey]];
}
[self downloadFailedForKeyboard:kbInfo error:error];
}
}
- (void)downloadKeyboardFromDictionary:(NSDictionary *)jsonDict {
NSDictionary *kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:
@"", kKeymanKeyboardIdKey,
@"", kKeymanLanguageIdKey,
@"", kKeymanKeyboardNameKey,
@"", kKeymanLanguageNameKey,
@"", kKeymanKeyboardVersionKey,
@"", kKeymanKeyboardRTLKey,
@"Y", kKeymanCustomKeyboardKey,
nil];
@try {
NSDictionary *options = [jsonDict objectForKey:kKeymanOptionsKey];
NSString *kbBaseUri = [options objectForKey:kKeymanKeyboardBaseURIKey];
NSString *fontBaseUri = [options objectForKey:kKeymanFontBaseURIKey];
NSDictionary *keyboard = [jsonDict objectForKey:kKeymanKeyboardKey];
NSString *fileName = [keyboard objectForKey:kKeymanKeyboardFilenameKey];
NSString *kbID = [keyboard objectForKey:kKeymanIdKey];
NSString *kbName = [keyboard objectForKey:kKeymanNameKey];
NSString *kbVersion = [keyboard objectForKey:kKeymanKeyboardVersionKey];
if (kbVersion == nil)
kbVersion = @"1.0";
NSString *isRTL = [keyboard objectForKey:kKeymanKeyboardRTLKey];
isRTL = (isRTL == nil)?@"N":@"Y";
NSArray *languages = [keyboard objectForKey:kKeymanLanguagesKey];
NSDictionary *kbFont = [keyboard objectForKey:kKeymanFontKey];
NSDictionary *kbOskFont = [keyboard objectForKey:kKeymanOskFontKey];
NSString *jsFont = [self jsFontFromFontDictionary:kbFont];
NSString *jsOskFont = kbOskFont?[self jsFontFromFontDictionary:kbOskFont]:nil;
if (keyboard == nil || fileName == nil || kbID == nil || kbName == nil || languages == nil) {
NSError *error = [NSError errorWithDomain:@"Keyman"
code:0
userInfo:[NSDictionary
dictionaryWithObject:@"The keyboard could not be installed"
forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
NSDictionary *keyboardInfo = nil;
NSMutableArray *keyboardsInfoArray = nil;
if ([languages count] > 1) {
keyboardsInfoArray = [NSMutableArray arrayWithCapacity:[languages count]];
for (NSDictionary *language in languages) {
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:kbID, kKeymanKeyboardIdKey,
[language objectForKey:kKeymanIdKey], kKeymanLanguageIdKey,
kbName, kKeymanKeyboardNameKey,
[language objectForKey:kKeymanNameKey], kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
isRTL, kKeymanKeyboardRTLKey,
@"Y", kKeymanCustomKeyboardKey,
jsFont, kKeymanFontKey,
jsOskFont, kKeymanOskFontKey, nil];
[keyboardsInfoArray addObject:info];
}
}
else {
keyboardInfo = [NSDictionary dictionaryWithObjectsAndKeys:kbID, kKeymanKeyboardIdKey,
[[languages objectAtIndex:0] objectForKey:kKeymanIdKey], kKeymanLanguageIdKey,
kbName, kKeymanKeyboardNameKey,
[[languages objectAtIndex:0] objectForKey:kKeymanNameKey], kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
isRTL, kKeymanKeyboardRTLKey,
@"Y", kKeymanCustomKeyboardKey,
jsFont, kKeymanFontKey,
jsOskFont, kKeymanOskFontKey, nil];
}
if (keyboardsInfoArray != nil)
kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:keyboardsInfoArray, kKeymanKeyboardInfoKey, nil];
else
kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:keyboardInfo, kKeymanKeyboardInfoKey, nil];
if (self.downloadQueue != nil) {
// Download queue is active.
NSError *error = [NSError errorWithDomain:@"Keyman"
code:0
userInfo:[NSDictionary
dictionaryWithObject:@"Download queue is busy"
forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
if ([self.reachability currentReachabilityStatus] == NotReachable) {
NSError *error = [NSError errorWithDomain:@"Keyman"
code:0
userInfo:[NSDictionary
dictionaryWithObject:@"No internet connection"
forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
NSString *keyboardPath = [self keyboardPathForFilename:fileName keyboardVersion:kbVersion];
NSURL *keyboardURL = [NSURL URLWithString:[kbBaseUri stringByAppendingString:fileName]];
NSMutableArray *keyboardFontURLs;
id fontSource = nil;
if (kbFont != nil && kbFont.count) {
fontSource = [kbFont objectForKey:kKeymanFontSourceKey];
if (fontSource == nil)
fontSource = [kbFont objectForKey:kKeymanFontFilenameKey]; // Font filename is deprecated
if ([fontSource isKindOfClass:[NSArray class]]) {
keyboardFontURLs = [NSMutableArray arrayWithCapacity:[fontSource count]];
for (NSString *source in fontSource) {
if ([source rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [source rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
[keyboardFontURLs addObject:[NSURL URLWithString:[fontBaseUri stringByAppendingString:source]]];
}
}
}
else if ([fontSource isKindOfClass:[NSString class]]) {
keyboardFontURLs = [NSMutableArray arrayWithCapacity:1];
NSString *source = (NSString *)fontSource;
if ([source rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [source rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
[keyboardFontURLs addObject:[NSURL URLWithString:[fontBaseUri stringByAppendingString:source]]];
}
}
else {
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Unexpected error: %@ is not a valid type to be processed.", [fontSource class]] forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
}
if (kbOskFont != nil && kbOskFont.count) {
fontSource = [kbOskFont objectForKey:kKeymanFontSourceKey];
if (fontSource == nil)
fontSource = [kbOskFont objectForKey:kKeymanFontFilenameKey]; // Font filename is deprecated
if ([fontSource isKindOfClass:[NSArray class]]) {
if (keyboardFontURLs == nil)
keyboardFontURLs = [NSMutableArray arrayWithCapacity:[fontSource count]];
for (NSString *source in fontSource) {
if ([source rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [source rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
NSURL *fontUrl = [NSURL URLWithString:[fontBaseUri stringByAppendingString:source]];
if (![keyboardFontURLs containsObject:fontUrl])
[keyboardFontURLs addObject:fontUrl];
}
}
}
else if ([fontSource isKindOfClass:[NSString class]]) {
if (keyboardFontURLs == nil)
keyboardFontURLs = [NSMutableArray arrayWithCapacity:1];
NSString *source = (NSString *)fontSource;
if ([source rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [source rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
NSURL *fontUrl = [NSURL URLWithString:[fontBaseUri stringByAppendingString:source]];
if (![keyboardFontURLs containsObject:fontUrl])
[keyboardFontURLs addObject:fontUrl];
}
}
else {
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Unexpected error: %@ is not a valid type to be processed.", [fontSource class]] forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
}
BOOL isUpdate = ([self latestKeyboardFileVersionWithID:kbID] != nil);
[self setDownloadQueue:[[KMHTTPDownloader alloc] init:self]];
NSDictionary *commonUserData = [NSDictionary dictionaryWithObjectsAndKeys:[kbInfo objectForKey:kKeymanKeyboardInfoKey], kKeymanKeyboardInfoKey,
[NSNumber numberWithBool:isUpdate], kKeymanUpdateKey, nil];
[self.downloadQueue setUserInfo:commonUserData];
KMHTTPDownloadRequest *request = [[KMHTTPDownloadRequest alloc] initWithURL:keyboardURL userInfo:commonUserData];
[request setDestinationFile:keyboardPath];
[request setTag:0];
[self.downloadQueue addRequest:request];
if (keyboardFontURLs != nil) {
for (int i=0; i<[keyboardFontURLs count]; i++) {
NSString *fontPath = [self fontPathForFilename:[[keyboardFontURLs objectAtIndex:i] lastPathComponent]];
request = [[KMHTTPDownloadRequest alloc] initWithURL:[[NSURL alloc] initWithString:fontPath] userInfo:commonUserData];
[request setDestinationFile:fontPath];
[request setTag:i+1];
[self.downloadQueue addRequest:request];
}
}
[self.downloadQueue run];
}
@catch (NSException *exception) {
if ([[exception description] rangeOfString:@"unrecognized selector"].location != NSNotFound) {
NSError *error = [NSError errorWithDomain:@"Keyman"
code:0
userInfo:[NSDictionary
dictionaryWithObject:@"Invalid JSON file"
forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
}
else {
NSError *error = [NSError errorWithDomain:@"Keyman"
code:0
userInfo:[NSDictionary
dictionaryWithObject:[exception description]
forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
}
}
@finally {
//
}
}
- (void)downloadKeyboardForLanguageIndex:(NSInteger)languageIndex keyboardIndex:(NSInteger)keyboardIndex isUpdate:(BOOL)isUpdate {
if (languageIndex >= [self.languages count]) return;
NSArray *keyboards = [[self.languages objectAtIndex:languageIndex] objectForKey:kKeymanLanguageKeyboardsKey];
if (keyboardIndex >= [keyboards count]) return;
NSDictionary *keyboardDict = [keyboards objectAtIndex:keyboardIndex];
NSString *kbID = [keyboardDict objectForKey:kKeymanIdKey];
NSString *langID = [[self.languages objectAtIndex:languageIndex] objectForKey:kKeymanIdKey];
[self downloadKeyboardWithID:kbID languageID:langID isUpdate:isUpdate];
}
- (void)downloadKeyboardWithID:(NSString *)keyboardID languageID:(NSString *)languageID isUpdate:(BOOL)isUpdate {
NSDictionary *kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:keyboardID, kKeymanKeyboardIdKey,
languageID, kKeymanLanguageIdKey,
nil];
if (self.downloadQueue != nil) {
// Download queue is active.
return;
}
if ([self.reachability currentReachabilityStatus] == NotReachable) {
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:@"No internet connection" forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
if (self.keyboardsInfo == nil) {
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Keyboard info has not yet been fetched. Call -[KMManager fetchKeyboardsList] first." forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
NSString *kbKey = [NSString stringWithFormat:@"%@_%@", languageID, keyboardID];
NSDictionary *keyboardDict = [self.keyboardsDictionary objectForKey:kbKey];
if (keyboardDict == nil) {
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Keyboard not found with key: %@", kbKey] forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
NSString *urlStr = [[self.keyboardsInfo objectForKey:keyboardID] objectForKey:kKeymanKeyboardURIKey];
NSURL *keyboardURL = [NSURL URLWithString:urlStr];
NSMutableArray *keyboardFontURLs = nil;
NSString *kbFontStr = [keyboardDict objectForKey:kKeymanFontKey];
NSDictionary *kbFont = [NSJSONSerialization JSONObjectWithData:[kbFontStr dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
id fontSource = nil;
if (kbFont != nil) {
fontSource = [kbFont objectForKey:kKeymanFontFilesKey];
if ([fontSource isKindOfClass:[NSArray class]]) {
keyboardFontURLs = [NSMutableArray arrayWithCapacity:[fontSource count]];
for (NSString *source in fontSource) {
if ([source rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [source rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
[keyboardFontURLs addObject:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self.options objectForKey:kKeymanFontBaseURIKey], source]]];
}
}
}
else if ([fontSource isKindOfClass:[NSString class]]) {
keyboardFontURLs = [NSMutableArray arrayWithCapacity:1];
NSString *source = (NSString *)fontSource;
if ([source rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [source rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
[keyboardFontURLs addObject:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self.options objectForKey:kKeymanFontBaseURIKey], source]]];
}
}
else {
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Unexpected error: %@ is not a valid type to be processed.", [fontSource class]] forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
}
NSString *kbOskFontStr = [keyboardDict objectForKey:kKeymanOskFontKey];
if (kbOskFontStr == nil)
kbOskFontStr = @"";
NSDictionary *kbOskFont = [NSJSONSerialization JSONObjectWithData:[kbOskFontStr dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
if (kbOskFont != nil) {
fontSource = [kbOskFont objectForKey:kKeymanFontFilesKey];
if ([fontSource isKindOfClass:[NSArray class]]) {
if (keyboardFontURLs == nil)
keyboardFontURLs = [NSMutableArray arrayWithCapacity:[fontSource count]];
for (NSString *source in fontSource) {
if ([source rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [source rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
NSURL *fontUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self.options objectForKey:kKeymanFontBaseURIKey], source]];
if (![keyboardFontURLs containsObject:fontUrl])
[keyboardFontURLs addObject:fontUrl];
}
}
}
else if ([fontSource isKindOfClass:[NSString class]]) {
if (keyboardFontURLs == nil)
keyboardFontURLs = [NSMutableArray arrayWithCapacity:1];
NSString *source = (NSString *)fontSource;
if ([source rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [source rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
NSURL *fontUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", [self.options objectForKey:kKeymanFontBaseURIKey], source]];
if (![keyboardFontURLs containsObject:fontUrl])
[keyboardFontURLs addObject:fontUrl];
}
}
else {
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Unexpected error: %@ is not a valid type to be processed.", [fontSource class]] forKey:NSLocalizedDescriptionKey]];
[self downloadFailedForKeyboard:kbInfo error:error];
return;
}
}
[self setDownloadQueue:[[KMHTTPDownloader alloc] init:self]];
NSDictionary *commonUserData = [NSDictionary dictionaryWithObjectsAndKeys:kbInfo, kKeymanKeyboardInfoKey,
[NSNumber numberWithBool:isUpdate], kKeymanUpdateKey, nil];
[self.downloadQueue setUserInfo:commonUserData];
NSString *kbVersion = [keyboardDict objectForKey:kKeymanKeyboardVersionKey];
NSString *keyboardPath = [self keyboardPathForFilename:[keyboardURL lastPathComponent] keyboardVersion:kbVersion];
KMHTTPDownloadRequest *request = [[KMHTTPDownloadRequest alloc] initWithURL:keyboardURL userInfo:commonUserData];
[request setDestinationFile:keyboardPath];
[request setTag:0];
[self.downloadQueue addRequest:request];
if (keyboardFontURLs != nil) {
for (int i=0; i<[keyboardFontURLs count]; i++) {
NSString *fontPath = [self fontPathForFilename:[[keyboardFontURLs objectAtIndex:i] lastPathComponent]];
request = [[KMHTTPDownloadRequest alloc] initWithURL:[keyboardFontURLs objectAtIndex:i] userInfo:commonUserData];
[request setDestinationFile:fontPath];
[request setTag:i+1];
[self.downloadQueue addRequest:request];
}
}
[self.downloadQueue run];
}
- (void)downloadQueueFinished:(KMHTTPDownloader *)queue {
if (self.debugPrintingOn) {
NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self activeFontDirectory] error:nil];
[self KMLog:[NSString stringWithFormat:@"Font Directory: %@", directoryContents] checkDebugPrinting:YES];
NSArray *directoryContents2 = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self activeLanguageDirectory] error:nil];
[self KMLog:[NSString stringWithFormat:@"Language Directory: %@", directoryContents2] checkDebugPrinting:YES];
}
}
- (void)downloadRequestStarted:(KMHTTPDownloadRequest *)request {
// If we're downloading a new keyboard. The extra check is there to filter out other potential request types in the future.
if (request.tag == 0 && request.typeCode == kmDownloadFile) {
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardDownloadStartedNotification
object:self
userInfo:[request userInfo]];
}
}
- (void)downloadRequestFinished:(KMHTTPDownloadRequest *)request {
if(request.typeCode == kmDownloadFile) {
NSString *kbId = nil;
NSString *langId = nil;
NSString *kbVersion = nil;
id kbInfo = [[request userInfo] objectForKey:kKeymanKeyboardInfoKey];
if ([kbInfo isKindOfClass:[NSDictionary class]]) {
kbId = [(NSDictionary *)kbInfo objectForKey:kKeymanKeyboardIdKey];
langId = [(NSDictionary *)kbInfo objectForKey:kKeymanLanguageIdKey];
kbVersion = [(NSDictionary *)kbInfo objectForKey:kKeymanKeyboardVersionKey];
}
else {
kbId = [[(NSArray *)kbInfo objectAtIndex:0] objectForKey:kKeymanKeyboardIdKey];
langId = [[(NSArray *)kbInfo objectAtIndex:0] objectForKey:kKeymanLanguageIdKey];
kbVersion = [[(NSArray *)kbInfo objectAtIndex:0] objectForKey:kKeymanKeyboardVersionKey];
}
BOOL isUpdate = [[[request userInfo] objectForKey:kKeymanUpdateKey] boolValue];
int statusCode = (int)[request responseStatusCode];
if (statusCode == 200) { // The request has succeeded.
if ([self.downloadQueue requestsCount] == 0) { // Download queue finished.
[self setDownloadQueue:nil];
[self registerCustomFonts];
[self KMLog:[NSString stringWithFormat:@"Downloaded keyboard: %@.", kbId] checkDebugPrinting:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardDownloadCompletedNotification
object:self
userInfo:[request userInfo]];
if (isUpdate) {
shouldReloadKeyboard = YES;
[self reloadKeyboard];
}
NSUserDefaults *userData = [self activeUserDefaults];
[userData setObject:[NSArray arrayWithObject:[NSDate date]] forKey:kKeymanSynchronizeSWKeyboardKey];
[userData synchronize];
}
}
else { // Possible request error (400 Bad Request, 404 Not Found, etc.)
[self.downloadQueue cancelAllOperations];
[self setDownloadQueue:nil];
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"%@ : %@", [request responseStatusMessage], [request url]] forKey:NSLocalizedDescriptionKey]];
[self KMLog:[NSString stringWithFormat:@"Keyboard download failed: %@.", error] checkDebugPrinting:YES];
if (!isUpdate) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *fileName = [[request url] lastPathComponent];
if ([fileName rangeOfString:@".js" options:NSBackwardsSearch].location != NSNotFound) {
NSString *kbPath = [self keyboardPathForFilename:fileName keyboardVersion:kbVersion];
if ([fileManager fileExistsAtPath:kbPath])
[fileManager removeItemAtPath:kbPath error:nil];
}
if ([fileName rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [fileName rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
NSString *kbPath = [self keyboardPathForID:kbId keyboardVersion:kbVersion];
if ([fileManager fileExistsAtPath:kbPath])
[fileManager removeItemAtPath:kbPath error:nil];
NSString *fontPath = [[self activeFontDirectory] stringByAppendingPathComponent:fileName];
if ([fileManager fileExistsAtPath:fontPath])
[fileManager removeItemAtPath:fontPath error:nil];
}
}
[self downloadFailedForKeyboard:[request userInfo] error:error];
}
} else if(request.typeCode == kmDownloadCachedData) {
// TODO: Implement.
if(request == self.currentRequest) {
@try {
NSError *e;
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:[self.currentRequest rawResponseData] options:NSJSONReadingMutableContainers error:&e];
self.options = [responseDict objectForKey:kKeymanOptionsKey];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:kKeymanNameKey ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)];
self.languages = [[[responseDict objectForKey:kKeymanLanguagesKey] objectForKey:kKeymanLanguagesKey] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
[self createKeyboardsInfo];
[self KMLog:[NSString stringWithFormat:@"Request completed -- %lu languages.", (unsigned long)[self.languages count]] checkDebugPrinting:YES];
KMFetchKeyboardsBlock completionBlock;
if(self.currentRequest.userInfo != nil) {
completionBlock = [self.currentRequest.userInfo objectForKey:@"completionBlock"];
}
self.currentRequest = nil;
if (completionBlock != nil) {
completionBlock(nil);
}
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanLanguagesUpdatedNotification object:self];
}
@catch(NSException *exception) {
[self KMLog:[NSString stringWithFormat:@"Failed: %@.", exception.reason] checkDebugPrinting:YES];
NSError *error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey]];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanLanguagesDownloadFailedNotification object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:NSUnderlyingErrorKey]];
}
}
}
}
- (void)downloadRequestFailed:(KMHTTPDownloadRequest *)request {
if(request.typeCode == kmDownloadFile) {
[self setDownloadQueue:nil];
NSError *error = [request error];
if (error == nil) error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:nil];
[self KMLog:[NSString stringWithFormat:@"Keyboard download failed: %@.", error] checkDebugPrinting:YES];
NSString *kbId = nil;
NSString *kbVersion = nil;
id kbInfo = [[request userInfo] objectForKey:kKeymanKeyboardInfoKey];
if ([kbInfo isKindOfClass:[NSDictionary class]]) {
kbId = [(NSDictionary *)kbInfo objectForKey:kKeymanKeyboardIdKey];
kbVersion = [(NSDictionary *)kbInfo objectForKey:kKeymanKeyboardVersionKey];
}
else {
kbId = [[(NSArray *)kbInfo objectAtIndex:0] objectForKey:kKeymanKeyboardIdKey];
kbVersion = [[(NSArray *)kbInfo objectAtIndex:0] objectForKey:kKeymanKeyboardVersionKey];
}
BOOL isUpdate = [[[request userInfo] objectForKey:kKeymanUpdateKey] boolValue];
if (!isUpdate) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *fileName = [[request url] lastPathComponent];
if ([fileName rangeOfString:@".js" options:NSBackwardsSearch].location != NSNotFound) {
NSString *kbPath = [self keyboardPathForFilename:fileName keyboardVersion:kbVersion];
if ([fileManager fileExistsAtPath:kbPath])
[fileManager removeItemAtPath:kbPath error:nil];
}
if ([fileName rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [fileName rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
NSString *kbPath = [self keyboardPathForID:kbId keyboardVersion:kbVersion];
if ([fileManager fileExistsAtPath:kbPath])
[fileManager removeItemAtPath:kbPath error:nil];
}
}
[self downloadFailedForKeyboard:[request userInfo] error:error];
} else if(request.typeCode == kmDownloadCachedData) {
if(request == self.currentRequest) {
NSError *error = [self.currentRequest error];
[self KMLog:[NSString stringWithFormat:@"Failed: %@.", error] checkDebugPrinting:YES];
KMFetchKeyboardsBlock completionBlock;
if(self.currentRequest.userInfo != nil) {
completionBlock = [self.currentRequest.userInfo objectForKey:@"completionBlock"];
}
self.currentRequest = nil;
if (completionBlock != nil) {
completionBlock([NSDictionary dictionaryWithObject:error forKey:NSUnderlyingErrorKey]);
}
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanLanguagesDownloadFailedNotification object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:NSUnderlyingErrorKey]];
};
}
}
- (void)downloadFailedForKeyboard:(NSDictionary *)keyboardInfo error:(NSError *)error {
id kbInfo = [keyboardInfo objectForKey:kKeymanKeyboardInfoKey];
if (kbInfo == nil)
kbInfo = keyboardInfo;
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardDownloadFailedNotification
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
kbInfo, kKeymanKeyboardInfoKey,
error, NSUnderlyingErrorKey,
nil]];
}
- (eKMKeyboardState)stateForKeyboardWithID:(NSString *)keyboardID {
eKMKeyboardState retVal = kKMKeyboardStateNone;
NSString *latestVersion = [self latestKeyboardFileVersionWithID:keyboardID];
if ([[self keyboardIdForCurrentRequest] isEqualToString:keyboardID]) {
retVal = kKMKeyboardStateDownloading;
} else if (latestVersion == nil) {
retVal = kKMKeyboardStateNeedsDownload;
}
else {
// Check version
if (self.keyboardsInfo != nil) {
NSString *kbVersion = [[self.keyboardsInfo objectForKey:keyboardID] objectForKey:kKeymanKeyboardVersionKey];
if (kbVersion == nil)
kbVersion = @"1.0";
if ([self compareVersions:latestVersion v2:kbVersion] >= 0)
retVal = kKMKeyboardStateUpToDate;
else
retVal = kKMKeyboardStateNeedsUpdate;
}
else
retVal = kKMKeyboardStateUpToDate;
}
/* // Check modified date (No longer used)
NSDictionary *attrs = [fileMan attributesOfItemAtPath:filePath error:nil];
NSDate *localModDate = [attrs objectForKey:NSFileModificationDate];
NSDate *modDate = [self dateFromKeymanDateString:[[self.keyboardsInfo objectForKey:keyboardID] objectForKey:kKeymanKeyboardModifiedKey]];
if (modDate != nil && localModDate != nil && [localModDate timeIntervalSinceDate:modDate] < 0)
retVal = kKMKeyboardStateNeedsUpdate;
else
retVal = kKMKeyboardStateUpToDate;
*/
return retVal;
}
- (NSString *)latestKeyboardFileVersionWithID:(NSString *)keyboardID {
NSString *kbFileVersion = nil;
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self activeLanguageDirectory] error:nil];
for (NSString *filename in dirContents) {
NSInteger index = [filename rangeOfString:[NSString stringWithFormat:@"%@-", keyboardID]].location;
if (index == 0) {
NSInteger firstIndex = [filename rangeOfString:@"-" options:NSBackwardsSearch].location+1;
NSInteger lastIndex = [filename rangeOfString:@".js" options:NSBackwardsSearch].location;
NSRange range = NSMakeRange(firstIndex, lastIndex - firstIndex);
NSString *v = [filename substringWithRange:range];
if (kbFileVersion != nil) {
if ([self compareVersions:v v2:kbFileVersion] > 0)
kbFileVersion = [v copy];
}
else if ([self compareVersions:v v2:v] == 0)
kbFileVersion = [v copy];
}
}
return kbFileVersion;
}
- (NSInteger)compareVersions:(NSString *)v1 v2:(NSString *)v2 {
// returns;
// -2 if v1 or v2 is invalid
// 0 if v1 = v2
// -1 if v1 < v2
// 1 if v1 > v2
if (v1 == nil || v2 == nil)
return -2;
if (!v1.length || !v2.length)
return -2;
NSArray *v1Values = [v1 componentsSeparatedByString:@"."];
NSArray *v2Values = [v2 componentsSeparatedByString:@"."];
NSUInteger len = (v1Values.count >= v2Values.count?v1Values.count:v2Values.count);
for (int i = 0; i < len; i++) {
NSString *vStr1 = @"0";
if (i < v1Values.count)
vStr1 = [v1Values objectAtIndex:i];
NSString *vStr2 = @"0";
if (i < v2Values.count)
vStr2 = [v2Values objectAtIndex:i];
NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
NSNumber *vInt1 = [numFormatter numberFromString:vStr1];
NSNumber *vInt2 = [numFormatter numberFromString:vStr2];
int iV1, iV2, iV1_, iV2_;
if (vInt1 != nil) { iV1 = [vInt1 intValue]; iV1_ = 0; }
else { iV1 = 0; iV1_ = 0; }
if (vInt2 != nil) { iV2 = [vInt2 intValue]; iV2_ = 0; }
else { iV2 = 0; iV2_ = 0; }
if (vInt1 == nil) {
if (i != (v1Values.count - 1))
return -2;
if ([vStr1.lowercaseString hasSuffix:@"b"]) {
NSNumber *vInt1_ = [numFormatter numberFromString:[vStr1 substringToIndex:vStr1.length-1]];
if (vInt1_ == nil)
return -2;
iV1 = [vInt1_ intValue];
iV1_ = -100;
} else if ([vStr1.lowercaseString hasSuffix:@"a"]) {
NSNumber *vInt1_ = [numFormatter numberFromString:[vStr1 substringToIndex:vStr1.length-1]];
if (vInt1_ == nil)
return -2;
iV1 = [vInt1_ intValue];
iV1_ = -200;
} else
return -2;
}
if (vInt2 == nil) {
if (i != (v2Values.count - 1))
return -2;
if ([vStr2.lowercaseString hasSuffix:@"b"]) {
NSNumber *vInt2_ = [numFormatter numberFromString:[vStr2 substringToIndex:vStr2.length-1]];
if (vInt2_ == nil)
return -2;
iV2 = [vInt2_ intValue];
iV2_ = -100;
}
else if ([vStr2.lowercaseString hasSuffix:@"a"]) {
NSNumber *vInt2_ = [numFormatter numberFromString:[vStr2 substringToIndex:vStr2.length-1]];
if (vInt2_ == nil)
return -2;
iV2 = [vInt2_ intValue];
iV2_ = -200;
}
else
return -2;
}
if (iV1 == iV2) {
if (iV1_ == iV2_)
continue;
if (iV1_ < iV2_)
return -1;
if (iV1_ > iV2_)
return 1;
}
else if (iV1 < iV2) {
return -1;
}
else if (iV1 > iV2) {
return 1;
}
}
return 0;
}
- (NSString *)keyboardIdForCurrentRequest {
NSString *retVal = nil;
if (self.currentRequest != nil) {
NSString *tmpStr = [[self.currentRequest.url absoluteString] lastPathComponent];
NSRange dotRange = [tmpStr rangeOfString:@".js" options:NSBackwardsSearch];
if (dotRange.location != NSNotFound) {
retVal = [tmpStr substringToIndex:dotRange.location];
}
}
else if (self.downloadQueue != nil) {
NSString *kbId;
id kbInfo = [[self.downloadQueue userInfo] objectForKey:kKeymanKeyboardInfoKey];
if ([kbInfo isKindOfClass:[NSDictionary class]])
kbId = [(NSDictionary *)kbInfo objectForKey:kKeymanKeyboardIdKey];
else
kbId = [[(NSArray *)kbInfo objectAtIndex:0] objectForKey:kKeymanKeyboardIdKey];
retVal = kbId;
}
return retVal;
}
+ (BOOL)retinaScreen {
return ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale == 2.0));
}
+ (CGFloat)keyboardHeight {
BOOL isPortrait = YES;
if (kIsKeymanSystemKeyboard)
isPortrait = [KMInputViewController isPortrait];
else {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
isPortrait = UIInterfaceOrientationIsPortrait(orientation);
}
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
if (isPortrait) {
return kIsKeymanSystemKeyboard?kKeymanPadPortraitSystemKeyboardHeight:kKeymanPadPortraitInAppKeyboardHeight;
}
else {
return kIsKeymanSystemKeyboard?kKeymanPadLandscapeSystemKeyboardHeight:kKeymanPadLandscapeInAppKeyboardHeight;
}
}
else {
if (isPortrait) {
return kIsKeymanSystemKeyboard?kKeymanPhonePortraitSystemKeyboardHeight:kKeymanPhonePortraitInAppKeyboardHeight;
}
else {
return kIsKeymanSystemKeyboard?kKeymanPhoneLandscapeSystemKeyboardHeight:kKeymanPhoneLandscapeInAppKeyboardHeight;
}
}
}
#pragma mark - Private Methods
+ (CGFloat)keyboardHeightWithOrientation:(UIInterfaceOrientation)orientation {
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
if (UIInterfaceOrientationIsPortrait(orientation)) {
return kIsKeymanSystemKeyboard?kKeymanPadPortraitSystemKeyboardHeight:kKeymanPadPortraitInAppKeyboardHeight;
}
else {
return kIsKeymanSystemKeyboard?kKeymanPadLandscapeSystemKeyboardHeight:kKeymanPadLandscapeInAppKeyboardHeight;
}
}
else {
if (UIInterfaceOrientationIsPortrait(orientation)) {
return kIsKeymanSystemKeyboard?kKeymanPhonePortraitSystemKeyboardHeight:kKeymanPhonePortraitInAppKeyboardHeight;
}
else {
return kIsKeymanSystemKeyboard?kKeymanPhoneLandscapeSystemKeyboardHeight:kKeymanPhoneLandscapeInAppKeyboardHeight;
}
}
}
+ (CGFloat)keyboardWidth {
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
int iosVersion = [[[UIDevice currentDevice] systemVersion] intValue];
if (iosVersion >= 8)
return screenSize.width;
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsPortrait(orientation))
return screenSize.width;
else
return screenSize.height;
}
- (void)resizeKeyboard {
static CGFloat xWidth = 0;
static CGFloat xHeight = 0;
CGFloat kbWidth = [[self class] keyboardWidth];
CGFloat kbHeight = [[self class] keyboardHeight];
if (didResizeToOrientation && kIsKeymanSystemKeyboard && xWidth == kbWidth && xHeight == kbHeight) {
didResizeToOrientation = NO;
return;
}
else {
xWidth = kbWidth;
xHeight = kbHeight;
}
[webView setFrame:CGRectMake(0.0f, 0.0f, kbWidth, kbHeight)];
// Workaround for WKWebView bug with landscape orientation
if (kIsKeymanSystemKeyboard && [[self class] isWKWebView:webView])
[self performSelector:@selector(resizeDelay) withObject:self afterDelay:1.0];
NSInteger oskHeight = kbHeight;
if (!kIsKeymanSystemKeyboard)
oskHeight = oskHeight - oskHeight%20;
else
oskHeight = oskHeight - oskHeight%10;
NSString *jsString = [NSString stringWithFormat:@"setOskWidth(%ld);", (long)kbWidth];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
jsString = [NSString stringWithFormat:@"setOskHeight(%ld);", (long)oskHeight];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
}
- (void)resizeDelay {
// + 1000 to work around iOS bug with resizing on landscape orientation. Technically we only
// need this for landscape but it doesn't hurt to do it with both. 1000 is a big number that
// should hopefully work on all devices.
CGFloat kbWidth = [[self class] keyboardWidth];
CGFloat kbHeight = [[self class] keyboardHeight];
[webView setFrame:CGRectMake(0.0f, 0.0f, kbWidth, kbHeight + 1000)];
}
- (void)resizeKeyboardIfNeeded {
static CGFloat xWidth = 0;
static CGFloat xHeight = 0;
CGFloat kbWidth = [[self class] keyboardWidth];
CGFloat kbHeight = [[self class] keyboardHeight];
if (kbWidth != xWidth || kbHeight != xHeight) {
[self resizeKeyboard];
xWidth = kbWidth;
xHeight = kbHeight;
}
}
- (void)resizeKeyboardWithOrientation:(UIInterfaceOrientation)orientation {
CGFloat kbWidth = [[self class] keyboardWidth];
CGFloat kbHeight = [[self class] keyboardHeightWithOrientation:orientation];
[webView setFrame:CGRectMake(0.0f, 0.0f, kbWidth, kbHeight)];
NSInteger oskHeight = kbHeight;
if (!kIsKeymanSystemKeyboard)
oskHeight = oskHeight - oskHeight%20;
else
oskHeight = oskHeight - oskHeight%10;
NSString *jsString = [NSString stringWithFormat:@"setOskWidth(%ld);", (long)kbWidth];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
jsString = [NSString stringWithFormat:@"setOskHeight(%ld);", (long)oskHeight];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
}
- (UIScrollView *)keymanScrollView {
if ([[self class] isWKWebView:webView])
return [(WKWebView *)webView scrollView];
else
return [(UIWebView *)webView scrollView];
}
- (BOOL)canAccessSharedContainer {
if ([self sharedKeymanDirectory] == nil)
return NO;
if (!kIsKeymanSystemKeyboard)
return YES;
NSFileManager *fileMan = [NSFileManager defaultManager];
NSString *sharedKeymanDir = [NSString stringWithString:[self sharedKeymanDirectory]];
NSString *path = [sharedKeymanDir stringByAppendingPathComponent:kKeymanFullFileName];
BOOL canAccess = [fileMan fileExistsAtPath:path];
return canAccess;
}
- (void)copyUserDefaultsToSharedContainer {
NSUserDefaults *sharedUserData = nil;
if ([[NSUserDefaults standardUserDefaults] respondsToSelector:@selector(initWithSuiteName:)])
sharedUserData = [[NSUserDefaults alloc] initWithSuiteName:applicationGroupIdentifier];
if (sharedUserData == nil)
return;
NSUserDefaults *defaultUserData = [NSUserDefaults standardUserDefaults];
if ([sharedUserData objectForKey:kKeymanUserKeyboardsListKey] == nil)
[sharedUserData setObject:[defaultUserData objectForKey:kKeymanUserKeyboardsListKey]
forKey:kKeymanUserKeyboardsListKey];
if ([sharedUserData objectForKey:kKeymanUserCurrentKeyboardKey] == nil)
[sharedUserData setObject:[defaultUserData objectForKey:kKeymanUserCurrentKeyboardKey]
forKey:kKeymanUserCurrentKeyboardKey];
if ([sharedUserData objectForKey:kKeymanEngineVersion] == nil)
[sharedUserData setObject:[defaultUserData objectForKey:kKeymanEngineVersion]
forKey:kKeymanEngineVersion];
if ([sharedUserData objectForKey:kKeymanKeyboardPickerDisplayedKey] == nil)
[sharedUserData setObject:[defaultUserData objectForKey:kKeymanKeyboardPickerDisplayedKey]
forKey:kKeymanKeyboardPickerDisplayedKey];
[sharedUserData synchronize];
}
- (void)copyUserDefaultsFromSharedContainer {
NSUserDefaults *sharedUserData = nil;
if ([[NSUserDefaults standardUserDefaults] respondsToSelector:@selector(initWithSuiteName:)])
sharedUserData = [[NSUserDefaults alloc] initWithSuiteName:applicationGroupIdentifier];
if (sharedUserData == nil)
return;
NSUserDefaults *defaultUserData = [NSUserDefaults standardUserDefaults];
if ([sharedUserData objectForKey:kKeymanUserKeyboardsListKey] != nil)
[defaultUserData setObject:[sharedUserData objectForKey:kKeymanUserKeyboardsListKey]
forKey:kKeymanUserKeyboardsListKey];
if ([sharedUserData objectForKey:kKeymanEngineVersion] != nil)
[defaultUserData setObject:[sharedUserData objectForKey:kKeymanEngineVersion]
forKey:kKeymanEngineVersion];
[defaultUserData synchronize];
}
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
{
NSURL* URL= [NSURL fileURLWithPath: filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
}
return success;
}
- (BOOL)copyAndExcludeFromBackup:(NSString*)pathToCopy destinationPath:(NSString*)destinationPath {
NSFileManager *fileMan = [NSFileManager defaultManager];
BOOL isDirectory;
BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
NSError *error;
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}
else
return false;
[self addSkipBackupAttributeToItemAtPath:destinationPath];
return true;
}
return false;
}
- (void)copyKeymanFilesToSharedContainer {
if ([self sharedKeymanDirectory] == nil)
return;
NSFileManager *fileMan = [NSFileManager defaultManager];
NSString *defaultKeymanDir = [NSString stringWithString:[self defaultKeymanDirectory]];
NSString *sharedKeymanDir = [NSString stringWithString:[self sharedKeymanDirectory]];
NSString *defaultLangDir = [NSString stringWithString:[self defaultLanguageDirectory]];
NSString *sharedLangDir = [NSString stringWithString:[self sharedLanguageDirectory]];
NSString *defaultFontDir = [NSString stringWithString:[self defaultFontDirectory]];
NSString *sharedFontDir = [NSString stringWithString:[self sharedFontDirectory]];
//NSError *error;
//BOOL isDirectory;
NSArray *dirContents = [fileMan contentsOfDirectoryAtPath:defaultKeymanDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [defaultKeymanDir stringByAppendingPathComponent:file];
NSString *destinationPath = [sharedKeymanDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/* BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}
}*/
}
dirContents = [fileMan contentsOfDirectoryAtPath:defaultLangDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [defaultLangDir stringByAppendingPathComponent:file];
NSString *destinationPath = [sharedLangDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/* BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}
}*/
}
dirContents = [fileMan contentsOfDirectoryAtPath:defaultFontDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [defaultFontDir stringByAppendingPathComponent:file];
NSString *destinationPath = [sharedFontDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/* BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}
}*/
}
}
- (void)copyKeymanFilesFromSharedContainer {
if ([self sharedKeymanDirectory] == nil)
return;
NSFileManager *fileMan = [NSFileManager defaultManager];
NSString *defaultKeymanDir = [NSString stringWithString:[self defaultKeymanDirectory]];
NSString *sharedKeymanDir = [NSString stringWithString:[self sharedKeymanDirectory]];
NSString *defaultLangDir = [NSString stringWithString:[self defaultLanguageDirectory]];
NSString *sharedLangDir = [NSString stringWithString:[self sharedLanguageDirectory]];
NSString *defaultFontDir = [NSString stringWithString:[self defaultFontDirectory]];
NSString *sharedFontDir = [NSString stringWithString:[self sharedFontDirectory]];
NSArray *dirContents = [fileMan contentsOfDirectoryAtPath:sharedKeymanDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [sharedKeymanDir stringByAppendingPathComponent:file];
NSString *destinationPath = [defaultKeymanDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/*BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}
}*/
}
dirContents = [fileMan contentsOfDirectoryAtPath:sharedLangDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [sharedLangDir stringByAppendingPathComponent:file];
NSString *destinationPath = [defaultLangDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/* BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}
}*/
}
dirContents = [fileMan contentsOfDirectoryAtPath:sharedFontDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [sharedFontDir stringByAppendingPathComponent:file];
NSString *destinationPath = [defaultFontDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/*BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}
}*/
}
[self registerCustomFonts];
}
- (void)copyWebFilesToLibrary {
NSString *libraryDirectory = [self activeKeymanDirectory];
if (![libraryDirectory length]) {
[self KMLog:@"Could not locate library directory! Could not copy Keyman files." checkDebugPrinting:NO];
return;
}
NSError *error = nil;
NSBundle *keymanBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"Keyman" ofType:@"bundle"]];
NSString *pathToCopy = [keymanBundle pathForResource:kKeymanFileName ofType:kKeymanFileExtension];
NSString *destinationPath = nil;
if ([pathToCopy length]) {
destinationPath = [libraryDirectory stringByAppendingPathComponent:kKeymanFullFileName];
// copy if destination does not exist or replace if source is newer
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/*if (![fileMan fileExistsAtPath:destinationPath]) {
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
NSURL *URL = [NSURL fileURLWithPath:destinationPath];
[URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:nil];
}
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:nil];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
NSURL *URL = [NSURL fileURLWithPath:destinationPath];
[URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:nil];
}*/
}
else {
error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Could not locate Keyman html file for copying." forKey:NSLocalizedDescriptionKey]];
}
if (error == nil) {
pathToCopy = [keymanBundle pathForResource:kKeymaniOSCodeFileName ofType:nil];
if ([pathToCopy length]) {
destinationPath = [libraryDirectory stringByAppendingPathComponent:[pathToCopy lastPathComponent]];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/*// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath]) {
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
//NSURL *URL = [NSURL fileURLWithPath:destinationPath];
//[URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:nil];
}
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:nil];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
//NSURL *URL = [NSURL fileURLWithPath:destinationPath];
//[URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:nil];
}*/
}
else {
error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Could not locate Keyman javascript for copying." forKey:NSLocalizedDescriptionKey]];
}
}
if (error == nil) {
pathToCopy = [keymanBundle pathForResource:[NSString stringWithFormat:@"%@-1.6.js", kKeymanDefaultKeyboardID] ofType:nil];
if ([pathToCopy length]) {
destinationPath = [[self activeLanguageDirectory] stringByAppendingPathComponent:[pathToCopy lastPathComponent]];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/*// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath]) {
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
//NSURL *URL = [NSURL fileURLWithPath:destinationPath];
//[URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:nil];
}
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:nil];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
//NSURL *URL = [NSURL fileURLWithPath:destinationPath];
//[URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:nil];
}*/
}
else {
error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Could not locate default Keyman keyboard for copying." forKey:NSLocalizedDescriptionKey]];
}
}
if (error == nil) {
pathToCopy = [keymanBundle pathForResource:@"DejaVuSans" ofType:@"ttf"];
if ([pathToCopy length]) {
destinationPath = [[self activeFontDirectory] stringByAppendingPathComponent:[pathToCopy lastPathComponent]];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/*// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath]) {
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
//NSURL *URL = [NSURL fileURLWithPath:destinationPath];
//[URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:nil];
}
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:nil];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
//NSURL *URL = [NSURL fileURLWithPath:destinationPath];
//[URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:nil];
}*/
}
else {
error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Could not locate DejaVuSans.ttf for copying." forKey:NSLocalizedDescriptionKey]];
}
}
if (error == nil) {
pathToCopy = [keymanBundle pathForResource:@"kmwosk" ofType:@"css"];
if ([pathToCopy length]) {
destinationPath = [libraryDirectory stringByAppendingPathComponent:[pathToCopy lastPathComponent]];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/*// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath]) {
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
}
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:nil];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}*/
}
else {
error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Could not locate kmwosk.css for copying." forKey:NSLocalizedDescriptionKey]];
}
}
if (error == nil) {
pathToCopy = [keymanBundle pathForResource:@"keymanweb-osk" ofType:@"ttf"];
if ([pathToCopy length]) {
destinationPath = [libraryDirectory stringByAppendingPathComponent:[pathToCopy lastPathComponent]];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/*// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:nil];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
}*/
}
else {
error = [NSError errorWithDomain:@"Keyman" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Could not locate keymanweb-osk.ttf for copying." forKey:NSLocalizedDescriptionKey]];
}
}
if (error != nil)
[self KMLog:[NSString stringWithFormat:@"Error copying Keyman files: %@", error] checkDebugPrinting:NO];
}
- (NSInteger)compareFileModDates:(NSString *)srcPath destinationPath:(NSString *)desPath {
// returns;
// -2 on error
// 0 if srcModDate = desModDate
// -1 if srcModDate < desModDate
// 1 if srcModDate > desModDate
NSError *e;
NSDictionary *srcAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:srcPath error:&e];
if (e != nil) return -2;
NSDictionary *desAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:desPath error:&e];
if (e != nil) return -2;
NSDate *srcModDate = [srcAttrs fileModificationDate];
NSDate *desModDate = [desAttrs fileModificationDate];
NSComparisonResult result = [srcModDate compare:desModDate];
if (result == NSOrderedDescending)
return 1;
else if (result == NSOrderedAscending)
return -1;
else
return 0;
}
- (void)renameOldKeyboardFiles {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *langDir = [self activeLanguageDirectory];
NSArray *dirContents = [fileManager contentsOfDirectoryAtPath:langDir error:nil];
for (NSString *filename in dirContents) {
if ([filename rangeOfString:@"-"].location == NSNotFound) {
if ([filename isEqualToString:@"us.js"]) {
NSString *filePath = [[self activeLanguageDirectory] stringByAppendingPathComponent:filename];
[fileManager removeItemAtPath:filePath error:nil];
NSUserDefaults *userData = [self activeUserDefaults];
NSDictionary *curKB = [userData objectForKey:kKeymanUserCurrentKeyboardKey];
if ([[curKB objectForKey:kKeymanKeyboardIdKey] isEqualToString:@"us"]) {
[userData removeObjectForKey:kKeymanUserCurrentKeyboardKey];
[userData synchronize];
}
}
else {
NSString *newFilename = [NSString stringWithFormat:@"%@-1.0.js", [filename substringToIndex:[filename length]-3]];
NSString *filePath = [langDir stringByAppendingPathComponent:filename];
NSString *newFilePath = [langDir stringByAppendingPathComponent:newFilename];
[fileManager moveItemAtPath:filePath toPath:newFilePath error:nil];
}
}
else {
NSString *oldFilename = [NSString stringWithFormat:@"%@.js", [filename substringToIndex:[filename rangeOfString:@"-"].location]];
NSString *oldFilePath = [langDir stringByAppendingPathComponent:oldFilename];
if ([fileManager fileExistsAtPath:oldFilePath])
[fileManager removeItemAtPath:oldFilePath error:nil];
}
}
}
- (NSString *)defaultKeymanDirectory {
NSString *keymanDir = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
if ([paths count]) {
keymanDir = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"keyman"];
if ([keymanDir length]) {
[[NSFileManager defaultManager] createDirectoryAtPath:keymanDir withIntermediateDirectories:YES attributes:nil error:nil];
}
}
return keymanDir;
}
- (NSString *)defaultLanguageDirectory {
NSString *langDir = nil;
langDir = [[self defaultKeymanDirectory] stringByAppendingPathComponent:@"languages"];
if ([langDir length]) {
[[NSFileManager defaultManager] createDirectoryAtPath:langDir withIntermediateDirectories:YES attributes:nil error:nil];
}
return langDir;
}
- (NSString *)defaultFontDirectory {
NSString *fontDir = nil;
fontDir = [[self defaultKeymanDirectory] stringByAppendingPathComponent:@"fonts"];
if ([fontDir length]) {
[[NSFileManager defaultManager] createDirectoryAtPath:fontDir withIntermediateDirectories:YES attributes:nil error:nil];
}
return fontDir;
}
+ (void)setApplicationGroupIdentifier:(NSString *)groupID {
applicationGroupIdentifier = [NSString stringWithString:groupID];
}
- (NSString *)sharedContainerPath {
NSString *path = nil;
NSFileManager *fileMan = [NSFileManager defaultManager];
if ([fileMan respondsToSelector:@selector(containerURLForSecurityApplicationGroupIdentifier:)])
path = [[fileMan containerURLForSecurityApplicationGroupIdentifier:applicationGroupIdentifier] path];
return path;
}
- (NSString *)sharedKeymanDirectory {
NSString *sharedKeymanDirectory = nil;
NSString *sharedPath = [self sharedContainerPath];
if (sharedPath != nil) {
sharedKeymanDirectory = [sharedPath stringByAppendingPathComponent:@"keyman"];
if ([sharedKeymanDirectory length]) {
[[NSFileManager defaultManager] createDirectoryAtPath:sharedKeymanDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
}
return sharedKeymanDirectory;
}
- (NSString *)sharedLanguageDirectory {
NSString *sharedLanguageDirectory = nil;
sharedLanguageDirectory = [[self sharedKeymanDirectory] stringByAppendingPathComponent:@"languages"];
if ([sharedLanguageDirectory length]) {
[[NSFileManager defaultManager] createDirectoryAtPath:sharedLanguageDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
return sharedLanguageDirectory;
}
- (NSString *)sharedFontDirectory {
NSString *sharedFontDirectory = nil;
sharedFontDirectory = [[self sharedKeymanDirectory] stringByAppendingPathComponent:@"fonts"];
if ([sharedFontDirectory length]) {
[[NSFileManager defaultManager] createDirectoryAtPath:sharedFontDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
return sharedFontDirectory;
}
- (NSString *)activeKeymanDirectory {
NSString *activeKeymanDirectory = nil;
if ([self canAccessSharedContainer])
activeKeymanDirectory = [self sharedKeymanDirectory];
else
activeKeymanDirectory = [self defaultKeymanDirectory];
return activeKeymanDirectory;
}
- (NSString *)activeLanguageDirectory {
NSString *activeLanguageDirectory = nil;
if ([self canAccessSharedContainer])
activeLanguageDirectory = [self sharedLanguageDirectory];
else
activeLanguageDirectory = [self defaultLanguageDirectory];
return activeLanguageDirectory;
}
- (NSString *)activeFontDirectory {
NSString *activeFontDirectory = nil;
if ([self canAccessSharedContainer])
activeFontDirectory = [self sharedFontDirectory];
else
activeFontDirectory = [self defaultFontDirectory];
return activeFontDirectory;
}
- (NSUserDefaults *)activeUserDefaults {
NSUserDefaults *userDefaults = nil;
if ([self canAccessSharedContainer])
userDefaults = [[NSUserDefaults alloc] initWithSuiteName:applicationGroupIdentifier];
else
userDefaults = [NSUserDefaults standardUserDefaults];
return userDefaults;
}
- (NSString *)keyboardPathForID:(NSString *)keyboardID keyboardVersion:(NSString *)kbVersion {
NSString *retVal = nil;
if ([keyboardID length]) {
if (kbVersion == nil || ![kbVersion length]) {
kbVersion = [self latestKeyboardFileVersionWithID:keyboardID];
if (kbVersion == nil)
return retVal;
}
retVal = [[[self activeLanguageDirectory] stringByAppendingPathComponent:keyboardID] stringByAppendingString:[NSString stringWithFormat:@"-%@%@", kbVersion, @".js"]];
}
return retVal;
}
- (NSString *)keyboardPathForFilename:(NSString *)filename keyboardVersion:(NSString *)kbVersion {
NSString *retVal = nil;
if ([filename length]) {
if ([filename rangeOfString:@"-" options:NSBackwardsSearch].location == NSNotFound) {
NSString *fn = [NSString stringWithFormat:@"%@-%@.js", [filename substringToIndex:filename.length-3], (kbVersion != nil)?kbVersion:@"1.0"];
retVal = [[self activeLanguageDirectory] stringByAppendingPathComponent:fn];
}
else
retVal = [[self activeLanguageDirectory] stringByAppendingPathComponent:filename];
}
return retVal;
}
- (NSString *)fontPathForFilename:(NSString *)filename {
NSString *retVal = nil;
if ([filename length]) {
retVal = [[self activeFontDirectory] stringByAppendingPathComponent:filename];
}
return retVal;
}
- (void)createKeyboardsInfo {
NSString *langId = nil;
NSString *kbID = nil;
NSString *kbVersion = nil;
NSString *isRTL = nil;
NSString *dictKey = nil;
NSString *kbBaseUri = [self.options objectForKey:kKeymanKeyboardBaseURIKey];
NSString *kbUri = nil;
NSDictionary *langDict = nil;
NSDictionary *kbDict = nil;
NSArray *keyboards = nil;
keyboardsInfo_ = [[NSMutableDictionary alloc] initWithCapacity:50];
keyboardsDictionary_ = [[NSMutableDictionary alloc] initWithCapacity:50];
for (langDict in self.languages) {
keyboards = [langDict objectForKey:kKeymanLanguageKeyboardsKey];
for (kbDict in keyboards) {
langId = [langDict objectForKey:kKeymanIdKey];
kbID = [kbDict objectForKey:kKeymanIdKey];
kbVersion = [kbDict objectForKey:kKeymanKeyboardVersionKey];
if (kbVersion == nil)
kbVersion = @"1.0";
isRTL = [kbDict objectForKey:kKeymanKeyboardRTLKey];
isRTL = (isRTL == nil)?@"N":@"Y";
dictKey = [NSString stringWithFormat:@"%@_%@",langId, kbID];
if ([self.keyboardsInfo objectForKey:kbID] == nil) {
kbUri = [NSString stringWithFormat:@"%@%@", kbBaseUri, [kbDict objectForKey:kKeymanKeyboardFilenameKey]];
[self.keyboardsInfo setObject:[NSDictionary dictionaryWithObjectsAndKeys:
[kbDict objectForKey:kKeymanNameKey], kKeymanKeyboardNameKey,
kbVersion, kKeymanKeyboardVersionKey,
isRTL, kKeymanKeyboardRTLKey,
[kbDict objectForKey:kKeymanKeyboardModifiedKey], kKeymanKeyboardModifiedKey,
[kbDict objectForKey:kKeymanKeyboardFileSizeKey], kKeymanKeyboardFileSizeKey,
kbUri, kKeymanKeyboardURIKey,
[kbDict objectForKey:kKeymanFontKey], kKeymanFontKey,
nil]
forKey:kbID];
}
if ([self.keyboardsDictionary objectForKey:dictKey] == nil) {
NSDictionary *oskFont = [kbDict objectForKey:kKeymanOskFontKey];
[keyboardsDictionary_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
[kbDict objectForKey:kKeymanIdKey], kKeymanKeyboardIdKey,
[langDict objectForKey:kKeymanIdKey], kKeymanLanguageIdKey,
[kbDict objectForKey:kKeymanNameKey], kKeymanKeyboardNameKey,
[langDict objectForKey:kKeymanNameKey], kKeymanLanguageNameKey,
kbVersion, kKeymanKeyboardVersionKey,
isRTL, kKeymanKeyboardRTLKey,
[self jsFontFromFontDictionary:[kbDict objectForKey:kKeymanFontKey]], kKeymanFontKey,
oskFont?[self jsFontFromFontDictionary:oskFont]:nil, kKeymanOskFontKey,
nil]
forKey:dictKey];
}
}
}
[self updateUserKeyboardsList];
}
- (void)updateUserKeyboardsList {
if (keyboardsDictionary_ == nil || keyboardsDictionary_.count == 0)
return;
NSUserDefaults *userData = [self activeUserDefaults];
CGFloat lastVersion = [self floatValueForVersionNumber:[userData objectForKey:kKeymanEngineVersion]];
CGFloat currentVersion = [self floatValueForVersionNumber:[self sdkVersion]];
if (lastVersion == currentVersion)
return;
NSMutableArray *userKbList = [[userData arrayForKey:kKeymanUserKeyboardsListKey] mutableCopy];
NSUInteger len = userKbList.count;
for (int i = 1; i < len; i++) {
NSString *kbID = [[userKbList objectAtIndex:i] objectForKey:kKeymanKeyboardIdKey];
NSString *langID = [[userKbList objectAtIndex:i] objectForKey:kKeymanLanguageIdKey];
NSMutableDictionary *kb = [[keyboardsDictionary_ objectForKey:[NSString stringWithFormat:@"%@_%@", langID, kbID]] mutableCopy];
if (kb != nil) {
[kb setObject:[self latestKeyboardFileVersionWithID:kbID] forKey:kKeymanKeyboardVersionKey];
[kb setObject:@"N" forKey:kKeymanCustomKeyboardKey];
[userKbList replaceObjectAtIndex:i withObject:kb];
}
else {
kb = [[userKbList objectAtIndex:i] mutableCopy];
[kb setObject:@"Y" forKey:kKeymanCustomKeyboardKey];
[userKbList replaceObjectAtIndex:i withObject:kb];
}
}
[userData setObject:userKbList forKey:kKeymanUserKeyboardsListKey];
[userData setObject:[self sdkVersion] forKey:kKeymanEngineVersion];
[userData synchronize];
}
- (CGFloat)floatValueForVersionNumber:(NSString *)versionNumber {
if (versionNumber == nil)
return 1.0f;
NSArray *numbers = [versionNumber componentsSeparatedByString:@"."];
NSMutableString *floatStr = [NSMutableString stringWithString:@""];
NSUInteger length = numbers.count;
for (int i = 0; i < length; i++) {
if (i == 0) {
[floatStr appendString:[NSString stringWithFormat:@"%@.", [numbers objectAtIndex:0]]];
if (length == 1)
[floatStr appendString:@"0"];
}
else {
[floatStr appendString:[numbers objectAtIndex:i]];
}
}
return [floatStr floatValue];
}
/* No longer used
- (NSDate *)dateFromKeymanDateString:(NSString *)dateString {
NSDate *retVal = nil;
if ([dateString length]) {
// format date
retVal = [self.dateFormatter dateFromString:dateString];
}
return retVal;
}
*/
- (void)reachabilityChanged:(NSNotification *)notification {
if (self.debugPrintingOn) {
NSString *reachStr = @"Not Reachable";
NetworkStatus status = [self.reachability currentReachabilityStatus];
if (status == ReachableViaWiFi) reachStr = @"Reachable Via WiFi";
if (status == ReachableViaWWAN) reachStr = @"Reachable Via WWan";
[self KMLog:[NSString stringWithFormat:@"Reachability changed to '%@'", reachStr] checkDebugPrinting:YES];
}
}
#pragma mark - WKNavigationDelegate
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSString *fragment = message.body;
if ([fragment hasPrefix:@"ios-log:"]) {
NSString *requestString = [fragment stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *logString = [[requestString componentsSeparatedByString:@":#iOS#"] objectAtIndex:1];
[self KMLog:[NSString stringWithFormat:@"WebView: %@", logString] checkDebugPrinting:NO];
}
else {
[self.webDelegate updatedFragment:fragment];
[self.inputDelegate updatedFragment:fragment];
[self updatedFragment:fragment];
}
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
NSString *lastComponent = [[webView URL] lastPathComponent];
NSString *fragment = [[webView URL] fragment];
if ([lastComponent isEqualToString:kKeymanFullFileName] && [fragment length] == 0) {
[self KMLog:@"Loaded keyboard." checkDebugPrinting:YES];
[self resizeKeyboard];
NSString *deviceType = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)?@"AppleMobile":@"AppleTablet";
[webView evaluateJavaScript:[NSString stringWithFormat:@"setDeviceType('%@');", deviceType] completionHandler:nil];
if ((self.keyboardID == nil || self.languageID == nil) && !shouldReloadKeyboard) {
NSString *kbID = kKeymanDefaultKeyboardID;
NSString *langID = kKeymanDefaultLanguageID;
NSString *kbName = kKeymanDefaultKeyboardName;
NSString *langName = kKeymanDefaultLanguageName;
NSString *font = kKeymanDefaultKeyboardFont;
NSString *oskFont = nil;
NSUserDefaults *userData = nil;
if (kIsKeymanSystemKeyboard)
userData = [NSUserDefaults standardUserDefaults];
else
userData = [self activeUserDefaults];
NSArray *userKeyboards = [[self activeUserDefaults] arrayForKey:kKeymanUserKeyboardsListKey];
if (userKeyboards != nil && [userKeyboards count] > 0) {
kbID = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanKeyboardIdKey];
langID = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanLanguageIdKey];
kbName = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanKeyboardNameKey];
langName = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanLanguageNameKey];
font = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanFontKey];
oskFont = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanOskFontKey];
}
NSDictionary *currentKB = [userData dictionaryForKey:kKeymanUserCurrentKeyboardKey];
//if (kIsKeymanSystemKeyboard && ![self canAccessSharedContainer])
//currentKB = nil;
if (currentKB != nil) {
NSString *cKbId = [currentKB objectForKey:kKeymanKeyboardIdKey];
NSString *cLangId = [currentKB objectForKey:kKeymanLanguageIdKey];
if ([self indexForUserKeyboardWithID:cKbId languageID:cLangId] >= 0) {
kbID = [currentKB objectForKey:kKeymanKeyboardIdKey];
langID = [currentKB objectForKey:kKeymanLanguageIdKey];
kbName = [[currentKB objectForKey:kKeymanKeyboardNameKey] stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"];
langName = [[currentKB objectForKey:kKeymanLanguageNameKey] stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"];
font = [currentKB objectForKey:kKeymanFontKey];
oskFont = [currentKB objectForKey:kKeymanOskFontKey];
}
}
[self setKeyboardWithID:kbID languageID:langID keyboardName:kbName languageName:langName font:font oskFont:oskFont];
}
NSDictionary *kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:
self.keyboardID, kKeymanKeyboardIdKey,
self.languageID, kKeymanLanguageIdKey,
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardLoadedNotification
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:kbInfo, kKeymanKeyboardInfoKey, nil]];
if (shouldReloadKeyboard) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resetKeyboard) object:nil];
[self performSelector:@selector(resetKeyboard) withObject:nil afterDelay:0.25];
shouldReloadKeyboard = NO;
}
}
}
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *fragment = [[request URL] fragment];
[self.webDelegate updatedFragment:fragment];
[self.inputDelegate updatedFragment:fragment];
[self updatedFragment:fragment];
NSString *requestString = [[[request URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
if ([requestString hasPrefix:@"ios-log:"]) {
NSString *logString = [[requestString componentsSeparatedByString:@":#iOS#"] objectAtIndex:1];
[self KMLog:[NSString stringWithFormat:@"WebView: %@", logString] checkDebugPrinting:NO];
}
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSURLRequest *request = [webView request];
NSString *lastComponent = [[request URL] lastPathComponent];
NSString *fragment = [[request URL] fragment];
if ([lastComponent isEqualToString:kKeymanFullFileName] && [fragment length] == 0) {
[self KMLog:@"Loaded keyboard." checkDebugPrinting:YES];
[self resizeKeyboard];
NSString *deviceType = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)?@"AppleMobile":@"AppleTablet";
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setDeviceType('%@');", deviceType]];
if ((self.keyboardID == nil || self.languageID == nil) && !shouldReloadKeyboard) {
NSString *kbID = kKeymanDefaultKeyboardID;
NSString *langID = kKeymanDefaultLanguageID;
NSString *kbName = kKeymanDefaultKeyboardName;
NSString *langName = kKeymanDefaultLanguageName;
NSString *font = kKeymanDefaultKeyboardFont;
NSString *oskFont = nil;
NSUserDefaults *userData = nil;
if (kIsKeymanSystemKeyboard)
userData = [NSUserDefaults standardUserDefaults];
else
userData = [self activeUserDefaults];
NSArray *userKeyboards = [[self activeUserDefaults] arrayForKey:kKeymanUserKeyboardsListKey];
if (userKeyboards != nil && [userKeyboards count] > 0) {
kbID = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanKeyboardIdKey];
langID = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanLanguageIdKey];
kbName = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanKeyboardNameKey];
langName = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanLanguageNameKey];
font = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanFontKey];
oskFont = [[userKeyboards objectAtIndex:0] objectForKey:kKeymanOskFontKey];
}
NSDictionary *currentKB = [userData dictionaryForKey:kKeymanUserCurrentKeyboardKey];
//if (kIsKeymanSystemKeyboard && ![self canAccessSharedContainer])
//currentKB = nil;
if (currentKB != nil) {
NSString *cKbId = [currentKB objectForKey:kKeymanKeyboardIdKey];
NSString *cLangId = [currentKB objectForKey:kKeymanLanguageIdKey];
NSInteger cIndex = [self indexForUserKeyboardWithID:cKbId languageID:cLangId];
if (cIndex >= 0) {
kbID = [[userKeyboards objectAtIndex:cIndex] objectForKey:kKeymanKeyboardIdKey];
langID = [[userKeyboards objectAtIndex:cIndex] objectForKey:kKeymanLanguageIdKey];
kbName = [[userKeyboards objectAtIndex:cIndex] objectForKey:kKeymanKeyboardNameKey];
langName = [[userKeyboards objectAtIndex:cIndex] objectForKey:kKeymanLanguageNameKey];
font = [[userKeyboards objectAtIndex:cIndex] objectForKey:kKeymanFontKey];
oskFont = [[userKeyboards objectAtIndex:cIndex] objectForKey:kKeymanOskFontKey];
/*
kbID = [currentKB objectForKey:kKeymanKeyboardIdKey];
langID = [currentKB objectForKey:kKeymanLanguageIdKey];
kbName = [[currentKB objectForKey:kKeymanKeyboardNameKey] stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"];
langName = [[currentKB objectForKey:kKeymanLanguageNameKey] stringByReplacingOccurrencesOfString:@"\\'" withString:@"'"];
font = [currentKB objectForKey:kKeymanFontKey];
oskFont = [currentKB objectForKey:kKeymanOskFontKey];
*/
}
}
[self setKeyboardWithID:kbID languageID:langID keyboardName:kbName languageName:langName font:font oskFont:oskFont];
}
NSDictionary *kbInfo = [NSDictionary dictionaryWithObjectsAndKeys:
self.keyboardID, kKeymanKeyboardIdKey,
self.languageID, kKeymanLanguageIdKey,
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardLoadedNotification
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:kbInfo, kKeymanKeyboardInfoKey, nil]];
if (shouldReloadKeyboard) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resetKeyboard) object:nil];
[self performSelector:@selector(resetKeyboard) withObject:nil afterDelay:0.25];
shouldReloadKeyboard = NO;
}
}
}
- (CGRect)getKeyFrameWithX:(CGFloat)x Y:(CGFloat)y W:(CGFloat)w H:(CGFloat)h {
BOOL isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad;
CGFloat adjY = isPad?-0.5:-1.0;
CGRect frame = CGRectMake(x-w/2.0f, y-adjY, w, h);
return frame;
}
- (void)updatedFragment:(NSString *)fragment {
if (fragment == nil || [fragment length] == 0)
return;
if ([fragment rangeOfString:@"showKeyPreview"].location != NSNotFound) {
BOOL isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad;
if (isPad)
return;
if (kIsKeymanSystemKeyboard && !self.isSystemKeyboardTopBarEnabled)
return;
if (subKeysView != nil)
return;
[self dismissKeyPreview];
[self clearSubKeyArrays];
NSRange range1 = [fragment rangeOfString:@"+x="];
NSRange range2 = [fragment rangeOfString:@"+y="];
NSRange range3 = [fragment rangeOfString:@"+w="];
NSRange range4 = [fragment rangeOfString:@"+h="];
NSRange range5 = [fragment rangeOfString:@"+t="];
range1.location += range1.length;
range1.length = range2.location - range1.location;
CGFloat x = [[fragment substringWithRange:range1] floatValue];
range2.location += range2.length;
range2.length = range3.location - range2.location;
CGFloat y = [[fragment substringWithRange:range2] floatValue];
range3.location += range3.length;
range3.length = range4.location - range3.location;
CGFloat w = [[fragment substringWithRange:range3] floatValue];
range4.location += range4.length;
range4.length = range5.location - range4.location;
CGFloat h = [[fragment substringWithRange:range4] floatValue];
range5.location += range5.length;
NSString *t = [fragment substringFromIndex:range5.location];
keyFrame = [self getKeyFrameWithX:x Y:y W:w H:h];
keyPreviewView = [[KMKeyPreviewView alloc] initWithFrame:keyFrame];
NSMutableString *text = nil;
NSArray *unicodes = [t 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]];
}
[keyPreviewView setLabelText:[NSString stringWithString:text]];
text = nil;
NSString *oskFontName = [self oskFontNameForKeyboardWithID:self.keyboardID languageID:self.languageID];
if (oskFontName == nil)
oskFontName = [self fontNameForKeyboardWithID:self.keyboardID languageID:self.languageID];
[keyPreviewView setLabelFont:oskFontName];
[[[self class] inputView] addSubview:keyPreviewView];
//keyPreviewView = [[UIView alloc] initWithFrame:CGRectMake(x-w/2.0f, y+6, w, h)];
//[keyPreviewView setBackgroundColor:[UIColor colorWithRed:1.0 green:0 blue:0 alpha:0.5]];
//[[[self class] inputView] addSubview:keyPreviewView];
}
else if ([fragment rangeOfString:@"dismissKeyPreview"].location != NSNotFound) {
BOOL isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad;
if (isPad || keyPreviewView == nil)
return;
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(dismissKeyPreview) object:nil];
[self performSelector:@selector(dismissKeyPreview) withObject:nil afterDelay:0.1];
[self clearSubKeyArrays];
}
else if ([fragment rangeOfString:@"insertText"].location != NSNotFound) {
[self dismissHelpBubble];
self.keymanHelpOn = NO;
}
else if ([fragment rangeOfString:@"menuKeyUp"].location != NSNotFound) {
[self dismissHelpBubble];
self.keymanHelpOn = NO;
if (kIsKeymanSystemKeyboard) {
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
[userData setBool:YES forKey:kKeymanKeyboardPickerDisplayedKey];
[userData synchronize];
}
}
else if ([fragment rangeOfString:@"hideKeyboard"].location != NSNotFound) {
[self dismissHelpBubble];
[self dismissSubKeys];
[self dismissKeyboardMenu];
}
else if ([fragment rangeOfString:@"showMore"].location != NSNotFound) {
[self dismissHelpBubble];
self.keymanHelpOn = NO;
[self dismissSubKeys];
[self dismissKeyboardMenu];
NSRange range1 = [fragment rangeOfString:@"+baseFrame="];
NSRange range2 = [fragment rangeOfString:@"+keys="];
NSRange range3 = [fragment rangeOfString:@"+font="];
range1.location += range1.length;
range1.length = range2.location - range1.location;
NSArray *frame = [[fragment substringWithRange:range1] componentsSeparatedByString:@","];
CGFloat x = [[frame objectAtIndex:0] floatValue];
CGFloat y = [[frame objectAtIndex:1] floatValue];
CGFloat w = [[frame objectAtIndex:2] floatValue];
CGFloat h = [[frame objectAtIndex:3] floatValue];
keyFrame = [self getKeyFrameWithX:x Y:y W:w H:h];
range2.location += range2.length;
range2.length = [fragment length] - range2.location;
if (range3.location != NSNotFound) {
range2.length -= [fragment length] - range3.location;
range3.location += range3.length;
specialOSKFont = [NSString stringWithString:[fragment substringFromIndex:range3.location]];
}
else {
specialOSKFont = nil;
}
NSString *keys = [fragment substringWithRange:range2];
NSArray *keyArray = [keys componentsSeparatedByString: @";"];
NSUInteger len = [keyArray count];
subKeyIDs = [keyArray mutableCopy];
subKeyTexts = [keyArray mutableCopy];
for(int i=0; i < len; i++) {
NSArray *values = [[keyArray objectAtIndex:i] componentsSeparatedByString: @":"];
if([values count] == 2) {
[subKeyIDs replaceObjectAtIndex:i withObject:[values objectAtIndex:0]];
[subKeyTexts replaceObjectAtIndex:i withObject:[values objectAtIndex:1]];
}
else if ([values count] == 1){
[subKeyIDs replaceObjectAtIndex:i withObject:[values objectAtIndex:0]];
NSString *subkeyText = [values objectAtIndex:0];
NSUInteger index = [subkeyText rangeOfString:@"-"].location;
if (index != NSNotFound)
subkeyText = [subkeyText substringFromIndex:index+1];
index = [subkeyText rangeOfString:@"_"].location;
if (index != NSNotFound)
subkeyText = [subkeyText substringFromIndex:index+1];
NSString *unicode = [NSString stringWithFormat:@"0x%@", subkeyText];
[subKeyTexts replaceObjectAtIndex:i withObject:unicode];
}
else {
[subKeyIDs replaceObjectAtIndex:i withObject:@""];
[subKeyTexts replaceObjectAtIndex:i withObject:@""];
}
}
//[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(clearSubKeyArrays) object:nil];
//[self performSelector:@selector(clearSubKeyArrays) withObject:nil afterDelay:0.525];
}
}
- (void)clearSubKeyArrays {
if (subKeysView == nil) {
[subKeys removeAllObjects];
[subKeyIDs removeAllObjects];
[subKeyTexts removeAllObjects];
}
}
- (void)dismissHelpBubble {
if (helpBubbleView != nil) {
[helpBubbleView removeFromSuperview];
helpBubbleView = nil;
}
}
- (void)dismissKeyPreview {
if (keyPreviewView != nil) {
[keyPreviewView removeFromSuperview];
keyPreviewView = nil;
}
}
- (CGSize)sizeFromFrame:(CGRect)frame {
return CGSizeMake(frame.size.width - frame.origin.x, frame.size.height - frame.origin.y);
}
#pragma mark - UIGestureRecognizer
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
// UILongPressGestureRecognizer implementation to show sub keys in a subview
- (void)holdAction:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
// Touch Ended
if (subKeysView != nil) {
[subKeysView removeFromSuperview];
[subKeysView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
subKeysView = nil;
//[self performSelector:@selector(setPopupVisible:) withObject:[NSNumber numberWithBool:NO] afterDelay:1.0];
[self setPopupVisible:[NSNumber numberWithBool:NO]];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanSubKeysMenuDismissedNotification object:self
userInfo:nil];
}
UIButton *button;
BOOL buttonClicked = NO;
for (int i = 0; i < [subKeys count]; i++) {
button = [subKeys objectAtIndex:i];
if (button.isHighlighted) {
button.highlighted = NO;
button.backgroundColor = subKeyColor;
[button setEnabled:NO];
[button sendActionsForControlEvents:UIControlEventTouchUpInside];
buttonClicked = YES;
break;
}
}
if (!buttonClicked) {
[self clearSubKeyArrays];
}
}
else if (sender.state == UIGestureRecognizerStateBegan) {
// Touch & Hold Began
CGPoint touchPoint = [sender locationInView:sender.view];
if ([[self class] isWKWebView:webView]) {
[(WKWebView *)webView evaluateJavaScript:@"langMenuPos();" completionHandler:^(id result, NSError *error) {
NSString *keyFrame = (NSString *)result;
[self setMenuKeyFrame:keyFrame];
if (CGRectContainsPoint(menuKeyFrame, touchPoint)) {
[self.inputDelegate updatedFragment:@"showKeyboardMenu"];
return;
}
[self touchHoldBegan];
}];
}
else {
NSString *keyFrame = [(UIWebView *)webView stringByEvaluatingJavaScriptFromString:@"langMenuPos();"];
[self setMenuKeyFrame:keyFrame];
if (CGRectContainsPoint(menuKeyFrame, touchPoint)) {
[self.inputDelegate updatedFragment:@"showKeyboardMenu"];
return;
}
[self touchHoldBegan];
}
}
else {
// Hold & Move
CGPoint touchPoint = [sender locationInView:[subKeysView containerView]];
UIButton *button;
for (int i = 0; i < [subKeys count]; i++) {
button = [subKeys objectAtIndex:i];
button.highlighted = NO;
button.backgroundColor = subKeyColor;
[button setEnabled:NO];
if (CGRectContainsPoint(button.frame, touchPoint)) {
[button setEnabled:YES];
button.highlighted = YES;
button.backgroundColor = subKeyColorHighlighted;
}
}
}
}
- (void)touchHoldBegan {
NSInteger subKeysCount = [subKeyIDs count];
CGFloat fontSize;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
fontSize = [UIFont buttonFontSize]*2;
else
fontSize = [UIFont buttonFontSize];
NSString *oskFontName = [self oskFontNameForKeyboardWithID:self.keyboardID languageID:self.languageID];
if (oskFontName == nil)
oskFontName = [self fontNameForKeyboardWithID:self.keyboardID languageID:self.languageID];
subKeys = [[NSMutableArray alloc] init];
if (subKeyIDs != nil && subKeysCount) {
for (int i = 0; i < subKeysCount; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTag:i];
[button setBackgroundColor:subKeyColor];
[button setRoundedBorderWithRadius:4.0
borderWidth:1.0
color:[UIColor grayColor]];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateDisabled];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateHighlighted];
if (oskFontName != nil)
[[button titleLabel] setFont:[UIFont fontWithName:oskFontName size:fontSize]];
else
[[button titleLabel] setFont:[UIFont systemFontOfSize:fontSize]];
if(specialOSKFont != nil) {
[[button titleLabel] setFont:[UIFont fontWithName:@"KeymanwebOsk" size:fontSize]];
[button setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled];
}
NSMutableString *buttonTitle = nil;
NSString *subKeyText = [subKeyTexts objectAtIndex:i];
if([subKeyText rangeOfString:@"0x"].location != NSNotFound){
NSArray *unicodes = [subKeyText componentsSeparatedByString: @","];
NSScanner *s = nil;
for(int i=0;i<[unicodes count];i++) {
s = [NSScanner scannerWithString:[unicodes objectAtIndex:i]];
unsigned int unicode;
[s scanHexInt:&unicode];
if(buttonTitle == nil)
buttonTitle = [NSMutableString stringWithFormat:@"%C", (unsigned short) unicode];
else
[buttonTitle appendString:[NSString stringWithFormat:@"%C", (unsigned short) unicode]];
}
}
else {
buttonTitle = [NSMutableString stringWithString:subKeyText];
}
[button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:[NSString stringWithString:buttonTitle] forState:UIControlStateNormal];
buttonTitle = nil;
[button setTintColor:[UIColor colorWithRed:181.0/255.0 green:181.0/255.0 blue:181.0/255.0 alpha:1.0]];
[button setEnabled:NO];
[subKeys addObject:button];
}
[self dismissKeyPreview];
subKeysView = [[KMSubKeysView alloc] initWithKeyFrame:keyFrame subKeys:subKeys];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanSubKeysMenuWillShowNotification object:self
userInfo:nil];
[[[self class] inputView] addSubview:subKeysView];
[self setPopupVisible:[NSNumber numberWithBool:YES]];
}
}
- (void)buttonClick:(id)sender {
NSInteger keyIndex = [sender tag];
if (keyIndex < [subKeyIDs count] && keyIndex < [subKeyTexts count]) {
NSString *subKeyID = [subKeyIDs objectAtIndex:keyIndex];
NSString *subKeyText = [subKeyTexts objectAtIndex:keyIndex];
NSString *jsString = [NSString stringWithFormat:@"executePopupKey('%@','%@');", subKeyID, subKeyText];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
}
[subKeys removeAllObjects];
[subKeyIDs removeAllObjects];
[subKeyTexts removeAllObjects];
}
- (BOOL)isSubKeysMenuVisible {
return subKeysView == nil?NO:YES;
}
- (void)dismissSubKeys {
if (subKeysView != nil) {
[subKeysView removeFromSuperview];
[subKeysView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
subKeysView = nil;
//[self performSelector:@selector(setPopupVisible:) withObject:[NSNumber numberWithBool:NO] afterDelay:1.0];
[self setPopupVisible:[NSNumber numberWithBool:NO]];
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanSubKeysMenuDismissedNotification object:self
userInfo:nil];
}
[subKeys removeAllObjects];
[subKeyIDs removeAllObjects];
[subKeyTexts removeAllObjects];
}
- (void)setPopupVisible:(NSNumber *)value {
BOOL visible = [value boolValue];
NSString *jsString = [NSString stringWithFormat:@"popupVisible(%@);", visible?@"1":@"0"];
if ([[self class] isWKWebView:webView])
[(WKWebView *)webView evaluateJavaScript:jsString completionHandler:nil];
else
[(UIWebView *)webView stringByEvaluatingJavaScriptFromString:jsString];
}
- (void)registerCustomFonts {
NSArray *directoryContents = [[NSArray alloc] initWithArray:[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self activeFontDirectory] error:nil]];
NSMutableDictionary *fontInfo = nil;
NSString *fontFilename = nil;
if (keymanFonts_ == nil)
keymanFonts_ = [[NSMutableDictionary alloc] init];
for (fontFilename in directoryContents) {
if ([fontFilename rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [fontFilename rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
fontInfo = [self.keymanFonts objectForKey:fontFilename];
if (fontInfo == nil) {
fontInfo = [[self registerFontWithFilename:fontFilename] mutableCopy];
if (fontInfo != nil)
[keymanFonts_ setObject:fontInfo forKey:fontFilename];
}
else if (![[fontInfo objectForKey:kKeymanFontRegisteredKey] boolValue]) {
fontInfo = [[self registerFontWithFilename:fontFilename] mutableCopy];
if (fontInfo != nil)
[keymanFonts_ setObject:fontInfo forKey:fontFilename];
}
}
}
}
- (void)unregisterCustomFonts {
NSArray *directoryContents = [[NSArray alloc] initWithArray:[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self activeFontDirectory] error:nil]];
NSString *fontFilename = nil;
NSMutableDictionary *fontInfo = nil;
if (keymanFonts_ == nil)
return;
for (fontFilename in directoryContents) {
if ([fontFilename rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [fontFilename rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
fontInfo = [self.keymanFonts objectForKey:fontFilename];
if (fontInfo != nil && [[fontInfo objectForKey:kKeymanFontRegisteredKey] boolValue]) {
if ([self unregisterFontWithFilename:fontFilename]) {
[fontInfo setObject:[NSNumber numberWithBool:NO] forKey:kKeymanFontRegisteredKey];
[keymanFonts_ setObject:fontInfo forKey:fontFilename];
}
}
}
}
}
- (NSDictionary *)registerFontWithFilename:(NSString *)fontFilename {
NSString *fontPath = [[self activeFontDirectory] stringByAppendingPathComponent:fontFilename];
if (![[NSFileManager defaultManager] fileExistsAtPath:fontPath])
return nil;
BOOL didRegister = NO;
NSString *fontName = nil;
NSURL *fontUrl = [NSURL fileURLWithPath:fontPath];
CGDataProviderRef provider = CGDataProviderCreateWithURL((__bridge CFURLRef)fontUrl);
CGFontRef font = CGFontCreateWithDataProvider(provider);
fontName = (NSString *)CFBridgingRelease(CGFontCopyPostScriptName(font));
CGDataProviderRelease(provider);
CGFontRelease(font);
if (![self fontExists:fontName]) {
CFErrorRef error;
didRegister = CTFontManagerRegisterFontsForURL((__bridge CFURLRef)fontUrl, kCTFontManagerScopeNone, &error);
if (!didRegister) {
CFStringRef errorDescription = CFErrorCopyDescription(error);
[self KMLog:[NSString stringWithFormat:@"Failed to register font: %@ reason: %@", [fontPath lastPathComponent], errorDescription] checkDebugPrinting:NO];
CFRelease(errorDescription);
error = nil;
}
else
[self KMLog:[NSString stringWithFormat:@"Registered font: %@", fontFilename] checkDebugPrinting:YES];
}
return [NSDictionary dictionaryWithObjectsAndKeys:fontName, kKeymanFontNameKey, [NSNumber numberWithBool:didRegister], kKeymanFontRegisteredKey, nil];
}
- (BOOL)unregisterFontWithFilename:(NSString *)fontFilename {
NSString *fontPath = [[self activeFontDirectory] stringByAppendingPathComponent:fontFilename];
if (![[NSFileManager defaultManager] fileExistsAtPath:fontPath])
return NO;
CFErrorRef error;
BOOL didUnregister = CTFontManagerUnregisterFontsForURL((__bridge CFURLRef)[NSURL fileURLWithPath:fontPath], kCTFontManagerScopeNone, &error);
if (!didUnregister) {
CFStringRef errorDescription = CFErrorCopyDescription(error);
[self KMLog:[NSString stringWithFormat:@"Failed to unregister font: %@ reason: %@", [fontPath lastPathComponent], errorDescription] checkDebugPrinting:NO];
CFRelease(errorDescription);
error = nil;
}
else
[self KMLog:[NSString stringWithFormat:@"Unregistered font: %@", fontFilename] checkDebugPrinting:YES];
return didUnregister;
}
- (BOOL)fontExists:(NSString *)fontName {
BOOL exists = NO;
NSArray *fontFamilyNames = [UIFont familyNames];
for (NSString *familyName in fontFamilyNames)
{
NSArray *fontNames = [UIFont fontNamesForFamilyName:familyName];
for (NSString *name in fontNames)
{
if ([fontName isEqualToString:name]) {
exists = YES;
break;
}
}
if (exists)
break;
}
return exists;
}
- (NSString *)fontNameForKeyboardWithID:(NSString *)keyboardID languageID:(NSString *)languageID {
NSUserDefaults *userData = [self activeUserDefaults];
NSArray *userKeyboards = [userData arrayForKey:kKeymanUserKeyboardsListKey];
NSString *fontName = nil;
if (userKeyboards != nil) {
NSString *kbId = nil;
NSString *langId = nil;
for (NSDictionary *kb in userKeyboards) {
kbId = [kb objectForKey:kKeymanKeyboardIdKey];
langId = [kb objectForKey:kKeymanLanguageIdKey];
if ([kbId isEqualToString:keyboardID] && [langId isEqualToString:languageID]) {
NSString *font = [kb objectForKey:kKeymanFontKey];
NSString *fontFilename = [self fontFilenameFromJSONFont:font];
fontName = [[self.keymanFonts objectForKey:fontFilename] objectForKey:kKeymanFontNameKey];
break;
}
}
}
if (fontName == nil && self.keyboardsDictionary != nil) {
NSString *kbKey = [NSString stringWithFormat:@"%@_%@", languageID, keyboardID];
NSString *fontFilename = [self fontFilenameFromJSONFont:[[self.keyboardsDictionary objectForKey:kbKey] objectForKey:kKeymanFontKey]];
fontName = [[self.keymanFonts objectForKey:fontFilename] objectForKey:kKeymanFontNameKey];
}
return fontName;
}
- (NSString *)oskFontNameForKeyboardWithID:(NSString *)keyboardID languageID:(NSString *)languageID {
NSUserDefaults *userData = [self activeUserDefaults];
NSArray *userKeyboards = [userData arrayForKey:kKeymanUserKeyboardsListKey];
NSString *oskFontName = nil;
if (userKeyboards != nil) {
NSString *kbId = nil;
NSString *langId = nil;
for (NSDictionary *kb in userKeyboards) {
kbId = [kb objectForKey:kKeymanKeyboardIdKey];
langId = [kb objectForKey:kKeymanLanguageIdKey];
if ([kbId isEqualToString:keyboardID] && [langId isEqualToString:languageID]) {
NSString *oskFont = [kb objectForKey:kKeymanOskFontKey];
NSString *oskFontFilename = [self fontFilenameFromJSONFont:oskFont];
oskFontName = [[self.keymanFonts objectForKey:oskFontFilename] objectForKey:kKeymanFontNameKey];
break;
}
}
}
if (oskFontName == nil && self.keyboardsDictionary != nil) {
NSString *kbKey = [NSString stringWithFormat:@"%@_%@", languageID, keyboardID];
NSString *oskFontFilename = [self fontFilenameFromJSONFont:[[self.keyboardsDictionary objectForKey:kbKey] objectForKey:kKeymanOskFontKey]];
oskFontName = [[self.keymanFonts objectForKey:oskFontFilename] objectForKey:kKeymanFontNameKey];
}
return oskFontName;
}
- (BOOL)isRTLKeyboardWithID:(NSString *)keyboardID languageID:(NSString *)languageID {
NSUserDefaults *userData = [self activeUserDefaults];
NSArray *userKeyboards = [userData arrayForKey:kKeymanUserKeyboardsListKey];
NSString *isRTL = nil;
if (userKeyboards != nil) {
NSString *kbId = nil;
NSString *langId = nil;
for (NSDictionary *kb in userKeyboards) {
kbId = [kb objectForKey:kKeymanKeyboardIdKey];
langId = [kb objectForKey:kKeymanLanguageIdKey];
if([kbId isEqualToString:keyboardID] && [langId isEqualToString:languageID]) {
isRTL = [kb objectForKey:kKeymanKeyboardRTLKey];
break;
}
}
}
if (isRTL == nil && self.keyboardsDictionary != nil) {
NSString *kbKey = [NSString stringWithFormat:@"%@_%@", languageID, keyboardID];
isRTL = [[self.keyboardsDictionary objectForKey:kbKey] objectForKey:kKeymanKeyboardRTLKey];
}
if (isRTL != nil && [isRTL isEqualToString:@"Y"])
return YES;
else
return NO;
}
- (NSString *)fontFilenameFromJSONFont:(NSString *)jsonFont {
if (jsonFont == nil)
return nil;
NSString *fontFilename = nil;
NSError *e;
if ([jsonFont rangeOfString:@".ttf"].location != NSNotFound || [jsonFont rangeOfString:@".otf"].location != NSNotFound) {
if ([[jsonFont substringFromIndex:jsonFont.length-4] isEqualToString:@".ttf"] || [[jsonFont substringFromIndex:jsonFont.length-4] isEqualToString:@".otf"])
return jsonFont;
}
NSDictionary *fontDict = [NSJSONSerialization JSONObjectWithData:[jsonFont dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&e];
if (fontDict != nil) {
id fontFiles = [fontDict objectForKey:kKeymanFontFilesKey];
if ([fontFiles isKindOfClass:[NSArray class]]) {
for (NSString *file in fontFiles) {
if ([file rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [file rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound) {
fontFilename = [NSString stringWithString:file];
break;
}
}
}
else if ([fontFiles isKindOfClass:[NSString class]]) {
NSString *file = (NSString *)fontFiles;
if ([file rangeOfString:@".ttf" options:NSBackwardsSearch].location != NSNotFound || [file rangeOfString:@".otf" options:NSBackwardsSearch].location != NSNotFound)
fontFilename = [NSString stringWithString:file];
}
}
return fontFilename;
}
- (NSString *)jsFontForKeyboardID:(NSString *)kbID languageID:(NSString *)langID {
NSString *kbKey = [NSString stringWithFormat:@"%@_%@", langID, kbID];
NSDictionary *kbDict = [self.keyboardsDictionary objectForKey:kbKey];
NSString *jsFont = [kbDict objectForKey:kKeymanFontKey];
return jsFont?jsFont:@"''";
}
- (NSString *)jsOskFontForKeyboardID:(NSString *)kbID languageID:(NSString *)langID {
NSString *kbKey = [NSString stringWithFormat:@"%@_%@", langID, kbID];
NSDictionary *kbDict = [self.keyboardsDictionary objectForKey:kbKey];
NSString *jsOskFont = [kbDict objectForKey:kKeymanOskFontKey];
return jsOskFont?jsOskFont:@"''";
}
- (NSString *)jsFontFromFontDictionary:(NSDictionary *)fontDict {
NSMutableString *str = nil;
if (fontDict != nil && fontDict.count) {
NSError *e;
NSData *data = [NSJSONSerialization dataWithJSONObject:fontDict options:0 error:&e];
str = [[NSMutableString alloc] initWithData:data encoding:NSASCIIStringEncoding];
[str replaceOccurrencesOfString:kKeymanFontFilenameKey withString:kKeymanFontFilesKey options:0 range:NSMakeRange(0, str.length)]; // Font filename is deprecated
[str replaceOccurrencesOfString:kKeymanFontSourceKey withString:kKeymanFontFilesKey options:0 range:NSMakeRange(0, str.length)];
}
else
return @"''";
return [NSString stringWithString:str];
}
+ (NSString *)md5FromString:(NSString *)str {
const char *cStr = [str UTF8String];
unsigned char digest[16];
CC_MD5(cStr, (CC_LONG)strlen(cStr), digest);
NSMutableString *md5String = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[md5String appendFormat:@"%02x", digest[i]];
return [NSString stringWithString:md5String];
}
+ (NSString *)sha1FromString:(NSString *)str {
const char *cStr = [str cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cStr length:str.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (CC_LONG)data.length, digest);
NSMutableString *sha1String = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
[sha1String appendFormat:@"%02x", digest[i]];
return [NSString stringWithString:sha1String];
}
- (void)KMLog:(NSString *)logStr checkDebugPrinting:(BOOL)checkDebugPrinting {
if (!checkDebugPrinting || (checkDebugPrinting && self.debugPrintingOn)) {
NSLog(@"%@", logStr);
[[NSNotificationCenter defaultCenter] postNotificationName:kKeymanDebugLogNotification object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:logStr, @"log", nil]];
//[self.debugLog appendString:[NSString stringWithFormat:@"[%@] %@\n", [NSDate date], logStr]];
}
}
/*
- (NSMutableString *)debugLog {
if (_debugLog == nil)
_debugLog = [NSMutableString stringWithString:@""];
return _debugLog;
}*/
#pragma mark - InputViewController methods
- (void)updateViewConstraints {
[self dismissSubKeys];
[self dismissKeyPreview];
[self dismissKeyboardMenu];
[self resizeKeyboardIfNeeded];
}
- (void)inputViewDidLoad {
[self dismissSubKeys];
[self dismissKeyPreview];
[self dismissKeyboardMenu];
[self resizeKeyboard];
NSUserDefaults *aUserDef = [self activeUserDefaults];
NSUserDefaults *sUserDef = [NSUserDefaults standardUserDefaults];
NSDate *xDate = [[sUserDef objectForKey:kKeymanSynchronizeSWKeyboardKey] objectAtIndex:0];
NSDate *nDate = [[aUserDef objectForKey:kKeymanSynchronizeSWKeyboardKey] objectAtIndex:0];
BOOL shouldSynchronize = YES;
if (xDate != nil && nDate != nil)
shouldSynchronize = ([nDate compare:xDate] != NSOrderedSame)?YES:NO;
else if (nDate == nil)
shouldSynchronize = NO;
if ((!didSynchronize || shouldSynchronize) && [self canAccessSharedContainer]) {
[self synchronizeSWKeyboard];
if (keyboardID_ != nil && languageID_ != nil) {
shouldReloadKeyboard = YES;
[self reloadKeyboard];
}
didSynchronize = YES;
[sUserDef setObject:[aUserDef objectForKey:kKeymanSynchronizeSWKeyboardKey] forKey:kKeymanSynchronizeSWKeyboardKey];
[sUserDef synchronize];
}
}
- (void)inputViewWillRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self dismissSubKeys];
[self dismissKeyPreview];
[self dismissKeyboardMenu];
[self resizeKeyboardWithOrientation:toInterfaceOrientation];
if (self.keymanHelpOn) {
[helpBubbleView removeFromSuperview];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showHelpBubble) object:nil];
[self performSelector:@selector(showHelpBubble) withObject:nil afterDelay:1.5];
}
didResizeToOrientation = YES;
}
- (void)synchronizeSWKeyboard {
[self copyUserDefaultsFromSharedContainer];
[self copyKeymanFilesFromSharedContainer];
}
- (void)setMenuKeyFrame:(NSString *)frameStr {
CGRect frame = CGRectZero;
if ([frameStr length]) {
NSArray *values = [frameStr componentsSeparatedByString: @","];
CGFloat x = [[values objectAtIndex:0] floatValue];
CGFloat y = [[values objectAtIndex:1] floatValue];
CGFloat w = [[values objectAtIndex:2] floatValue];
CGFloat h = [[values objectAtIndex:3] floatValue];
BOOL isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad;
CGFloat adjX = 0.0;
CGFloat adjY = isPad?-0.5:-1.0;
frame = CGRectMake(x+adjX -w/2.0f, y -adjY, w, h);
}
menuKeyFrame = frame;
}
- (void)showKeyboardMenu:(KMInputViewController *)ic closeButtonTitle:(NSString *)closeButtonTitle {
UIView *parentView = ic.view;
if (parentView == nil)
parentView = webView;
if ([[self class] isWKWebView:webView]) {
[(WKWebView *)webView evaluateJavaScript:@"langMenuPos();" completionHandler:^(id result, NSError *error) {
NSString *keyFrame = (NSString *)result;
[self setMenuKeyFrame:keyFrame];
if (!CGRectEqualToRect(menuKeyFrame, CGRectZero)) {
[keyboardMenuView removeFromSuperview];
keyboardMenuView = [[KMKeyboardMenuView alloc] initWithKeyFrame:menuKeyFrame inputViewController:ic closeButtonTitle:closeButtonTitle];
[parentView addSubview:keyboardMenuView];
}
}];
}
else {
NSString *keyFrame = [(UIWebView *)webView stringByEvaluatingJavaScriptFromString:@"langMenuPos();"];
[self setMenuKeyFrame:keyFrame];
if (!CGRectEqualToRect(menuKeyFrame, CGRectZero)) {
[keyboardMenuView removeFromSuperview];
keyboardMenuView = [[KMKeyboardMenuView alloc] initWithKeyFrame:menuKeyFrame inputViewController:ic closeButtonTitle:closeButtonTitle];
[parentView addSubview:keyboardMenuView];
}
}
}
- (void)dismissKeyboardMenu {
if (keyboardMenuView != nil) {
[keyboardMenuView removeFromSuperview];
keyboardMenuView = nil;
}
}
- (BOOL)isKeyboardMenuVisible {
return keyboardMenuView == nil?NO:YES;
}
- (BOOL)isSystemKeyboardTopBarEnabled {
return YES;
}
- (BOOL)copyKeymanFilesToTemp {
if (NSTemporaryDirectory() == nil)
return NO;
NSFileManager *fileMan = [NSFileManager defaultManager];
NSString *activeKeymanDir = [NSString stringWithString:[self activeKeymanDirectory]];
NSString *tempKeymanDir = [NSString stringWithString:[NSTemporaryDirectory() stringByAppendingPathComponent:@"keyman"]];
[[NSFileManager defaultManager] createDirectoryAtPath:tempKeymanDir withIntermediateDirectories:YES attributes:nil error:nil];
NSString *activeLangDir = [NSString stringWithString:[self activeLanguageDirectory]];
NSString *tempLangDir = [NSString stringWithString:[tempKeymanDir stringByAppendingPathComponent:@"languages"]];
[[NSFileManager defaultManager] createDirectoryAtPath:tempLangDir withIntermediateDirectories:YES attributes:nil error:nil];
NSString *activeFontDir = [NSString stringWithString:[self activeFontDirectory]];
NSString *tempFontDir = [NSString stringWithString:[tempKeymanDir stringByAppendingPathComponent:@"fonts"]];
[[NSFileManager defaultManager] createDirectoryAtPath:tempFontDir withIntermediateDirectories:YES attributes:nil error:nil];
NSArray *dirContents = [fileMan contentsOfDirectoryAtPath:activeKeymanDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [activeKeymanDir stringByAppendingPathComponent:file];
NSString *destinationPath = [tempKeymanDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/* BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
if (error)
[self KMLog:[NSString stringWithFormat:@"%@", error.description] checkDebugPrinting:NO];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
if (error)
[self KMLog:[NSString stringWithFormat:@"%@", error.description] checkDebugPrinting:NO];
}
}*/
}
dirContents = [fileMan contentsOfDirectoryAtPath:activeLangDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [activeLangDir stringByAppendingPathComponent:file];
NSString *destinationPath = [tempLangDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/* BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
if (error)
[self KMLog:[NSString stringWithFormat:@"%@", error.description] checkDebugPrinting:NO];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
if (error)
[self KMLog:[NSString stringWithFormat:@"%@", error.description] checkDebugPrinting:NO];
}
}*/
}
dirContents = [fileMan contentsOfDirectoryAtPath:activeFontDir error:nil];
for (NSString *file in dirContents){
NSString *pathToCopy = [activeFontDir stringByAppendingPathComponent:file];
NSString *destinationPath = [tempFontDir stringByAppendingPathComponent:file];
[self copyAndExcludeFromBackup:pathToCopy destinationPath:destinationPath];
/* BOOL fileExists = [fileMan fileExistsAtPath:pathToCopy isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
// copy if destination does not exist or replace if source is newer
if (![fileMan fileExistsAtPath:destinationPath])
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:nil];
else if ([self compareFileModDates:pathToCopy destinationPath:destinationPath] > 0) {
[fileMan removeItemAtPath:destinationPath error:&error];
if (error)
[self KMLog:[NSString stringWithFormat:@"%@", error.description] checkDebugPrinting:NO];
[fileMan copyItemAtPath:pathToCopy toPath:destinationPath error:&error];
if (error)
[self KMLog:[NSString stringWithFormat:@"%@", error.description] checkDebugPrinting:NO];
}
}*/
}
return YES;
}
@end